Skip to content

Commit

Permalink
Nostr keys fix (#298)
Browse files Browse the repository at this point in the history
* Add NIP-06 test.

* Separate out keys tests. Add taproot test to sanity-check wallet-generated addresses and descriptors.

* Fix Nostr NIP-06 key derivation.
  • Loading branch information
cryptoquick authored Jul 25, 2023
1 parent 4e2d831 commit 2c7f48e
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 24 deletions.
40 changes: 18 additions & 22 deletions src/bitcoin/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ use bdk::{
miniscript::{descriptor::DescriptorKeyParseError, Tap},
};
use bip39::{Language, Mnemonic};
use bitcoin::KeyPair;
use bitcoin_hashes::{sha256, Hash};
use miniscript_crate::DescriptorPublicKey;
use nostr_sdk::prelude::{FromSkStr, ToBech32};
use thiserror::Error;
use zeroize::Zeroize;

use crate::{
constants::{BTC_PATH, NETWORK, NOSTR_PATH},
constants::{BTC_PATH, NETWORK},
structs::{DecryptedWalletData, PrivateWalletData, PublicWalletData, SecretString},
};

Expand All @@ -31,6 +32,9 @@ pub enum BitcoinKeysError {
/// Unexpected key variant in nostr_keypair
#[error("Unexpected key variant in nostr_keypair")]
UnexpectedKeyVariantInNostrKeypair,
/// secp256k1 error
#[error(transparent)]
Secp256k1Error(#[from] bitcoin::secp256k1::Error),
/// BIP-32 error
#[error(transparent)]
Bip32Error(#[from] bitcoin::util::bip32::Error),
Expand Down Expand Up @@ -105,27 +109,19 @@ fn watcher_xpub(
}
}

fn nostr_keypair(
xprv: &ExtendedPrivKey,
path: &str,
change: u32,
) -> Result<(String, String), BitcoinKeysError> {
// For NIP-06 Nostr signing and Carbonado encryption key derivation
fn nostr_keypair(xprv: &ExtendedPrivKey) -> Result<(String, String), BitcoinKeysError> {
pub const NOSTR_PATH: &str = "m/44'/1237'/0'/0/0";
let deriv_descriptor = DerivationPath::from_str(NOSTR_PATH)?;
let secp = Secp256k1::new();
let xprv = get_descriptor(xprv, path, change)?;

if let DescriptorSecretKey::XPrv(desc_xkey) = xprv {
let first_keypair = desc_xkey
.xkey
.ckd_priv(&secp, ChildNumber::from_normal_idx(0)?)?
.to_keypair(&secp);

Ok((
hex::encode(first_keypair.secret_bytes()),
hex::encode(first_keypair.x_only_public_key().0.serialize()),
))
} else {
Err(BitcoinKeysError::UnexpectedKeyVariantInNostrKeypair)
}
let nostr_sk = xprv.derive_priv(&secp, &deriv_descriptor)?;
let keypair =
KeyPair::from_seckey_slice(&secp, nostr_sk.private_key.secret_bytes().as_slice())?;

Ok((
hex::encode(nostr_sk.private_key.secret_bytes()),
hex::encode(keypair.x_only_public_key().0.serialize()),
))
}

pub async fn new_mnemonic(
Expand Down Expand Up @@ -175,7 +171,7 @@ pub async fn get_mnemonic(
let rgb_udas_descriptor_xpub = xpub_desc(&xprv, &btc_path, 21)?;
let watcher_xpub = watcher_xpub(&xprv, &btc_path, 0)?;

let (nostr_prv, nostr_pub) = nostr_keypair(&xprv, NOSTR_PATH, 0)?;
let (nostr_prv, nostr_pub) = nostr_keypair(&xprv)?;
let nostr_keys = nostr_sdk::Keys::from_sk_str(&nostr_prv)?;
let nostr_nsec = nostr_keys.secret_key()?.to_bech32()?;
let nostr_npub = nostr_keys.public_key().to_bech32()?;
Expand Down
2 changes: 0 additions & 2 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ pub static BTC_PATH: Lazy<RwLock<String>> = Lazy::new(|| {
BTC_TESTNET_PATH.to_owned()
})
});
// For NIP-06 Nostr signing and Carbonado encryption key derivation
pub const NOSTR_PATH: &str = "m/44h/1237h/0h";

// Magic number for versioning descriptors
pub const DIBA_DESCRIPTOR_VERSION: u8 = 0;
Expand Down
75 changes: 75 additions & 0 deletions tests/keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#![cfg(not(target_arch = "wasm32"))]

use anyhow::Result;
use bitmask_core::{
bitcoin::{get_wallet_data, save_mnemonic},
constants::switch_network,
structs::SecretString,
util::init_logging,
};

#[tokio::test]
pub async fn taproot() -> Result<()> {
init_logging("nostr_tests=debug");

const MNEMONIC: &str =
"empty faculty salute fortune select asthma attract question violin movie smile erupt half step lion deposit render stumble double mobile fossil height usual topple";

switch_network("bitcoin").await?;

let decrypted_wallet = save_mnemonic(
&SecretString(MNEMONIC.to_owned()),
&SecretString("".to_owned()),
)
.await?;

let wallet_data = get_wallet_data(
&SecretString(decrypted_wallet.public.btc_descriptor_xpub.to_owned()),
None,
)
.await?;

// Compare to those generated by Sparrow Wallet
assert_eq!(
decrypted_wallet.public.btc_descriptor_xpub,
"tr([496f1ccc/86'/0'/0']xpub6CBkARCPxmbRjaxzHxC38e9sKUVtMTRFqBYUFdXAHFBpeQzJz6mYSaQ1qSvCrNzYUNuvpD9FS6fmK9YowdCxaiCUSpjzNm5hvV2JxEodZ1q/0/*)",
"correct taproot xpub descriptor is derived from mnemonic"
);

assert_eq!(
wallet_data.address, "bc1pljwytlvv9n8ug5e7cxrjrfmhudd2w7r0nmdpt7j0387mc0zzpveq6jeqs6",
"correct first address is derived"
);

Ok(())
}

#[tokio::test]
pub async fn nip06() -> Result<()> {
init_logging("nostr_tests=debug");

// Using this tool:
// https://nip06.jaonoct.us

const MNEMONIC: &str =
"garment castle exhaust confirm wrong timber earth invest output comfort actress slot";

let wallet_data = save_mnemonic(
&SecretString(MNEMONIC.to_owned()),
&SecretString("".to_owned()),
)
.await?;

assert_eq!(
wallet_data.private.nostr_nsec,
"nsec1t9s9xyu55mezxwkf98uws2m8h6smjvehgngme0346rwm456g3kfs8pt0qw",
"correct nsec is generated"
);
assert_eq!(
wallet_data.public.nostr_npub,
"npub1v5zwxyd3dtmrvgnamxxlfxj92t52hwkg09dmwjhmkjujkq8kdzms77547c",
"correct npub is generated"
);

Ok(())
}

0 comments on commit 2c7f48e

Please sign in to comment.