diff --git a/crates/cashu/src/nuts/nut02.rs b/crates/cashu/src/nuts/nut02.rs index df152ffa5..da571179e 100644 --- a/crates/cashu/src/nuts/nut02.rs +++ b/crates/cashu/src/nuts/nut02.rs @@ -162,27 +162,35 @@ impl Id { /// /// This function will not panic under normal circumstances as the hash output /// is always valid hex and the correct length. - pub fn v2_from_data(map: &Keys, unit: &CurrencyUnit, expiry: Option) -> Self { + pub fn v2_from_data( + map: &Keys, + unit: &CurrencyUnit, + input_fee_ppk: u64, + expiry: Option, + ) -> Self { let mut keys: Vec<(&Amount, &super::PublicKey)> = map.iter().collect(); keys.sort_by_key(|(amt, _v)| *amt); - let mut pubkeys_concat: Vec = keys + let keys_string = keys .iter() - .map(|(_, pubkey)| pubkey.to_bytes()) - .collect::>() - .concat(); + .map(|(amt, pubkey)| format!("{}:{}", amt, hex::encode(pubkey.to_bytes()))) + .collect::>() + .join(","); + + let mut data = keys_string; + data.push_str(&format!("|unit:{}", unit)); - // Add the unit - pubkeys_concat.extend(b"unit:"); - pubkeys_concat.extend(unit.to_string().to_lowercase().as_bytes()); + if input_fee_ppk > 0 { + data.push_str(&format!("|input_fee_ppk:{}", input_fee_ppk)); + } - // Add the expiration if let Some(expiry) = expiry { - pubkeys_concat.extend(b"final_expiry:"); - pubkeys_concat.extend(expiry.to_string().as_bytes()); + if expiry > 0 { + data.push_str(&format!("|final_expiry:{}", expiry)); + } } - let hash = Sha256::hash(&pubkeys_concat); + let hash = Sha256::hash(data.as_bytes()); let hex_of_hash = hex::encode(hash.to_byte_array()); Self { @@ -472,14 +480,11 @@ pub struct KeySet { /// Keyset state - indicates whether the mint will sign new outputs with this keyset #[serde(skip_serializing_if = "Option::is_none")] pub active: Option, - /// Input fee in parts per thousand (ppk) per input spent from this keyset - #[serde( - deserialize_with = "deserialize_input_fee_ppk", - default = "default_input_fee_ppk" - )] - pub input_fee_ppk: u64, /// Keyset [`Keys`] pub keys: Keys, + /// Input Fee PPK + #[serde(default)] + pub input_fee_ppk: u64, /// Expiry #[serde(skip_serializing_if = "Option::is_none")] pub final_expiry: Option, @@ -490,7 +495,12 @@ impl KeySet { pub fn verify_id(&self) -> Result<(), Error> { let keys_id = match self.id.version { KeySetVersion::Version00 => Id::v1_from_keys(&self.keys), - KeySetVersion::Version01 => Id::v2_from_data(&self.keys, &self.unit, self.final_expiry), + KeySetVersion::Version01 => Id::v2_from_data( + &self.keys, + &self.unit, + self.input_fee_ppk, + self.final_expiry, + ), }; ensure_cdk!( @@ -572,6 +582,9 @@ pub struct MintKeySet { pub unit: CurrencyUnit, /// Keyset [`MintKeys`] pub keys: MintKeys, + /// Input Fee PPK + #[serde(default)] + pub input_fee_ppk: u64, #[serde(skip_serializing_if = "Option::is_none")] /// Expiry [`Option`] pub final_expiry: Option, @@ -590,6 +603,7 @@ impl MintKeySet { xpriv: Xpriv, unit: CurrencyUnit, amounts: &[u64], + input_fee_ppk: u64, final_expiry: Option, version: KeySetVersion, ) -> Self { @@ -615,12 +629,15 @@ impl MintKeySet { let keys = MintKeys::new(map); let id = match version { KeySetVersion::Version00 => Id::v1_from_keys(&keys.clone().into()), - KeySetVersion::Version01 => Id::v2_from_data(&keys.clone().into(), &unit, final_expiry), + KeySetVersion::Version01 => { + Id::v2_from_data(&keys.clone().into(), &unit, input_fee_ppk, final_expiry) + } }; Self { id, unit, keys, + input_fee_ppk, final_expiry, } } @@ -631,12 +648,14 @@ impl MintKeySet { /// /// This function will panic if the RNG fails or if key derivation fails, /// which should not happen under normal circumstances. + #[allow(clippy::too_many_arguments)] pub fn generate_from_seed( secp: &Secp256k1, seed: &[u8], amounts: &[u64], currency_unit: CurrencyUnit, derivation_path: DerivationPath, + input_fee_ppk: u64, final_expiry: Option, version: KeySetVersion, ) -> Self { @@ -648,6 +667,7 @@ impl MintKeySet { .expect("RNG busted"), currency_unit, amounts, + input_fee_ppk, final_expiry, version, ) @@ -659,12 +679,14 @@ impl MintKeySet { /// /// This function will panic if the RNG fails or if key derivation fails, /// which should not happen under normal circumstances. + #[allow(clippy::too_many_arguments)] pub fn generate_from_xpriv( secp: &Secp256k1, xpriv: Xpriv, amounts: &[u64], currency_unit: CurrencyUnit, derivation_path: DerivationPath, + input_fee_ppk: u64, final_expiry: Option, version: KeySetVersion, ) -> Self { @@ -675,6 +697,7 @@ impl MintKeySet { .expect("RNG busted"), currency_unit, amounts, + input_fee_ppk, final_expiry, version, ) @@ -687,7 +710,12 @@ impl From for Id { let keys: Keys = keyset.keys.into(); match keyset.id.version { KeySetVersion::Version00 => Id::v1_from_keys(&keys), - KeySetVersion::Version01 => Id::v2_from_data(&keys, &keyset.unit, keyset.final_expiry), + KeySetVersion::Version01 => Id::v2_from_data( + &keys, + &keyset.unit, + keyset.input_fee_ppk, + keyset.final_expiry, + ), } } } @@ -814,24 +842,25 @@ mod test { fn test_v2_deserialization_and_id_generation() { let unit: CurrencyUnit = CurrencyUnit::from_str("sat").unwrap(); let expiry: u64 = 2059210353; // +10 years from now + let input_fee_ppk = 100; let keys: Keys = serde_json::from_str(SHORT_KEYSET).unwrap(); let id_from_str = - Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035") + Id::from_str("015ba18a8adcd02e715a58358eb618da4a4b3791151a4bee5e968bb88406ccf76a") .unwrap(); - let id = Id::v2_from_data(&keys, &unit, Some(expiry)); + let id = Id::v2_from_data(&keys, &unit, input_fee_ppk, Some(expiry)); assert_eq!(id, id_from_str); let keys: Keys = serde_json::from_str(KEYSET).unwrap(); let id_from_str = - Id::from_str("0125bc634e270ad7e937af5b957f8396bb627d73f6e1fd2ffe4294c26b57daf9e0") + Id::from_str("01ab6aa4ff30390da34986d84be5274b48ad7a74265d791095bfc39f4098d9764f") .unwrap(); - let id = Id::v2_from_data(&keys, &unit, Some(expiry)); + let id = Id::v2_from_data(&keys, &unit, 0, Some(expiry)); assert_eq!(id, id_from_str); - let id = Id::v2_from_data(&keys, &unit, None); + let id = Id::v2_from_data(&keys, &unit, 0, None); let id_from_str = - Id::from_str("016d72f27c8d22808ad66d1959b3dab83af17e2510db7ffd57d2365d9eec3ced75") + Id::from_str("012fbb01a4e200c76df911eeba3b8fe1831202914b24664f4bccbd25852a6708f8") .unwrap(); assert_eq!(id, id_from_str); } diff --git a/crates/cdk-common/src/database/wallet/test/mod.rs b/crates/cdk-common/src/database/wallet/test/mod.rs index 7b75a7150..2250b2ac8 100644 --- a/crates/cdk-common/src/database/wallet/test/mod.rs +++ b/crates/cdk-common/src/database/wallet/test/mod.rs @@ -286,8 +286,8 @@ where id: keyset_id, unit: CurrencyUnit::Sat, active: None, - input_fee_ppk: 0, keys: keys.clone(), + input_fee_ppk: 0, final_expiry: None, }; @@ -312,8 +312,8 @@ where id: keyset_id, unit: CurrencyUnit::Sat, active: None, - input_fee_ppk: 0, keys: keys.clone(), + input_fee_ppk: 0, final_expiry: None, }; @@ -338,8 +338,8 @@ where id: keyset_id, unit: CurrencyUnit::Sat, active: None, - input_fee_ppk: 0, keys, + input_fee_ppk: 0, final_expiry: None, }; diff --git a/crates/cdk-signatory/src/common.rs b/crates/cdk-signatory/src/common.rs index bae0bc045..9337633a5 100644 --- a/crates/cdk-signatory/src/common.rs +++ b/crates/cdk-signatory/src/common.rs @@ -58,7 +58,7 @@ pub async fn init_keysets( if let Some((input_fee_ppk, max_order)) = supported_units.get(&unit) { if !keysets.is_empty() - && &highest_index_keyset.input_fee_ppk == input_fee_ppk + && highest_index_keyset.input_fee_ppk == *input_fee_ppk && highest_index_keyset.amounts.len() == (*max_order as usize) { tracing::debug!("Current highest index keyset matches expect fee and max order. Setting active"); @@ -69,6 +69,7 @@ pub async fn init_keysets( &highest_index_keyset.amounts, highest_index_keyset.unit.clone(), highest_index_keyset.derivation_path.clone(), + highest_index_keyset.input_fee_ppk, highest_index_keyset.final_expiry, cdk_common::nut02::KeySetVersion::Version00, ); @@ -139,6 +140,7 @@ pub fn create_new_keyset( .expect("RNG busted"), unit, amounts, + input_fee_ppk, final_expiry, // TODO: change this to Version01 to generate keysets v2 cdk_common::nut02::KeySetVersion::Version00, diff --git a/crates/cdk-signatory/src/db_signatory.rs b/crates/cdk-signatory/src/db_signatory.rs index 4b80f9c67..364c88eee 100644 --- a/crates/cdk-signatory/src/db_signatory.rs +++ b/crates/cdk-signatory/src/db_signatory.rs @@ -144,6 +144,7 @@ impl DbSignatory { &keyset_info.amounts, keyset_info.unit.clone(), keyset_info.derivation_path.clone(), + keyset_info.input_fee_ppk, keyset_info.final_expiry, keyset_info.id.get_version(), ) @@ -286,6 +287,7 @@ mod test { &[1, 2], CurrencyUnit::Sat, derivation_path_from_unit(CurrencyUnit::Sat, 0).unwrap(), + 0, None, cdk_common::nut02::KeySetVersion::Version00, ); @@ -332,6 +334,7 @@ mod test { &[1, 2], CurrencyUnit::Sat, derivation_path_from_unit(CurrencyUnit::Sat, 0).unwrap(), + 0, None, cdk_common::nut02::KeySetVersion::Version00, ); diff --git a/crates/cdk-signatory/src/proto/convert.rs b/crates/cdk-signatory/src/proto/convert.rs index 6999bcda2..2616a0d20 100644 --- a/crates/cdk-signatory/src/proto/convert.rs +++ b/crates/cdk-signatory/src/proto/convert.rs @@ -317,7 +317,6 @@ impl TryInto for KeySet { .try_into() .map_err(|_| cdk_common::Error::Custom("Invalid unit encoding".to_owned()))?, active: Some(self.active), - input_fee_ppk: self.input_fee_ppk, keys: cdk_common::Keys::new( self.keys .ok_or(cdk_common::error::Error::Custom(INTERNAL_ERROR.to_owned()))? @@ -326,6 +325,7 @@ impl TryInto for KeySet { .map(|(k, v)| cdk_common::PublicKey::from_slice(&v).map(|pk| (k.into(), pk))) .collect::, _>>()?, ), + input_fee_ppk: self.input_fee_ppk, final_expiry: self.final_expiry, }) } diff --git a/crates/cdk-signatory/src/signatory.rs b/crates/cdk-signatory/src/signatory.rs index 8f0986290..761c71ccd 100644 --- a/crates/cdk-signatory/src/signatory.rs +++ b/crates/cdk-signatory/src/signatory.rs @@ -73,7 +73,7 @@ pub struct SignatoryKeySet { pub keys: Keys, /// Amounts supported by the keyset pub amounts: Vec, - /// Information about the fee per public key + /// Input fee for the keyset (parts per thousand) pub input_fee_ppk: u64, /// Final expiry of the keyset (unix timestamp in the future) pub final_expiry: Option, @@ -91,8 +91,8 @@ impl From for KeySet { id: val.id, unit: val.unit, active: Some(val.active), - input_fee_ppk: val.input_fee_ppk, keys: val.keys, + input_fee_ppk: val.input_fee_ppk, final_expiry: val.final_expiry, } } diff --git a/crates/cdk-sql-common/src/mint/keys.rs b/crates/cdk-sql-common/src/mint/keys.rs index 0419941a7..6beddd1a7 100644 --- a/crates/cdk-sql-common/src/mint/keys.rs +++ b/crates/cdk-sql-common/src/mint/keys.rs @@ -45,7 +45,7 @@ pub(crate) fn sql_row_to_keyset_info(row: Vec) -> Result) -> Result { id: column_as_string!(id, Id::from_str, Id::from_bytes), unit: column_as_string!(unit, CurrencyUnit::from_str), active: matches!(active, Column::Integer(1)), - input_fee_ppk: column_as_nullable_number!(input_fee_ppk).unwrap_or_default(), + input_fee_ppk: column_as_nullable_number!(input_fee_ppk).unwrap_or(0), final_expiry: column_as_nullable_number!(final_expiry), }) } diff --git a/crates/cdk/src/mint/mod.rs b/crates/cdk/src/mint/mod.rs index 22544c07c..dcdf84b74 100644 --- a/crates/cdk/src/mint/mod.rs +++ b/crates/cdk/src/mint/mod.rs @@ -1175,7 +1175,7 @@ mod tests { let keys = mint.pubkeys(); - let expected_keys = r#"{"keysets":[{"id":"005f6e8c540c9e61","unit":"sat","active":true,"input_fee_ppk":0,"keys":{"1":"03e8aded7525acee36e3394e28f2dcbc012533ef2a2b085a55fc291d311afee3ef","1024":"0351a68a667c5fc21d66c187baecefa1d65529d06b7ae13112d432b6bca16b0e8c","1048576":"02b016346e5a322d371c6e6164b28b31b4d93a51572351ca2f26cdc12e916d9ac3","1073741824":"03f12e6a0903ed0db87485a296b1dca9d953a8a6919ff88732238fbc672d6bd125","128":"0351e33a076f415c2cadc945bc9bcb75bf4a774b28df8a0605dea1557e5897fed8","131072":"027cdf7be8b20a49ac7f2f065f7c53764c8926799877858c6b00b888a8aa6741a5","134217728":"0380658e5163fcf274e1ace6c696d1feef4c6068e0d03083d676dc5ef21804f22d","16":"031dbab0e4f7fb4fb0030f0e1a1dc80668eadd0b1046df3337bb13a7b9c982d392","16384":"028e9c6ce70f34cd29aad48656bf8345bb5ba2cb4f31fdd978686c37c93f0ab411","16777216":"02f2508e7df981c32f7b0008a273e2a1f19c23bb60a1561dba6b2a95ed1251eb90","2":"02628c0919e5cb8ce9aed1f81ce313f40e1ab0b33439d5be2abc69d9bb574902e0","2048":"0376166d8dcf97d8b0e9f11867ff0dafd439c90255b36a25be01e37e14741b9c6a","2097152":"028f25283e36a11df7713934a5287267381f8304aca3c1eb1b89fddce973ef1436","2147483648":"02cece3fb38a54581e0646db4b29242b6d78e49313dda46764094f9d128c1059c1","256":"0314b9f4300367c7e64fa85770da90839d2fc2f57d63660f08bb3ebbf90ed76840","262144":"026939b8f766c3ebaf26408e7e54fc833805563e2ef14c8ee4d0435808b005ec4c","268435456":"031526f03de945c638acccb879de837ac3fabff8590057cfb8552ebcf51215f3aa","32":"037241f7ad421374eb764a48e7769b5e2473582316844fda000d6eef28eea8ffb8","32768":"0253e34bab4eec93e235c33994e01bf851d5caca4559f07d37b5a5c266de7cf840","33554432":"0381883a1517f8c9979a84fcd5f18437b1a2b0020376ecdd2e515dc8d5a157a318","4":"039e7c7f274e1e8a90c61669e961c944944e6154c0794fccf8084af90252d2848f","4096":"03d40f47b4e5c4d72f2a977fab5c66b54d945b2836eb888049b1dd9334d1d70304","4194304":"03e5841d310819a49ec42dfb24839c61f68bbfc93ac68f6dad37fd5b2d204cc535","512":"030d95abc7e881d173f4207a3349f4ee442b9e51cc461602d3eb9665b9237e8db3","524288":"03772542057493a46eed6513b40386e766eedada16560ffde2f776b65794e9f004","536870912":"035eb3e7262e126c5503e1b402db05f87de6556773ae709cb7aa1c3b0986b87566","64":"02bc9767b4abf88becdac47a59e67ee9a9a80b9864ef57d16084575273ac63c0e7","65536":"02684ede207f9ace309b796b5259fc81ef0d4492b4fb5d66cf866b0b4a6f27bec9","67108864":"02aa648d39c9a725ef5927db15af6895f0d43c17f0a31faff4406314fc80180086","8":"02ca0e563ae941700aefcb16a7fb820afbb3258ae924ab520210cb730227a76ca3","8192":"03be18afaf35a29d7bcd5dfd1936d82c1c14691a63f8aa6ece258e16b0c043049b","8388608":"0307ebfeb87b7bca9baa03fad00499e5cc999fa5179ef0b7ad4f555568bcb946f5"}}]}"#; + let expected_keys = r#"{"keysets":[{"id":"005f6e8c540c9e61","unit":"sat","active":true,"keys":{"1":"03e8aded7525acee36e3394e28f2dcbc012533ef2a2b085a55fc291d311afee3ef","1024":"0351a68a667c5fc21d66c187baecefa1d65529d06b7ae13112d432b6bca16b0e8c","1048576":"02b016346e5a322d371c6e6164b28b31b4d93a51572351ca2f26cdc12e916d9ac3","1073741824":"03f12e6a0903ed0db87485a296b1dca9d953a8a6919ff88732238fbc672d6bd125","128":"0351e33a076f415c2cadc945bc9bcb75bf4a774b28df8a0605dea1557e5897fed8","131072":"027cdf7be8b20a49ac7f2f065f7c53764c8926799877858c6b00b888a8aa6741a5","134217728":"0380658e5163fcf274e1ace6c696d1feef4c6068e0d03083d676dc5ef21804f22d","16":"031dbab0e4f7fb4fb0030f0e1a1dc80668eadd0b1046df3337bb13a7b9c982d392","16384":"028e9c6ce70f34cd29aad48656bf8345bb5ba2cb4f31fdd978686c37c93f0ab411","16777216":"02f2508e7df981c32f7b0008a273e2a1f19c23bb60a1561dba6b2a95ed1251eb90","2":"02628c0919e5cb8ce9aed1f81ce313f40e1ab0b33439d5be2abc69d9bb574902e0","2048":"0376166d8dcf97d8b0e9f11867ff0dafd439c90255b36a25be01e37e14741b9c6a","2097152":"028f25283e36a11df7713934a5287267381f8304aca3c1eb1b89fddce973ef1436","2147483648":"02cece3fb38a54581e0646db4b29242b6d78e49313dda46764094f9d128c1059c1","256":"0314b9f4300367c7e64fa85770da90839d2fc2f57d63660f08bb3ebbf90ed76840","262144":"026939b8f766c3ebaf26408e7e54fc833805563e2ef14c8ee4d0435808b005ec4c","268435456":"031526f03de945c638acccb879de837ac3fabff8590057cfb8552ebcf51215f3aa","32":"037241f7ad421374eb764a48e7769b5e2473582316844fda000d6eef28eea8ffb8","32768":"0253e34bab4eec93e235c33994e01bf851d5caca4559f07d37b5a5c266de7cf840","33554432":"0381883a1517f8c9979a84fcd5f18437b1a2b0020376ecdd2e515dc8d5a157a318","4":"039e7c7f274e1e8a90c61669e961c944944e6154c0794fccf8084af90252d2848f","4096":"03d40f47b4e5c4d72f2a977fab5c66b54d945b2836eb888049b1dd9334d1d70304","4194304":"03e5841d310819a49ec42dfb24839c61f68bbfc93ac68f6dad37fd5b2d204cc535","512":"030d95abc7e881d173f4207a3349f4ee442b9e51cc461602d3eb9665b9237e8db3","524288":"03772542057493a46eed6513b40386e766eedada16560ffde2f776b65794e9f004","536870912":"035eb3e7262e126c5503e1b402db05f87de6556773ae709cb7aa1c3b0986b87566","64":"02bc9767b4abf88becdac47a59e67ee9a9a80b9864ef57d16084575273ac63c0e7","65536":"02684ede207f9ace309b796b5259fc81ef0d4492b4fb5d66cf866b0b4a6f27bec9","67108864":"02aa648d39c9a725ef5927db15af6895f0d43c17f0a31faff4406314fc80180086","8":"02ca0e563ae941700aefcb16a7fb820afbb3258ae924ab520210cb730227a76ca3","8192":"03be18afaf35a29d7bcd5dfd1936d82c1c14691a63f8aa6ece258e16b0c043049b","8388608":"0307ebfeb87b7bca9baa03fad00499e5cc999fa5179ef0b7ad4f555568bcb946f5"},"input_fee_ppk":0}]}"#; assert_eq!(expected_keys, serde_json::to_string(&keys.clone()).unwrap()); } diff --git a/crates/cdk/src/wallet/auth/auth_wallet.rs b/crates/cdk/src/wallet/auth/auth_wallet.rs index 7a50d5677..7d176b705 100644 --- a/crates/cdk/src/wallet/auth/auth_wallet.rs +++ b/crates/cdk/src/wallet/auth/auth_wallet.rs @@ -390,7 +390,7 @@ impl AuthWallet { keysets .get(&active_keyset_id) .map(|x| x.input_fee_ppk) - .unwrap_or_default(), + .unwrap_or(0), self.load_keyset_keys(active_keyset_id) .await? .iter()