From aa00387c211c6ca8a38c229ea3f4229e6e52f27d Mon Sep 17 00:00:00 2001 From: Ryan Breen Date: Thu, 22 Dec 2022 16:47:38 -0500 Subject: [PATCH] generate nostr keys using NIP-06 derivation key --- README.md | 26 ++++++++++++++++++++------ src/cli.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 051be27..3041eba 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ instead of a mnemonic seed phrase. ``` $ brainseed seed -Entropy prompt: hello world -Confirm: hello world +> Entropy prompt: hello world +> Confirm: hello world cliff burden nut payment negative soccer one mad pulse balcony force inside ``` @@ -22,8 +22,8 @@ Directly generate a watch-only output descriptor, to import into a wallet like S ``` $ brainseed watch -Entropy prompt: hello world -Confirm: hello world +> Entropy prompt: hello world +> Confirm: hello world wpkh([b343958c/84'/1'/0']tpubDCCj6osNwAnnSqViJQjqHD9xkJ6UXKT73ZB36W5gNapyCmirdibyzHeRsAYK5z9V5fi4ZdGAGA2jbXxPD1qS3Yht2tU3shPuatfUUWvKeCc/0/*)#cj35ttn3 ``` ## Signing a transaction @@ -32,12 +32,26 @@ This can be used to sign files as well, if you don't want to use a seed phrase i ``` $ brainseed sign input.psbt output.psbt -Entropy prompt: hello world -Confirm: hello world +> Entropy prompt: hello world +> Confirm: hello world ``` After importing a watch-only wallet into something like Sparrow, generate a transaction and save it as a raw file. Then sign it with this command, and load it back into Sparrow. +## Nostr keys + +Nostr's NIP-06 specifies the derivation path for generaing key pair's from Bitcoin seeds. By default, the derivation path is `m/44'/1237'/0'/0/0`. You can now generate key pairs like this: + +``` +$ brainseed nostr +> Entropy prompt: hello world +> Confirm: hello world +Private: 69cae0b7638b0eee28044b555dbe9063a3ded632bbfc3d3e3f2778467617d1e6 +Public: b3a19b7ef2749552db88fd43300db72484c4520a0ee84ae5881df871f1d84369 +``` + +You can pass `--nip19` to use the bech32 encoding from NIP-19 (better known as the `npub/nsec` encoding). Additionally, you can specify additional indexes on the derivation path by using `-i `. For instance, `-i 1` will use the derivation path `m/44'/1237'/0'/0/1`. This might be useful if you wish to rotate keys. + ## Frequently Asked Questions ### This is horrible. Why would you do such a thing? diff --git a/src/cli.rs b/src/cli.rs index 8b0ac6f..87c85cb 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -3,8 +3,12 @@ use std::{io::Write, path::PathBuf}; use anyhow::Context; use bdk::{ bitcoin::{ + bech32::{encode, ToBase32, Variant}, consensus::{deserialize, serialize}, + hashes::hex::ToHex, psbt::PartiallySignedTransaction, + secp256k1::Secp256k1, + util::bip32::{DerivationPath, ExtendedPrivKey}, Network, }, database::MemoryDatabase, @@ -42,6 +46,7 @@ impl Cli { Action::Seed => self.write_output(seed.to_string().as_bytes()), Action::Sign { input, output } => self.sign(input, output, seed), Action::Watch => self.show_descriptor(seed), + Action::Nostr { nip19, index } => self.nostr(seed, *nip19, *index), } } @@ -122,6 +127,31 @@ impl Cli { Network::Bitcoin } } + + fn nostr(&self, seed: Mnemonic, nip19: bool, index: u32) -> Result<(), anyhow::Error> { + let ctx = Secp256k1::new(); + let dv: DerivationPath = format!("m/44'/1237'/0'/0/{index}") + .parse() + .context("Invalid derivation path for nostr")?; + let seed = seed.to_seed(""); + let master = ExtendedPrivKey::new_master(Network::Bitcoin, &seed)?; + let xpriv = master.derive_priv(&ctx, &dv)?; + let privkey = xpriv.to_priv(); + let pubkey = privkey.public_key(&ctx); + + let (pubkey, privkey) = match nip19 { + true => ( + encode("nsec", privkey.to_bytes().to_base32(), Variant::Bech32)?, + encode("npub", pubkey.to_bytes().to_base32(), Variant::Bech32)?, + ), + false => (pubkey.to_bytes()[1..].to_hex(), privkey.to_bytes().to_hex()), + }; + + println!("Private: {privkey}"); + println!("Public: {pubkey}"); + + Ok(()) + } } #[derive(clap::Subcommand, Clone)] @@ -134,4 +164,16 @@ pub enum Action { /// Show wallet descriptor that is useful for importing as a watch-only wallet. Watch, + + /// Generate a Nostr private/public keypair (optionally in NIP-19 encoding). + Nostr { + /// Serialize the keys according to NIP-19 (npub/nsec format). + #[clap(long)] + nip19: bool, + + /// Child index for the derivation path m/44'/1237'/0'/0. + /// Use this to create additional keys for your seed. + #[clap(short, long, default_value_t = 0)] + index: u32, + }, }