mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-05 05:06:14 +01:00
feat: r and secret from seed
This commit is contained in:
@@ -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"] }
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
134
crates/cashu/src/nuts/nut13.rs
Normal file
134
crates/cashu/src/nuts/nut13.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user