feat: derive secret from path and seed

This commit is contained in:
thesimplekid
2023-12-31 16:24:22 +00:00
parent 969a8c4ccb
commit 9b5e9b2ea4
7 changed files with 120 additions and 7 deletions

View File

@@ -2,11 +2,12 @@
use std::collections::HashMap;
use std::str::FromStr;
use bip39::Mnemonic;
use cashu::dhke::{construct_proofs, unblind_message};
#[cfg(feature = "nut07")]
use cashu::nuts::nut00::mint;
use cashu::nuts::{
BlindedSignature, CurrencyUnit, Keys, PreMintSecrets, PreSwap, Proof, Proofs, SwapRequest,
BlindedSignature, CurrencyUnit, Id, Keys, PreMintSecrets, PreSwap, Proof, Proofs, SwapRequest,
Token,
};
#[cfg(feature = "nut07")]
@@ -24,7 +25,7 @@ use crate::utils::unix_time;
#[derive(Debug, Error)]
pub enum Error {
/// Insufficient Funds
#[error("Insuddicient Funds")]
#[error("Insufficient Funds")]
InsufficientFunds,
#[error("`{0}`")]
Cashu(#[from] cashu::error::wallet::Error),
@@ -41,8 +42,15 @@ pub enum Error {
Custom(String),
}
#[derive(Clone, Debug)]
pub struct BackupInfo {
mnemonic: Mnemonic,
counter: HashMap<Id, u64>,
}
#[derive(Clone, Debug)]
pub struct Wallet<C: Client> {
backup_info: Option<BackupInfo>,
pub client: C,
pub mint_url: UncheckedUrl,
pub mint_quotes: HashMap<String, MintQuote>,
@@ -57,9 +65,11 @@ impl<C: Client> Wallet<C> {
mint_url: UncheckedUrl,
mint_quotes: Vec<MintQuote>,
melt_quotes: Vec<MeltQuote>,
backup_info: Option<BackupInfo>,
mint_keys: Keys,
) -> Self {
Self {
backup_info,
client,
mint_url,
mint_keys,
@@ -158,7 +168,18 @@ impl<C: Client> Wallet<C> {
return Err(Error::QuoteUnknown);
};
let premint_secrets = PreMintSecrets::random((&self.mint_keys).into(), quote_info.amount)?;
let premint_secrets = match &self.backup_info {
Some(backup_info) => PreMintSecrets::from_seed(
Id::from(&self.mint_keys),
*backup_info
.counter
.get(&Id::from(&self.mint_keys))
.unwrap_or(&0),
&backup_info.mnemonic,
quote_info.amount,
)?,
None => PreMintSecrets::random((&self.mint_keys).into(), quote_info.amount)?,
};
let mint_res = self
.client

View File

@@ -23,6 +23,9 @@ nut08 = []
[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"
hex = "0.4.3"
k256 = { version = "0.13.1", features=["arithmetic", "serde", "schnorr"] }
lightning-invoice = { version = "0.25.0", features=["serde"] }

View File

@@ -30,6 +30,12 @@ impl Default for Amount {
}
}
impl Default for &Amount {
fn default() -> Self {
&Amount::ZERO
}
}
impl From<u64> for Amount {
fn from(value: u64) -> Self {
Self(value)

View File

@@ -92,6 +92,7 @@ pub mod wallet {
use base64::engine::{general_purpose, GeneralPurpose};
use base64::{alphabet, Engine as _};
use bip39::Mnemonic;
use serde::{Deserialize, Serialize};
use url::Url;
@@ -209,6 +210,45 @@ 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.as_bytes(), Some(blinding_factor.into()))?;
let blinded_message = BlindedMessage {
keyset_id,
amount,
b: 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)
}
pub fn iter(&self) -> impl Iterator<Item = &PreMint> {
self.secrets.iter()
}

View File

@@ -2,10 +2,13 @@
// https://github.com/cashubtc/nuts/blob/main/01.md
use std::collections::{BTreeMap, HashMap};
use std::str::FromStr;
use bip32::{DerivationPath, XPrv};
use bip39::Mnemonic;
use serde::{Deserialize, Serialize};
use super::KeySet;
use super::{Id, KeySet};
use crate::error::Error;
use crate::Amount;
@@ -79,6 +82,22 @@ impl SecretKey {
pub fn public_key(&self) -> PublicKey {
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())
}
}
/// Mint Keys [NUT-01]

View File

@@ -46,6 +46,15 @@ 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 std::fmt::Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!(

View File

@@ -1,12 +1,14 @@
// MIT License
// Copyright (c) 2023 Clark Moody
// https://github.com/clarkmoody/cashu-rs/blob/master/src/secret.rs
//! Secret
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)]
@@ -41,6 +43,19 @@ 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 as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}