Skip to content

generate nostr keys using NIP-06 derivation key #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand All @@ -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
Expand All @@ -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 <NUMBER>`. 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?
Expand Down
42 changes: 42 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
}
}

Expand Down Expand Up @@ -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)]
Expand All @@ -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,
},
}