mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-05 05:06:14 +01:00
feat: derive secret from path and seed
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user