feat: r and secret from seed

This commit is contained in:
thesimplekid
2024-03-04 22:36:16 +00:00
parent a891613bb3
commit a0a3a80377
7 changed files with 162 additions and 85 deletions

View File

@@ -15,20 +15,20 @@ description = "Cashu rust wallet and mint library"
default = ["mint", "wallet", "all-nuts"]
mint = []
wallet = []
all-nuts = ["nut07", "nut08", "nut09", "nut10", "nut11"]
all-nuts = ["nut07", "nut08", "nut09", "nut10", "nut11", "nut13"]
nut07 = []
nut08 = []
nut09 = []
nut10 = []
nut11 = ["nut10"]
nut13 = ["dep:bip39", "dep:bip32", "nut09"]
[dependencies]
base64 = "0.21.0"
bitcoin = { version = "0.30.0", features=["serde", "rand"] }
# TODO: Should be optional
bip39 = "2.0.0"
bip32 = "0.5.1"
bip39 = { version = "2.0.0", optional = true }
bip32 = { version = "0.5.1", optional = true }
hex = "0.4.3"
k256 = { version = "0.13.1", features=["arithmetic", "serde", "schnorr"] }
lightning-invoice = { version = "0.25.0", features=["serde"] }

View File

@@ -15,6 +15,8 @@ pub mod nut09;
pub mod nut10;
#[cfg(feature = "nut11")]
pub mod nut11;
#[cfg(feature = "nut13")]
pub mod nut13;
#[cfg(feature = "wallet")]
pub use nut00::wallet::{PreMint, PreMintSecrets, Token};

View File

