From 904d14064f51ec9a3f74882a312a70eea3a6784c Mon Sep 17 00:00:00 2001
From: Awiteb
Date: Wed, 6 Aug 2025 07:58:06 +0000
Subject: [PATCH] feat: Sign using NIP-07
Signed-off-by: Awiteb
---
CHANGELOG.md | 1 +
Cargo.lock | 39 +++++++++++++++++++++
Cargo.toml | 25 +++++++-------
README.md | 2 +-
docs/SUMMARY.md | 1 +
docs/commands.md | 3 ++
docs/config/nip07.md | 22 ++++++++++++
docs/index.html | 2 +-
src/cli/commands/config/mod.rs | 8 ++++-
src/cli/commands/config/nip07.rs | 58 ++++++++++++++++++++++++++++++++
src/cli/commands/mod.rs | 43 ++++++++++++++---------
src/cli/config.rs | 5 ++-
src/cli/mod.rs | 31 +++++++++++++++--
src/cli/options_state.rs | 56 ++++++++++++++++++++++++++++++
src/cli/parsers.rs | 4 +--
src/error.rs | 10 ++++--
src/nostr_utils/mod.rs | 2 +-
17 files changed, 272 insertions(+), 40 deletions(-)
create mode 100644 docs/config/nip07.md
create mode 100644 src/cli/commands/config/nip07.rs
create mode 100644 src/cli/options_state.rs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 01cc909..b4d6bac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support signing using NIP-46 bunker - by Awiteb
- Keyring the secret key `n34 config keyring --enable` - by Awiteb
- New flag to `patch apply and merge` to mention patches - by Awiteb
+- Sign using NIP-07 - by Awiteb
### Dependencies
diff --git a/Cargo.lock b/Cargo.lock
index 69c85c3..db85029 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -837,6 +837,12 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
[[package]]
name = "hyper"
version = "1.6.0"
@@ -850,6 +856,7 @@ dependencies = [
"http",
"http-body",
"httparse",
+ "httpdate",
"itoa",
"pin-project-lite",
"smallvec",
@@ -1235,6 +1242,7 @@ dependencies = [
"either",
"futures",
"nostr",
+ "nostr-browser-signer-proxy",
"nostr-connect",
"nostr-keyring",
"nostr-sdk",
@@ -1279,6 +1287,25 @@ dependencies = [
"url",
]
+[[package]]
+name = "nostr-browser-signer-proxy"
+version = "0.43.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36272167bc7aa8b87d0638675d1b0fd7bff04eb6ab49f97303229a06a7bbc849"
+dependencies = [
+ "atomic-destructor",
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "nostr",
+ "serde",
+ "serde_json",
+ "tokio",
+ "tracing",
+ "uuid",
+]
+
[[package]]
name = "nostr-connect"
version = "0.43.0"
@@ -2494,6 +2521,18 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+[[package]]
+name = "uuid"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
+dependencies = [
+ "getrandom 0.3.3",
+ "js-sys",
+ "serde",
+ "wasm-bindgen",
+]
+
[[package]]
name = "valuable"
version = "0.1.1"
diff --git a/Cargo.toml b/Cargo.toml
index 479f6a1..b64c30a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,18 +29,19 @@ nostr-sdk = { version = "0.43.0", default-features = false }
serde = { version = "1.0.219", features = ["derive"] }
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
-chrono = "0.4.41"
-convert_case = "0.8.0"
-dirs = "6.0.0"
-easy-ext = "1.0.2"
-either = "1.15.0"
-futures = "0.3.31"
-regex = "1.11.1"
-tempfile = "3.20.0"
-thiserror = "2.0.12"
-toml = "0.9.4"
-tracing = "0.1.41"
-tracing-subscriber = "0.3.19"
+chrono = "0.4.41"
+convert_case = "0.8.0"
+dirs = "6.0.0"
+easy-ext = "1.0.2"
+either = "1.15.0"
+futures = "0.3.31"
+nostr-browser-signer-proxy = "0.43.0"
+regex = "1.11.1"
+tempfile = "3.20.0"
+thiserror = "2.0.12"
+toml = "0.9.4"
+tracing = "0.1.41"
+tracing-subscriber = "0.3.19"
[profile.release]
lto = "fat"
diff --git a/README.md b/README.md
index bf65c70..40b68c6 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ details, see the following section.
- [X] Proof of Work ([NIP-13])
- [X] `nostr:` URI scheme, in the issue/reply content ([NIP-21])
- [X] Signing using bunker ([NIP-46])
-- [ ] Signing using [NIP-07] proxy ([nostr-browser-signer-proxy])
+- [X] Signing using [NIP-07] proxy ([nostr-browser-signer-proxy])
- [ ] Code Snippets ([NIP-C0])
- [X] In device relays and repos bookmark (`sets` command)
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index aa52e77..78a8509 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -9,6 +9,7 @@
- [Manage Configuration](config/README.md)
- [Default PoW Difficulty](config/pow.md)
- [Fallback Relays](config/relays.md)
+ - [NIP-07 Browser Signer Proxy](config/nip07.md)
- [NIP-46 Bunker](config/bunker.md)
- [Secret Key Keyring](config/keyring.md)
- [Manage Repositories](repo/README.md)
diff --git a/docs/commands.md b/docs/commands.md
index c7f28f4..1adce82 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -8,6 +8,9 @@ The `n34` command-line tool accepts the following options:
signing events.
- `-b`, `--bunker-url`: The URL of a NIP-46 bunker service used for remote
signing of events.
+- `-7`, `--nip07`: Enables signing events using the browser's NIP-07
+ extension. Listens on `127.0.0.1:51034`. You can configure the address with `n34
+ config nip07`
- `-r`, `--relays`: A relay to read from and write to. This option can be
specified multiple times to connect to several relays.
- `--pow`: Sets the Proof of Work difficulty required when creating events.
diff --git a/docs/config/nip07.md b/docs/config/nip07.md
new file mode 100644
index 0000000..3e2f9c2
--- /dev/null
+++ b/docs/config/nip07.md
@@ -0,0 +1,22 @@
+# NIP-07 Browser Signer Proxy
+
+> `n34 config nip07`
+
+**Usage:**
+```
+Manage the NIP-07 browser signer proxy by enabling or disabling it and configuring the `ip:port` address.
+
+Usage: n34 config nip07 [OPTIONS] <--enable|--disable>
+
+Options:
+ --enable Enable NIP-07 as the default signer
+ --disable Disable NIP-07 as the default signer
+ --addr Set the `ip:port` for the browser signer proxy (default: 127.0.0.1:51034)
+```
+
+Use [NIP-07] (Browser Extension Signer) as your default signer. This is achieved
+by running a proxy at the specified `ADDR`, which defaults to `127.0.0.1:51034`.
+The proxy forwards `n34` requests to the browser signer and relays the responses
+back.
+
+[NIP-07]: https://github.com/nostr-protocol/nips/blob/master/07.md
diff --git a/docs/index.html b/docs/index.html
index 4e824b2..c70ebff 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -390,7 +390,7 @@
Signing using bunker (NIP-46)
-
+
Signing using NIP-07 proxy (nostr-browser-signer-proxy)
diff --git a/src/cli/commands/config/mod.rs b/src/cli/commands/config/mod.rs
index 24968be..8b81c31 100644
--- a/src/cli/commands/config/mod.rs
+++ b/src/cli/commands/config/mod.rs
@@ -18,6 +18,8 @@
mod bunker;
/// `config keyring` subcommand
mod keyring;
+/// `config nip07` subcommand
+mod nip07;
/// `config pow` subcommand
mod pow;
/// `config relays` subcommand
@@ -27,6 +29,7 @@ use clap::Subcommand;
use self::bunker::BunkerArgs;
use self::keyring::KeyringArgs;
+use self::nip07::Nip07Args;
use self::pow::PowArgs;
use self::relays::RelaysArgs;
use super::CliOptions;
@@ -45,10 +48,13 @@ pub enum ConfigSubcommands {
/// Managing the secret key keyring, including enabling, disabling, or
/// resetting it.
Keyring(KeyringArgs),
+ /// Controls the NIP-07 browser signer proxy, turning it on or off, and
+ /// configures the `ip:port` address.
+ Nip07(Nip07Args),
}
impl CommandRunner for ConfigSubcommands {
async fn run(self, options: CliOptions) -> N34Result<()> {
- crate::run_command!(self, options, & Pow Relays Bunker Keyring)
+ crate::run_command!(self, options, & Pow Relays Bunker Keyring Nip07)
}
}
diff --git a/src/cli/commands/config/nip07.rs b/src/cli/commands/config/nip07.rs
new file mode 100644
index 0000000..3751224
--- /dev/null
+++ b/src/cli/commands/config/nip07.rs
@@ -0,0 +1,58 @@
+// n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr
+// Copyright (C) 2025 Awiteb
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use std::net::SocketAddr;
+
+use clap::{ArgGroup, Args};
+
+use crate::{
+ cli::{CliOptions, options_state::DEFAULT_NIP07_PROXY_ADDR, traits::CommandRunner},
+ error::N34Result,
+};
+
+#[derive(Args, Debug)]
+#[clap(
+ group(
+ ArgGroup::new("options")
+ .required(true)
+ )
+)]
+pub struct Nip07Args {
+ /// Enable NIP-07 as the default signer.
+ #[arg(long, group = "options")]
+ enable: bool,
+ /// Disable NIP-07 as the default signer.
+ #[arg(long, group = "options", group = "disable_options")]
+ disable: bool,
+ /// Set the default `ip:port` for the browser signer proxy.
+ #[arg(long, group = "disable_options")]
+ addr: Option,
+}
+
+impl CommandRunner for Nip07Args {
+ const NEED_SIGNER: bool = false;
+
+ async fn run(self, mut options: CliOptions) -> N34Result<()> {
+ if self.enable {
+ let addr = self.addr.unwrap_or(DEFAULT_NIP07_PROXY_ADDR);
+ options.config.nip07 = Some(addr)
+ } else {
+ options.config.nip07 = None
+ }
+
+ options.config.dump()
+ }
+}
diff --git a/src/cli/commands/mod.rs b/src/cli/commands/mod.rs
index 5fa3341..ea94134 100644
--- a/src/cli/commands/mod.rs
+++ b/src/cli/commands/mod.rs
@@ -31,7 +31,7 @@ use std::fmt;
use std::sync::Arc;
use std::time::Duration;
-use clap::{ArgGroup, Args, Parser};
+use clap::{Args, Parser};
use nostr::key::{Keys, PublicKey, SecretKey};
use nostr::nips::nip46::NostrConnectURI;
use nostr::signer::{IntoNostrSigner, NostrSigner};
@@ -44,6 +44,7 @@ use self::reply::ReplyArgs;
use self::repo::RepoSubcommands;
use self::sets::SetsSubcommands;
use super::CliConfig;
+use super::options_state::OptionsState;
use super::types::RelayOrSet;
use super::{parsers, traits::CommandRunner};
use crate::cli::Cli;
@@ -59,21 +60,18 @@ pub const DEFAULT_FALLBACK_PATH: &str = "I_DO_NOT_KNOW_WHY_CLAP_DO_NOT_SUPPORT_L
const BUNKER_TIMEOUT: Duration = Duration::from_secs(60 * 3);
/// The command-line interface options
-#[derive(Args, Clone)]
-#[clap(
- group(
- ArgGroup::new("signer")
- .args(&["secret_key", "bunker_url"])
- .required(false)
- )
-)]
+#[derive(Args)]
pub struct CliOptions {
/// Your Nostr secret key
- #[arg(short, long)]
+ #[arg(short, long, group = "signer")]
pub secret_key: Option,
/// NIP-46 bunker url used for signing events
- #[arg(short, long, value_parser=parsers::parse_bunker_url)]
+ #[arg(short, long, group = "signer", value_parser = parsers::parse_bunker_url)]
pub bunker_url: Option,
+ /// Enables signing events using the browser's NIP-07 extension. Listens on
+ /// `127.0.0.1:51034`.
+ #[arg(short = '7', long, group = "signer")]
+ pub nip07: bool,
/// Fallbacks relay to write and read from it. Multiple relays can be
/// passed.
#[arg(short, long)]
@@ -86,6 +84,10 @@ pub struct CliOptions {
hide_default_value = true, value_parser = parsers::parse_config_path
)]
pub config: CliConfig,
+ /// The state of options. Some values that are used by them but should not
+ /// be entered via the CLI
+ #[arg(skip)]
+ pub state: OptionsState,
}
/// N34 commands
@@ -124,7 +126,7 @@ pub enum Commands {
impl CliOptions {
/// Gets the public key of the user.
pub async fn pubkey(&self) -> N34Result {
- let Some(signer) = self.signer()? else {
+ let Some(signer) = self.signer().await? else {
unreachable!(
"This method should only be called when a signer is required. If this panic \
occurs, it indicates a bug where the command failed to properly require a signer \
@@ -136,10 +138,21 @@ impl CliOptions {
}
/// Returns the signer
- pub fn signer(&self) -> N34Result