@@ -116,7 +116,6 @@ pub mod wallet {
use base64::engine::{general_purpose, GeneralPurpose};
use base64::{alphabet, Engine as _};
use bip39::Mnemonic;
use serde::{Deserialize, Serialize};
use url::Url;
@@ -156,7 +155,7 @@ pub mod wallet {
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
pub struct PreMintSecrets {
secrets: Vec<PreMint>,
pub secrets: Vec<PreMint>,
}
impl PreMintSecrets {
@@ -229,41 +228,6 @@ pub mod wallet {
Ok(PreMintSecrets { secrets: output })
}
/// Generate blinded messages from predetermined secrets and blindings
/// factor
/// TODO: Put behind feature
pub fn from_seed(
keyset_id: Id,
counter: u64,
mnemonic: &Mnemonic,
amount: Amount,
) -> Result<Self, wallet::Error> {
let mut pre_mint_secrets = PreMintSecrets::default();
let mut counter = counter;
for amount in amount.split() {
let secret = Secret::from_seed(mnemonic, keyset_id, counter);
let blinding_factor = SecretKey::from_seed(mnemonic, keyset_id, counter);
let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor.into()))?;
let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
let pre_mint = PreMint {
blinded_message,
secret: secret.clone(),
r: r.into(),
amount: Amount::ZERO,
};
pre_mint_secrets.secrets.push(pre_mint);
counter += 1;
}
Ok(pre_mint_secrets)
}
#[cfg(feature = "nut11")]
pub fn with_p2pk_conditions(
keyset_id: Id,

View File

@@ -4,14 +4,12 @@
use std::collections::{BTreeMap, HashMap};
use std::str::FromStr;
use bip32::{DerivationPath, XPrv};
use bip39::Mnemonic;
use k256::elliptic_curve::generic_array::GenericArray;
#[cfg(feature = "nut11")]
use k256::schnorr::{SigningKey, VerifyingKey};
use serde::{Deserialize, Serialize};
use super::{Id, KeySet};
use super::KeySet;
use crate::error::Error;
use crate::Amount;
@@ -99,7 +97,7 @@ impl std::fmt::Display for PublicKey {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SecretKey(#[serde(with = "crate::serde_utils::serde_secret_key")] k256::SecretKey);
pub struct SecretKey(#[serde(with = "crate::serde_utils::serde_secret_key")] pub k256::SecretKey);
impl From<SecretKey> for k256::SecretKey {
fn from(value: SecretKey) -> k256::SecretKey {
@@ -136,22 +134,6 @@ impl SecretKey {
self.0.public_key().into()
}
// TODO: put behind feature
pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Self {
let path = DerivationPath::from_str(&format!(
"m/129372'/0'/{}'/{}'/1",
u64::from(keyset_id),
counter
))
.unwrap();
let signing_key = XPrv::derive_from_path(mnemonic.to_seed(""), &path).unwrap();
let private_key = signing_key.private_key();
Self(private_key.into())
}
pub fn random() -> Self {
let mut rng = rand::thread_rng();
SecretKey(k256::SecretKey::random(&mut rng))

View File

@@ -47,12 +47,16 @@ impl Id {
const STRLEN: usize = 14;
}
impl From<Id> for u64 {
fn from(value: Id) -> Self {
value
.id
.iter()
.fold(0, |acc, &byte| (acc << 8) | u64::from(byte))
impl TryFrom<Id> for u64 {
type Error = Error;
fn try_from(value: Id) -> Result<Self, Self::Error> {
let hex_bytes: [u8; 8] = hex::decode(value.to_string())?
.try_into()
.map_err(|_| Error::Length)?;
let int = u64::from_be_bytes(hex_bytes);
Ok(int % (2_u64.pow(31) - 1))
}
}
@@ -406,4 +410,12 @@ mod test {
let _keyset_response: KeysetResponse = serde_json::from_str(h).unwrap();
}
#[test]
fn test_to_int() {
let id = Id::from_str("009a1f293253e41e").unwrap();
let id_int = u64::try_from(id).unwrap();
assert_eq!(864559728, id_int)
}
}

View File

@@ -0,0 +1,134 @@
use std::str::FromStr;
use bip32::{DerivationPath, XPrv};
use bip39::Mnemonic;
use super::{Id, SecretKey};
use crate::error::Error;
use crate::secret::Secret;
impl Secret {
pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Result<Self, Error> {
let path = DerivationPath::from_str(&format!(
"m/129372'/0'/{}'/{}'/0",
u64::try_from(keyset_id).unwrap(),
counter
))
.unwrap();
let xpriv = XPrv::derive_from_path(mnemonic.to_seed(""), &path).unwrap();
Ok(Self(hex::encode(xpriv.private_key().to_bytes())))
}
}
impl SecretKey {
pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Result<Self, Error> {
let path = DerivationPath::from_str(&format!(
"m/129372'/0'/{}'/{}'/1",
u64::try_from(keyset_id).unwrap(),
counter
))
.unwrap();
let signing_key = XPrv::derive_from_path(mnemonic.to_seed(""), &path).unwrap();
let private_key = signing_key.private_key();
Ok(Self(private_key.into()))
}
}
#[cfg(feature = "wallet")]
mod wallet {
use bip39::Mnemonic;
use crate::dhke::blind_message;
use crate::error::wallet;
use crate::nuts::{BlindedMessage, Id, PreMint, PreMintSecrets, SecretKey};
use crate::secret::Secret;
use crate::Amount;
impl PreMintSecrets {
/// Generate blinded messages from predetermined secrets and blindings
/// factor
pub fn from_seed(
keyset_id: Id,
counter: u64,
mnemonic: &Mnemonic,
amount: Amount,
) -> Result<Self, wallet::Error> {
let mut pre_mint_secrets = PreMintSecrets::default();
let mut counter = counter;
for amount in amount.split() {
let secret = Secret::from_seed(mnemonic, keyset_id, counter)?;
let blinding_factor = SecretKey::from_seed(mnemonic, keyset_id, counter)?;
let (blinded, r) = blind_message(&secret.to_bytes(), Some(blinding_factor.into()))?;
let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
let pre_mint = PreMint {
blinded_message,
secret: secret.clone(),
r: r.into(),
amount: Amount::ZERO,
};
pre_mint_secrets.secrets.push(pre_mint);
counter += 1;
}
Ok(pre_mint_secrets)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_secret_from_seed() {
let seed =
"half depart obvious quality work element tank gorilla view sugar picture humble";
let mnemonic = Mnemonic::from_str(seed).unwrap();
let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
let test_secrets = [
"485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae",
"8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270",
"bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8",
"59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf",
"576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0",
];
for (i, test_secret) in test_secrets.iter().enumerate() {
let secret = Secret::from_seed(&mnemonic, keyset_id, i.try_into().unwrap()).unwrap();
assert_eq!(secret, Secret::from_str(test_secret).unwrap())
}
}
#[test]
fn test_r_from_seed() {
let seed =
"half depart obvious quality work element tank gorilla view sugar picture humble";
let mnemonic = Mnemonic::from_str(seed).unwrap();
let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
let test_rs = [
"ad00d431add9c673e843d4c2bf9a778a5f402b985b8da2d5550bf39cda41d679",
"967d5232515e10b81ff226ecf5a9e2e2aff92d66ebc3edf0987eb56357fd6248",
"b20f47bb6ae083659f3aa986bfa0435c55c6d93f687d51a01f26862d9b9a4899",
"fb5fca398eb0b1deb955a2988b5ac77d32956155f1c002a373535211a2dfdc29",
"5f09bfbfe27c439a597719321e061e2e40aad4a36768bb2bcc3de547c9644bf9",
];
for (i, test_r) in test_rs.iter().enumerate() {
let r = SecretKey::from_seed(&mnemonic, keyset_id, i.try_into().unwrap()).unwrap();
assert_eq!(r, SecretKey::from_hex(test_r).unwrap())
}
}
}

View File

@@ -2,17 +2,13 @@
use std::str::FromStr;
use bip32::{DerivationPath, XPrv};
use bip39::Mnemonic;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::nuts::Id;
/// The secret data that allows spending ecash
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Secret(String);
pub struct Secret(pub String);
#[derive(Debug, Error)]
pub enum Error {
@@ -45,19 +41,6 @@ impl Secret {
Self(secret)
}
pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Self {
let path = DerivationPath::from_str(&format!(
"m/129372'/0'/{}'/{}'/0",
u64::from(keyset_id),
counter
))
.unwrap();
let xpriv = XPrv::derive_from_path(mnemonic.to_seed(""), &path).unwrap();
Self(hex::encode(xpriv.private_key().to_bytes()))
}
pub fn to_bytes(&self) -> Vec<u8> {
self.0.clone().into_bytes()
}