diff --git a/Cargo.lock b/Cargo.lock index 1132e8974a..93c1ce2a33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1126,6 +1126,7 @@ dependencies = [ "strum_macros 0.27.2", "thiserror 2.0.18", "tracing", + "unicode-normalization", "url", "utoipa", "uuid", diff --git a/crates/cashu/Cargo.toml b/crates/cashu/Cargo.toml index cee5205919..650efb7539 100644 --- a/crates/cashu/Cargo.toml +++ b/crates/cashu/Cargo.toml @@ -38,6 +38,7 @@ strum_macros.workspace = true nostr-sdk = { workspace = true, optional = true } zeroize = "1" web-time.workspace = true +unicode-normalization = "0.1.25" [target.'cfg(target_arch = "wasm32")'.dependencies] uuid = { workspace = true, features = ["js"], optional = true } diff --git a/crates/cashu/src/nuts/nut00/mod.rs b/crates/cashu/src/nuts/nut00/mod.rs index 0c0bdfbbbe..01c25e43ab 100644 --- a/crates/cashu/src/nuts/nut00/mod.rs +++ b/crates/cashu/src/nuts/nut00/mod.rs @@ -9,8 +9,11 @@ use std::hash::{Hash, Hasher}; use std::str::FromStr; use std::string::FromUtf8Error; +#[cfg(feature = "mint")] +use bitcoin::hashes::Hash as BitcoinHash; use serde::{Deserialize, Deserializer, Serialize}; use thiserror::Error; +use unicode_normalization::UnicodeNormalization; use super::nut02::ShortKeysetId; #[cfg(feature = "wallet")] @@ -585,6 +588,10 @@ pub enum CurrencyUnit { #[cfg(feature = "mint")] impl CurrencyUnit { /// Derivation index mint will use for unit + #[deprecated( + since = "0.15.0", + note = "This function is outdated; use `hashed_derivation_index` instead." + )] pub fn derivation_index(&self) -> Option { match self { Self::Sat => Some(0), @@ -595,6 +602,28 @@ impl CurrencyUnit { _ => None, } } + + /// Construct a custom unit, normalizing to uppercase and trimming whitespace. + pub fn custom>(value: S) -> Self { + Self::Custom(normalize_custom_unit(value.as_ref()).to_uppercase()) + } + + /// Big endian encoded integer of the first 4 bytes of the sha256 hash of the unit string. + pub fn hashed_derivation_index(&self) -> u32 { + use bitcoin::hashes::sha256; + + // transform to uppercase + let unit_str = self.to_string().to_uppercase(); + + let bytes = ::hash(unit_str.as_bytes()); + // Take the first 4 bytes and convert to u32 (big endian) make sure the integer + u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) & !(1 << 31) + } +} + +fn normalize_custom_unit(value: &str) -> String { + let trimmed = value.trim_matches(|c: char| matches!(c, ' ' | '\t' | '\r' | '\n')); + trimmed.nfc().collect::() } impl FromStr for CurrencyUnit { @@ -607,13 +636,14 @@ impl FromStr for CurrencyUnit { "USD" => Ok(Self::Usd), "EUR" => Ok(Self::Eur), "AUTH" => Ok(Self::Auth), - _ => Ok(Self::Custom(value.to_string())), + _ => Ok(Self::Custom(normalize_custom_unit(value))), } } } impl fmt::Display for CurrencyUnit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // let binding = normalize_custom_unit(&self).clone(); let s = match self { CurrencyUnit::Sat => "SAT", CurrencyUnit::Msat => "MSAT", @@ -622,10 +652,16 @@ impl fmt::Display for CurrencyUnit { CurrencyUnit::Auth => "AUTH", CurrencyUnit::Custom(unit) => unit, }; + if let Some(width) = f.width() { - write!(f, "{:width$}", s.to_lowercase(), width = width) + write!( + f, + "{:width$}", + normalize_custom_unit(s).to_lowercase(), + width = width + ) } else { - write!(f, "{}", s.to_lowercase()) + write!(f, "{}", normalize_custom_unit(s).to_lowercase()) } } } @@ -635,7 +671,7 @@ impl Serialize for CurrencyUnit { where S: serde::Serializer, { - serializer.serialize_str(&self.to_string()) + serializer.serialize_str(&self.to_string().to_lowercase()) } } @@ -1142,6 +1178,31 @@ mod tests { ); } + #[test] + #[cfg(feature = "mint")] + fn four_bytes_hash_currency_unit() { + let unit = CurrencyUnit::Sat; + let index = unit.hashed_derivation_index(); + assert_eq!(index, 1967237907); + + let unit = CurrencyUnit::Msat; + let index = unit.hashed_derivation_index(); + assert_eq!(index, 142929756); + + let unit = CurrencyUnit::Eur; + let index = unit.hashed_derivation_index(); + assert_eq!(index, 1473545324); + + let unit = CurrencyUnit::Usd; + let index = unit.hashed_derivation_index(); + assert_eq!(index, 577560378); + + let unit = CurrencyUnit::Auth; + let index = unit.hashed_derivation_index(); + + assert_eq!(index, 1222349093) + } + #[test] fn test_payment_method_parsing() { // Test known methods (case insensitive) diff --git a/crates/cashu/src/nuts/nut02.rs b/crates/cashu/src/nuts/nut02.rs index da571179ed..6589fe39ea 100644 --- a/crates/cashu/src/nuts/nut02.rs +++ b/crates/cashu/src/nuts/nut02.rs @@ -80,6 +80,23 @@ impl KeySetVersion { _ => Err(Error::UnknownVersion), } } + + /// [`KeySetVersion`] from proto value + pub fn from_proto_i32(value: i32) -> Result { + match value { + 1 => Ok(Self::Version00), + 2 => Ok(Self::Version01), + _ => Err(Error::UnknownVersion), + } + } + + /// [`KeySetVersion`] to proto value + pub fn to_proto_i32(&self) -> i32 { + match self { + Self::Version00 => 1, + Self::Version01 => 2, + } + } } impl fmt::Display for KeySetVersion { @@ -920,6 +937,25 @@ mod test { assert_eq!(id_from_bytes, id); } + #[test] + fn test_keyset_version_proto_mapping() { + assert_eq!( + KeySetVersion::from_proto_i32(1).unwrap(), + KeySetVersion::Version00 + ); + assert_eq!( + KeySetVersion::from_proto_i32(2).unwrap(), + KeySetVersion::Version01 + ); + assert!(matches!( + KeySetVersion::from_proto_i32(0), + Err(Error::UnknownVersion) + )); + + assert_eq!(KeySetVersion::Version00.to_proto_i32(), 1); + assert_eq!(KeySetVersion::Version01.to_proto_i32(), 2); + } + #[test] fn test_deserialization_keys_response() { let keys = r#"{"keysets":[{"id":"I2yN+iRYfkzT","unit":"sat","keys":{"1":"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566","2":"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5","4":"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7","8":"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0","16":"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d","32":"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612","64":"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664","128":"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9","256":"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459","512":"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb","1024":"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc","2048":"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b","4096":"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2","8192":"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21","16384":"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50","32768":"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04","65536":"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d","131072":"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41","262144":"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328","524288":"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86","1048576":"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788","2097152":"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c","4194304":"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512","8388608":"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0","16777216":"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21","33554432":"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262","67108864":"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3","134217728":"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020","268435456":"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276","536870912":"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9","1073741824":"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee","2147483648":"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a","4294967296":"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5","8589934592":"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3","17179869184":"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9","34359738368":"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75","68719476736":"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754","137438953472":"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6","274877906944":"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a","549755813888":"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785","1099511627776":"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a","2199023255552":"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258","4398046511104":"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a","8796093022208":"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e","17592186044416":"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310","35184372088832":"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06","70368744177664":"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1","140737488355328":"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed","281474976710656":"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d","562949953421312":"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a","1125899906842624":"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9","2251799813685248":"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f","4503599627370496":"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73","9007199254740992":"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49","18014398509481984":"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6","36028797018963968":"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0","72057594037927936":"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd","144115188075855872":"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a","288230376151711744":"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc","576460752303423488":"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a","1152921504606846976":"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06","2305843009213693952":"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099","4611686018427387904":"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f","9223372036854775808":"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad"}},{"id":"00759e3f8b06b36f","unit":"sat","keys":{"1":"038a935c51c76c780ff9731cfbe9ab477f38346775809fa4c514340feabbec4b3a","2":"038288b12ebf2db3645e5d58835bd100398b6b19dfef338c698b55c05d0d41fb0a","4":"02fc8201cf4ea29abac0495d1304064f0e698762b8c0db145c1737b38a9d61c7e2","8":"02274243e03ca19f969acc7072812405b38adc672d1d753e65c63746b3f31cc6eb","16":"025f07cb2493351e7d5202f05eaf3934d5c9d17e73385e9de5bfab802f7d8caf92","32":"03afce0a897c858d7c88c1454d492eac43011e3396dda5b778ba1fcab381c748b1","64":"037b2178f42507f0c95e09d9b435a127df4b3e23ccd20af8075817d3abe90947ad","128":"02ebce8457b48407d4d248dba5a31b3eabf08a6285d09d08e40681c4adaf77bd40","256":"03c89713d27d6f8e328597b43dd87623efdcb251a484932f9e095ebfb6dbf4bdf2","512":"02df10f3ebba69916d03ab1754488770498f2e5466224d6df6d12811a13e46776c","1024":"02f5d9cba0502c21c6b39938a09dcb0390f124a2fd65e45dfeccd153cc1864273d","2048":"039de1dad91761b194e7674fb6ba212241aaf7f49dcb578a8fe093196ad1b20d1c","4096":"03cc694ba22e455f1c22b2cee4a40ecdd4f3bb4da0745411adb456158372d3efbb","8192":"029d66c24450fc315e046010df6870d61daa90c5c486c5ec4d7d3b99c5c2bce923","16384":"0387d063821010c7bd5cf79441870182f70cd432d13d3fc255e7b6ffd82c9d3c5a","32768":"021a94c6c03f7de8feb25b8a8b8d1f1c6f56af4bc533eb97c9e8b89c76b616ff11","65536":"038989c6ed91a7c577953115b465ee400a270a64e95eda8f7ee9d6bf30b8fe4908","131072":"03c3d3cd2523f004ee479a170b0ec5c74c060edb8356fc1b0a9ed8087cf6345172","262144":"02e54a7546f1a9194f30baa593a13d4e2949eb866593445d89675d7d394ef6320b","524288":"034e91037b3f1d3258d1e871dede80e98ef83e307c2e5ff589f38bd046f97546f8","1048576":"03306d42752a1adcfa394af2a690961ac9b80b1ac0f5fdc0890f66f8dc7d25ac6e","2097152":"03ec114332fe798c3e36675566c4748fda7d881000a01864ec48486512d7901e76","4194304":"02095e3e443d98ca3dfabcebc2f9154f3656b889783f7edb8290cfb01f497e63cf","8388608":"03c90f31525a4f9ab6562ec3edbf2bafc6662256ea6ce82ab19a45d2aee80b2f15","16777216":"03c0ae897a45724465c713c1379671ac5ff0a81c32e5f2dd27ea7e5530c7af484c","33554432":"034bcf793b70ba511e9c84cd07fc0c73c061e912bc02df4cac7871d048bad653b6","67108864":"021c6826c23a181d14962f43121943569a54f9d5af556eb839aee42d3f62debee6","134217728":"030e1bc651b6496922978d6cd3ed923cbf12b4332c496f841f506f5abf9d186d35","268435456":"03e3219e50cf389a75794f82ab4f880f5ffe9ca227b992c3e93cb4bb659d8e3353","536870912":"03879ad42536c410511ac6956b9da2d0da59ce7fbb6068bd9b25dd7cccddcc8096","1073741824":"03c4d3755a17904c0cfa7d7a21cc5b4e85fca8ac85369fcb12a6e2177525117dee","2147483648":"02e7a5d5cd3ea24f05f741dddad3dc8c5e24db60eb9bf9ad888b1c5dfbd792665e","4294967296":"03c783d24d8c9e51207eb3d6199bf48d6eb81a4b34103b422724be15501ff921bd","8589934592":"03200234495725455f4c4e6b6cb7b7936eb7cd1d1c9bb73d2ce032bae7d728b3ca","17179869184":"02eafa50ac67de2c206d1a67245b72ec20fac081c2a550294cc0a711246ed65a41","34359738368":"024c153c2a56de05860006aff9dc35ec9cafd7ac68708442a3a326c858b0c1a146","68719476736":"035a890c2d5c8bf259b98ac67d0d813b87778bcb0c0ea1ee9717ac804b0be3f563","137438953472":"025184ca832f08b105fdb471e2caf14025a1daa6f44ce90b4c7703878ccb6b26e8","274877906944":"039d19a41abdd49949c60672430018c63f27c5a28991f9fbb760499daccc63146c","549755813888":"03a138ac626dd3e6753459903aa128a13c052ed0058f2ead707c203bd4a7565237","1099511627776":"0298c8ef2eab728613103481167102efaf2d4b7a303cb94b9393da37a034a95c53","2199023255552":"02d88f8fc93cd2edf303fdebfecb70e59b5373cb8f746a1d075a9c86bc9382ac07","4398046511104":"02afd89ee23eee7d5fe6687fee898f64e9b01913ec71b5c596762b215e040c701f","8796093022208":"02196b461f3c804259e597c50e514920427aab4beaef0c666185fb2ff4399813db","17592186044416":"037b33746a6fd7a71d4cf17c85d13a64b98620614c0028d4995163f1b8484ee337","35184372088832":"036cce0a1878bbc63b3108c379ef4e6529fbf20ed675d80d91ca3ccc55fde4bdbd","70368744177664":"039c81dccb319ba70597cdf9db33b459164a1515c27366c8f667b01d988874e554","140737488355328":"036b2dd85a3c44c4458f0b246ce19a1524a191f1716834cfb452c6e1f946172c19","281474976710656":"022c84722c31a2b3d8cfd9b6a9e6199515fd97d6a9c390fc3d82f123bfc501ad04","562949953421312":"0355e2be85ee599b8fa7e6e68a9954573d032e89aa9e65c2e1231991664c200bf3","1125899906842624":"024b10818cd27f3eec6c9daf82b9dfa53928ab0711b711070bd39892ac10dee765","2251799813685248":"02a6d726432bb18c3145eba4fc0b587bf64f3be8617c0070dda33944474b3f8740","4503599627370496":"0248304be3cbaf31ec320bc636bb936c5984caf773df950fc44c6237ec09c557a1","9007199254740992":"03a3c0e9da7ece7d7b132c53662c0389bd87db801dff5ac9edd9f46699cb1dc065","18014398509481984":"03b6c4c874e2392072e17fbfd181afbd40d6766a8ca4cf932264ba98d98de1328c","36028797018963968":"0370dca4416ec6e30ff02f8e9db7804348b42e3f5c22099dfc896fa1b2ccbe7a69","72057594037927936":"0226250140aedb79de91cb4cc7350884bde229063f34ee0849081bb391a37c273e","144115188075855872":"02baef3a94d241aee9d6057c7a7ee7424f8a0bcb910daf6c49ddcabf70ffbc77d8","288230376151711744":"030f95a12369f1867ce0dbf2a6322c27d70c61b743064d76cfc81dd43f1a052ae6","576460752303423488":"021bc89118ab6eb1fbebe0fa6cc76da8236a7991163475a73a22d8efd016a45800","1152921504606846976":"03b0c1e658d7ca12830a0b590ea5a4d6db51084ae80b6d8abf27ad2d762209acd1","2305843009213693952":"0266926ce658a0bdae934071f22e09dbb6ecaff2a4dc4b1f8e23626570d993b48e","4611686018427387904":"03ac17f10f9bb745ebd8ee9cdca1b6981f5a356147d431196c21c6d4869402bde0","9223372036854775808":"037ab5b88c8ce34c4a3970be5c6f75b8a7a5493a12ef56a1c9ba9ff5f90de46fcc"}},{"id":"000f01df73ea149a","unit":"sat","keys":{"1":"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566","2":"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5","4":"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7","8":"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0","16":"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d","32":"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612","64":"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664","128":"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9","256":"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459","512":"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb","1024":"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc","2048":"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b","4096":"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2","8192":"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21","16384":"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50","32768":"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04","65536":"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d","131072":"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41","262144":"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328","524288":"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86","1048576":"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788","2097152":"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c","4194304":"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512","8388608":"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0","16777216":"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21","33554432":"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262","67108864":"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3","134217728":"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020","268435456":"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276","536870912":"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9","1073741824":"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee","2147483648":"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a","4294967296":"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5","8589934592":"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3","17179869184":"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9","34359738368":"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75","68719476736":"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754","137438953472":"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6","274877906944":"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a","549755813888":"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785","1099511627776":"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a","2199023255552":"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258","4398046511104":"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a","8796093022208":"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e","17592186044416":"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310","35184372088832":"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06","70368744177664":"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1","140737488355328":"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed","281474976710656":"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d","562949953421312":"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a","1125899906842624":"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9","2251799813685248":"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f","4503599627370496":"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73","9007199254740992":"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49","18014398509481984":"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6","36028797018963968":"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0","72057594037927936":"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd","144115188075855872":"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a","288230376151711744":"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc","576460752303423488":"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a","1152921504606846976":"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06","2305843009213693952":"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099","4611686018427387904":"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f","9223372036854775808":"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad"}},{"id":"yjzQhxghPdrr","unit":"sat","keys":{"1":"038a935c51c76c780ff9731cfbe9ab477f38346775809fa4c514340feabbec4b3a","2":"038288b12ebf2db3645e5d58835bd100398b6b19dfef338c698b55c05d0d41fb0a","4":"02fc8201cf4ea29abac0495d1304064f0e698762b8c0db145c1737b38a9d61c7e2","8":"02274243e03ca19f969acc7072812405b38adc672d1d753e65c63746b3f31cc6eb","16":"025f07cb2493351e7d5202f05eaf3934d5c9d17e73385e9de5bfab802f7d8caf92","32":"03afce0a897c858d7c88c1454d492eac43011e3396dda5b778ba1fcab381c748b1","64":"037b2178f42507f0c95e09d9b435a127df4b3e23ccd20af8075817d3abe90947ad","128":"02ebce8457b48407d4d248dba5a31b3eabf08a6285d09d08e40681c4adaf77bd40","256":"03c89713d27d6f8e328597b43dd87623efdcb251a484932f9e095ebfb6dbf4bdf2","512":"02df10f3ebba69916d03ab1754488770498f2e5466224d6df6d12811a13e46776c","1024":"02f5d9cba0502c21c6b39938a09dcb0390f124a2fd65e45dfeccd153cc1864273d","2048":"039de1dad91761b194e7674fb6ba212241aaf7f49dcb578a8fe093196ad1b20d1c","4096":"03cc694ba22e455f1c22b2cee4a40ecdd4f3bb4da0745411adb456158372d3efbb","8192":"029d66c24450fc315e046010df6870d61daa90c5c486c5ec4d7d3b99c5c2bce923","16384":"0387d063821010c7bd5cf79441870182f70cd432d13d3fc255e7b6ffd82c9d3c5a","32768":"021a94c6c03f7de8feb25b8a8b8d1f1c6f56af4bc533eb97c9e8b89c76b616ff11","65536":"038989c6ed91a7c577953115b465ee400a270a64e95eda8f7ee9d6bf30b8fe4908","131072":"03c3d3cd2523f004ee479a170b0ec5c74c060edb8356fc1b0a9ed8087cf6345172","262144":"02e54a7546f1a9194f30baa593a13d4e2949eb866593445d89675d7d394ef6320b","524288":"034e91037b3f1d3258d1e871dede80e98ef83e307c2e5ff589f38bd046f97546f8","1048576":"03306d42752a1adcfa394af2a690961ac9b80b1ac0f5fdc0890f66f8dc7d25ac6e","2097152":"03ec114332fe798c3e36675566c4748fda7d881000a01864ec48486512d7901e76","4194304":"02095e3e443d98ca3dfabcebc2f9154f3656b889783f7edb8290cfb01f497e63cf","8388608":"03c90f31525a4f9ab6562ec3edbf2bafc6662256ea6ce82ab19a45d2aee80b2f15","16777216":"03c0ae897a45724465c713c1379671ac5ff0a81c32e5f2dd27ea7e5530c7af484c","33554432":"034bcf793b70ba511e9c84cd07fc0c73c061e912bc02df4cac7871d048bad653b6","67108864":"021c6826c23a181d14962f43121943569a54f9d5af556eb839aee42d3f62debee6","134217728":"030e1bc651b6496922978d6cd3ed923cbf12b4332c496f841f506f5abf9d186d35","268435456":"03e3219e50cf389a75794f82ab4f880f5ffe9ca227b992c3e93cb4bb659d8e3353","536870912":"03879ad42536c410511ac6956b9da2d0da59ce7fbb6068bd9b25dd7cccddcc8096","1073741824":"03c4d3755a17904c0cfa7d7a21cc5b4e85fca8ac85369fcb12a6e2177525117dee","2147483648":"02e7a5d5cd3ea24f05f741dddad3dc8c5e24db60eb9bf9ad888b1c5dfbd792665e","4294967296":"03c783d24d8c9e51207eb3d6199bf48d6eb81a4b34103b422724be15501ff921bd","8589934592":"03200234495725455f4c4e6b6cb7b7936eb7cd1d1c9bb73d2ce032bae7d728b3ca","17179869184":"02eafa50ac67de2c206d1a67245b72ec20fac081c2a550294cc0a711246ed65a41","34359738368":"024c153c2a56de05860006aff9dc35ec9cafd7ac68708442a3a326c858b0c1a146","68719476736":"035a890c2d5c8bf259b98ac67d0d813b87778bcb0c0ea1ee9717ac804b0be3f563","137438953472":"025184ca832f08b105fdb471e2caf14025a1daa6f44ce90b4c7703878ccb6b26e8","274877906944":"039d19a41abdd49949c60672430018c63f27c5a28991f9fbb760499daccc63146c","549755813888":"03a138ac626dd3e6753459903aa128a13c052ed0058f2ead707c203bd4a7565237","1099511627776":"0298c8ef2eab728613103481167102efaf2d4b7a303cb94b9393da37a034a95c53","2199023255552":"02d88f8fc93cd2edf303fdebfecb70e59b5373cb8f746a1d075a9c86bc9382ac07","4398046511104":"02afd89ee23eee7d5fe6687fee898f64e9b01913ec71b5c596762b215e040c701f","8796093022208":"02196b461f3c804259e597c50e514920427aab4beaef0c666185fb2ff4399813db","17592186044416":"037b33746a6fd7a71d4cf17c85d13a64b98620614c0028d4995163f1b8484ee337","35184372088832":"036cce0a1878bbc63b3108c379ef4e6529fbf20ed675d80d91ca3ccc55fde4bdbd","70368744177664":"039c81dccb319ba70597cdf9db33b459164a1515c27366c8f667b01d988874e554","140737488355328":"036b2dd85a3c44c4458f0b246ce19a1524a191f1716834cfb452c6e1f946172c19","281474976710656":"022c84722c31a2b3d8cfd9b6a9e6199515fd97d6a9c390fc3d82f123bfc501ad04","562949953421312":"0355e2be85ee599b8fa7e6e68a9954573d032e89aa9e65c2e1231991664c200bf3","1125899906842624":"024b10818cd27f3eec6c9daf82b9dfa53928ab0711b711070bd39892ac10dee765","2251799813685248":"02a6d726432bb18c3145eba4fc0b587bf64f3be8617c0070dda33944474b3f8740","4503599627370496":"0248304be3cbaf31ec320bc636bb936c5984caf773df950fc44c6237ec09c557a1","9007199254740992":"03a3c0e9da7ece7d7b132c53662c0389bd87db801dff5ac9edd9f46699cb1dc065","18014398509481984":"03b6c4c874e2392072e17fbfd181afbd40d6766a8ca4cf932264ba98d98de1328c","36028797018963968":"0370dca4416ec6e30ff02f8e9db7804348b42e3f5c22099dfc896fa1b2ccbe7a69","72057594037927936":"0226250140aedb79de91cb4cc7350884bde229063f34ee0849081bb391a37c273e","144115188075855872":"02baef3a94d241aee9d6057c7a7ee7424f8a0bcb910daf6c49ddcabf70ffbc77d8","288230376151711744":"030f95a12369f1867ce0dbf2a6322c27d70c61b743064d76cfc81dd43f1a052ae6","576460752303423488":"021bc89118ab6eb1fbebe0fa6cc76da8236a7991163475a73a22d8efd016a45800","1152921504606846976":"03b0c1e658d7ca12830a0b590ea5a4d6db51084ae80b6d8abf27ad2d762209acd1","2305843009213693952":"0266926ce658a0bdae934071f22e09dbb6ecaff2a4dc4b1f8e23626570d993b48e","4611686018427387904":"03ac17f10f9bb745ebd8ee9cdca1b6981f5a356147d431196c21c6d4869402bde0","9223372036854775808":"037ab5b88c8ce34c4a3970be5c6f75b8a7a5493a12ef56a1c9ba9ff5f90de46fcc"}}]}"#; diff --git a/crates/cdk-common/src/error.rs b/crates/cdk-common/src/error.rs index 9165e3cf5a..f7c453143b 100644 --- a/crates/cdk-common/src/error.rs +++ b/crates/cdk-common/src/error.rs @@ -254,7 +254,9 @@ pub enum Error { /// Oidc config not set #[error("Oidc client not set")] OidcNotSet, - + /// Unit String collision + #[error("Unit string picked collided: `{0}`")] + UnitStringCollision(CurrencyUnit), // Wallet Errors /// P2PK spending conditions not met #[error("P2PK condition not met `{0}`")] diff --git a/crates/cdk-common/src/grpc.rs b/crates/cdk-common/src/grpc.rs index 1b794b01d0..2c5aad50dd 100644 --- a/crates/cdk-common/src/grpc.rs +++ b/crates/cdk-common/src/grpc.rs @@ -4,28 +4,31 @@ use tonic::{Request, Status}; /// Header name for protocol version pub const VERSION_HEADER: &str = "x-cdk-protocol-version"; +/// Header for version of the signatory protofile +pub const VERSION_SIGNATORY_HEADER: &str = "x-signatory-schema-version"; /// Creates a client-side interceptor that injects a specific protocol version into outgoing requests /// /// # Panics /// Panics if the version string is not a valid gRPC metadata ASCII value pub fn create_version_inject_interceptor( + header: &'static str, version: &'static str, ) -> impl Fn(Request<()>) -> Result, Status> + Clone { move |mut request: Request<()>| { - request.metadata_mut().insert( - VERSION_HEADER, - version.parse().expect("Invalid protocol version"), - ); + request + .metadata_mut() + .insert(header, version.parse().expect("Invalid protocol version")); Ok(request) } } /// Creates a server-side interceptor that validates a specific protocol version on incoming requests pub fn create_version_check_interceptor( + header: &'static str, expected_version: &'static str, ) -> impl Fn(Request<()>) -> Result, Status> + Clone { - move |request: Request<()>| match request.metadata().get(VERSION_HEADER) { + move |request: Request<()>| match request.metadata().get(header) { Some(version) => { let version = version .to_str() diff --git a/crates/cdk-common/src/lib.rs b/crates/cdk-common/src/lib.rs index f0bdf474cb..2b96283b8e 100644 --- a/crates/cdk-common/src/lib.rs +++ b/crates/cdk-common/src/lib.rs @@ -11,9 +11,6 @@ pub mod task; /// Protocol version for gRPC Mint RPC communication pub const MINT_RPC_PROTOCOL_VERSION: &str = "1.0.0"; -/// Protocol version for gRPC Signatory communication -pub const SIGNATORY_PROTOCOL_VERSION: &str = "1.0.0"; - /// Protocol version for gRPC Payment Processor communication pub const PAYMENT_PROCESSOR_PROTOCOL_VERSION: &str = "1.0.0"; diff --git a/crates/cdk-mint-rpc/src/proto/server.rs b/crates/cdk-mint-rpc/src/proto/server.rs index 2a6738d638..db58f4fd26 100644 --- a/crates/cdk-mint-rpc/src/proto/server.rs +++ b/crates/cdk-mint-rpc/src/proto/server.rs @@ -139,7 +139,10 @@ impl MintRPCServer { Server::builder().tls_config(tls_config)?.add_service( CdkMintServer::with_interceptor( self.clone(), - create_version_check_interceptor(cdk_common::MINT_RPC_PROTOCOL_VERSION), + create_version_check_interceptor( + cdk_common::grpc::VERSION_HEADER, + cdk_common::MINT_RPC_PROTOCOL_VERSION, + ), ), ) } @@ -147,7 +150,10 @@ impl MintRPCServer { tracing::warn!("No valid TLS configuration found, starting insecure server"); Server::builder().add_service(CdkMintServer::with_interceptor( self.clone(), - create_version_check_interceptor(cdk_common::MINT_RPC_PROTOCOL_VERSION), + create_version_check_interceptor( + cdk_common::grpc::VERSION_HEADER, + cdk_common::MINT_RPC_PROTOCOL_VERSION, + ), )) } }; diff --git a/crates/cdk-payment-processor/src/proto/server.rs b/crates/cdk-payment-processor/src/proto/server.rs index 81f242f838..1d409bb0f2 100644 --- a/crates/cdk-payment-processor/src/proto/server.rs +++ b/crates/cdk-payment-processor/src/proto/server.rs @@ -108,6 +108,7 @@ impl PaymentProcessorServer { CdkPaymentProcessorServer::with_interceptor( self.clone(), create_version_check_interceptor( + cdk_common::grpc::VERSION_HEADER, cdk_common::PAYMENT_PROCESSOR_PROTOCOL_VERSION, ), ), @@ -118,6 +119,7 @@ impl PaymentProcessorServer { Server::builder().add_service(CdkPaymentProcessorServer::with_interceptor( self.clone(), create_version_check_interceptor( + cdk_common::grpc::VERSION_HEADER, cdk_common::PAYMENT_PROCESSOR_PROTOCOL_VERSION, ), )) diff --git a/crates/cdk-signatory/src/common.rs b/crates/cdk-signatory/src/common.rs index e4e0ad2592..2044025807 100644 --- a/crates/cdk-signatory/src/common.rs +++ b/crates/cdk-signatory/src/common.rs @@ -1,15 +1,15 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::str::FromStr; use std::sync::Arc; use bitcoin::bip32::{ChildNumber, DerivationPath, Xpriv}; use bitcoin::secp256k1::{self, All, Secp256k1}; use cdk_common::common::IssuerVersion; -use cdk_common::database; use cdk_common::error::Error; use cdk_common::mint::MintKeySetInfo; use cdk_common::nuts::{CurrencyUnit, MintKeySet}; use cdk_common::util::unix_time; +use cdk_common::{database, nut02}; /// Initialize keysets pub async fn init_keysets( @@ -79,14 +79,8 @@ pub fn create_new_keyset( amounts: &[u64], input_fee_ppk: u64, final_expiry: Option, - use_keyset_v2: bool, + keyset_id_version: nut02::KeySetVersion, ) -> (MintKeySet, MintKeySetInfo) { - let version = if use_keyset_v2 { - cdk_common::nut02::KeySetVersion::Version01 - } else { - cdk_common::nut02::KeySetVersion::Version00 - }; - let keyset = MintKeySet::generate( secp, xpriv @@ -96,7 +90,7 @@ pub fn create_new_keyset( amounts, input_fee_ppk, final_expiry, - version, + keyset_id_version, ); let keyset_info = MintKeySetInfo { id: keyset.id, @@ -114,11 +108,38 @@ pub fn create_new_keyset( } pub fn derivation_path_from_unit(unit: CurrencyUnit, index: u32) -> Option { - let unit_index = unit.derivation_index()?; + let unit_index = unit.hashed_derivation_index(); Some(DerivationPath::from(vec![ - ChildNumber::from_hardened_idx(0).expect("0 is a valid index"), - ChildNumber::from_hardened_idx(unit_index).expect("0 is a valid index"), + ChildNumber::from_hardened_idx(129372).expect("129372 is a valid index"), + ChildNumber::from_hardened_idx(unit_index).expect("unit index should be valid"), ChildNumber::from_hardened_idx(index).expect("0 is a valid index"), ])) } + +/// take all the keyset units and if te new keyset is a new unit we check +pub fn check_unit_string_collision( + keysets: Vec, + new_keyset: &MintKeySetInfo, +) -> Result<(), Error> { + let mut unit_hash: HashSet = HashSet::new(); + + for key in keysets { + unit_hash.insert(key.unit); + } + + if unit_hash.contains(&new_keyset.unit) { + // the currency unit already exists so we don't have to check it + return Ok(()); + } + + let new_unit_int = new_keyset.unit.hashed_derivation_index(); + for unit in unit_hash.iter() { + let existing_unit_string = unit.hashed_derivation_index(); + if existing_unit_string == new_unit_int { + return Err(Error::UnitStringCollision(new_keyset.unit.clone())); + } + } + + Ok(()) +} diff --git a/crates/cdk-signatory/src/db_signatory.rs b/crates/cdk-signatory/src/db_signatory.rs index 5c49f09b0a..002f4f61ef 100644 --- a/crates/cdk-signatory/src/db_signatory.rs +++ b/crates/cdk-signatory/src/db_signatory.rs @@ -13,7 +13,9 @@ use cdk_common::{database, Error, PublicKey}; use tokio::sync::RwLock; use tracing::instrument; -use crate::common::{create_new_keyset, derivation_path_from_unit, init_keysets}; +use crate::common::{ + check_unit_string_collision, create_new_keyset, derivation_path_from_unit, init_keysets, +}; use crate::signatory::{RotateKeyArguments, Signatory, SignatoryKeySet, SignatoryKeysets}; /// In-memory Signatory @@ -47,7 +49,6 @@ impl DbSignatory { ) -> Result { let secp_ctx = Secp256k1::new(); let xpriv = Xpriv::new_master(bitcoin::Network::Bitcoin, seed).expect("RNG busted"); - init_keysets(xpriv, &secp_ctx, &localstore, &supported_units).await?; supported_units.entry(CurrencyUnit::Auth).or_insert((0, 1)); @@ -224,8 +225,12 @@ impl Signatory for DbSignatory { args.input_fee_ppk, // TODO: add and connect settings for this None, - args.use_keyset_v2, + args.keyset_id_type, ); + + let keysets = self.keysets().await?; + check_unit_string_collision(keysets.keysets, &info)?; + let id = info.id; let mut tx = self.localstore.begin_transaction().await?; tx.add_keyset_info(info.clone()).await?; @@ -244,22 +249,24 @@ mod test { use bitcoin::key::Secp256k1; use bitcoin::Network; + use cdk_common::util::hex; use cdk_common::{Amount, MintKeySet, PublicKey}; use super::*; #[test] fn mint_mod_generate_keyset_from_seed() { - let seed = "test_seed".as_bytes(); + let seed = hex::decode("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); let keyset = MintKeySet::generate_from_seed( &Secp256k1::new(), - seed, + &seed, &[1, 2], CurrencyUnit::Sat, derivation_path_from_unit(CurrencyUnit::Sat, 0).unwrap(), 0, None, - cdk_common::nut02::KeySetVersion::Version00, + cdk_common::nut02::KeySetVersion::Version01, ); assert_eq!(keyset.unit, CurrencyUnit::Sat); @@ -269,14 +276,14 @@ mod test { ( Amount::from(1), PublicKey::from_hex( - "0257aed43bf2c1cdbe3e7ae2db2b27a723c6746fc7415e09748f6847916c09176e", + "0380a4bb98d9bc5d5b11c7cf2b705dbc894b62ac99cf67e0ef1a3d47ea6dc54706", ) .unwrap(), ), ( Amount::from(2), PublicKey::from_hex( - "03ad95811e51adb6231613f9b54ba2ba31e4442c9db9d69f8df42c2b26fbfed26e", + "022fe5e50a15d721014b538ca6a3ff20ee049b195ba0b1705f64829da8779b6940", ) .unwrap(), ), @@ -295,9 +302,10 @@ mod test { #[test] fn mint_mod_generate_keyset_from_xpriv() { - let seed = "test_seed".as_bytes(); + let seed = hex::decode("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); let network = Network::Bitcoin; - let xpriv = Xpriv::new_master(network, seed).expect("Failed to create xpriv"); + let xpriv = Xpriv::new_master(network, &seed).expect("Failed to create xpriv"); let keyset = MintKeySet::generate_from_xpriv( &Secp256k1::new(), xpriv, @@ -316,14 +324,14 @@ mod test { ( Amount::from(1), PublicKey::from_hex( - "0257aed43bf2c1cdbe3e7ae2db2b27a723c6746fc7415e09748f6847916c09176e", + "0380a4bb98d9bc5d5b11c7cf2b705dbc894b62ac99cf67e0ef1a3d47ea6dc54706", ) .unwrap(), ), ( Amount::from(2), PublicKey::from_hex( - "03ad95811e51adb6231613f9b54ba2ba31e4442c9db9d69f8df42c2b26fbfed26e", + "022fe5e50a15d721014b538ca6a3ff20ee049b195ba0b1705f64829da8779b6940", ) .unwrap(), ), @@ -339,4 +347,402 @@ mod test { assert_eq!(amounts_and_pubkeys, expected_amounts_and_pubkeys); } + + #[test] + fn mint_make_btc_remote_signer_keyset() { + let seed = hex::decode("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + let network = Network::Bitcoin; + let xpriv = Xpriv::new_master(network, &seed).expect("Failed to create xpriv"); + let keyset = MintKeySet::generate_from_xpriv( + &Secp256k1::new(), + xpriv, + &[ + 1, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256, + 512, + 1024, + 2048, + 4096, + 8192, + 16384, + 32768, + 65536, + 131072, + 262144, + 524288, + 1_048_576, + 2_097_152, + 4_194_304, + 8_388_608, + 16_777_216, + 33_554_432, + 67_108_864, + 134_217_728, + 268_435_456, + 536_870_912, + 1_073_741_824, + 2_147_483_648, + 4_294_967_296, + 8_589_934_592, + 17_179_869_184, + 34_359_738_368, + 68_719_476_736, + 137_438_953_472, + 274_877_906_944, + 549_755_813_888, + 1_099_511_627_776, + 2_199_023_255_552, + 4_398_046_511_104, + 8_796_093_022_208, + 17_592_186_044_416, + 35_184_372_088_832, + 70_368_744_177_664, + 140_737_488_355_328, + 281_474_976_710_656, + 562_949_953_421_312, + 1_125_899_906_842_624, + 2_251_799_813_685_248, + 4_503_599_627_370_496, + 9_007_199_254_740_992, + 18_014_398_509_481_984, + 36_028_797_018_963_968, + 72_057_594_037_927_936, + 144_115_188_075_855_872, + 288_230_376_151_711_744, + 576_460_752_303_423_488, + 1_152_921_504_606_846_976, + 2_305_843_009_213_693_952, + 4_611_686_018_427_387_904, + 9_223_372_036_854_775_808, + ], + CurrencyUnit::Sat, + derivation_path_from_unit(CurrencyUnit::Sat, 1).unwrap(), + 0, + None, + cdk_common::nut02::KeySetVersion::Version00, + ); + + assert_eq!(keyset.unit, CurrencyUnit::Sat); + assert_eq!(keyset.keys.len(), 64); + + let expected_results: HashMap = [ + ( + 1, + "0233501d047ff4058007722d5d24e10a8ff5c723a677be411fff46a3cee9a92cc0", + ), + ( + 2, + "03a09803ce40118b8917fafa08409dbe6e8bb36d76c55f4c58400cd720abaf54cb", + ), + ( + 4, + "02dac058df2e8611098286ef87ee9698f555548784ab4b1a860c79338073ad8c49", + ), + ( + 8, + "025b66b937d65544981817aa9a053a762a7d72a7543c66a54370ea68aa53170a10", + ), + ( + 16, + "027cf2ad5fa02b99ea37b305048562828453d89dfa7defcda1c10f6746f25f7541", + ), + ( + 32, + "0336033cbbc044737bced1fd40b7f0cb0ce08a83aedaa882ed1ced875a1f517879", + ), + ( + 64, + "035be95ecaadbfe67b14f07205d13bbcab5da58bb595c57dfb9b61c5e3e7e4de0e", + ), + ( + 128, + "0232c757957a8f5a14e93a9bbe8852c273b985ad238ce9b4d5a16885d8a761462b", + ), + ( + 256, + "02cbd889df7d38e95dca2ee0e09bc22e3ae57e95975043854a5560a464f970ac1f", + ), + ( + 512, + "02c99a0b72ba8f01c5da765c534e75ae3e5f51e4931bfced18a91df4b9233b168f", + ), + ( + 1024, + "0320527abb6ae3dd6db9da5041ca941be679e953b446614843af7a4393e9ac96bc", + ), + ( + 2048, + "033f9276b0c5f73fbeb0130eab5705a8e878f4191fe251a18cbd918cda3c9e2d5e", + ), + ( + 4096, + "03cf69ed2939be4ac35308560d4423e1a0d96cacf9fe33267c7e6a047bf438e53e", + ), + ( + 8192, + "027c8bfff71352766c3870e9f5f577830bbb44eadfb757fdff9a8cd209c4b22d76", + ), + ( + 16384, + "02ea21bd310828b9e46746eba2ae985626b3a2efc2468db66ae480715dc6deec8a", + ), + ( + 32768, + "027ae7179192282d5b44ac55bff82c13e1ea916ae1edefa33ea64100be7408e015", + ), + ( + 65536, + "028f333c1beada3445cb62108e35d72199925a055c1e7c102c742e1761770f6c62", + ), + ( + 131072, + "03de95cae3614499a3df2d412e91aa09ddef8b8d49e8d652e3798419da86958139", + ), + ( + 262144, + "03c7817c19b4b107eb2ccf2f32b60f9c22a59a1d4a93e492ad01f1505097a654b7", + ), + ( + 524288, + "028aad03886b6ec6b9f628090e9c151a73f025aa949a9686dac1f0b32995a4e8df", + ), + ( + 1048576, + "034bf50a5916d9f112b8fbfe82a5ac914b5bec792b107cf25922c9866f002473e8", + ), + ( + 2097152, + "03d2894e1b1b7ab7497ff69e16d280b630f60ba34fe00edd7c748ae5ee73bc0d1a", + ), + ( + 4194304, + "0285ba0ee2960927de958610b13d63fc29019407eb32c477d9a2d016fda3062a37", + ), + ( + 8388608, + "03d7a4b4b1b8d6b9f2b5966e380a62f8efd53f79d1965e076a716d2fb75e9774a1", + ), + ( + 16777216, + "037a033e2f1df992523df83bcb9aa02cefdadd59882d7949f4500f5493d89fa2fd", + ), + ( + 33554432, + "03014de7af4809599cabc6d6b30e5121b4a88153eb38a7b66dd8e50e3166215ab0", + ), + ( + 67108864, + "0240162a1d2eb1841450de53a6244a625922b14006153d5219dad0fcf0c369c497", + ), + ( + 134217728, + "03f8c6f7b0ee71f66940a33c746c3bf8b1cba793a498dd2fdeb6857552415a4d5d", + ), + ( + 268435456, + "02dc9de15fa1332f5a2c8f85045ea127cbc3407fb8a844b453f38e1c9cdce9ef87", + ), + ( + 536870912, + "0291bdcb1719b5bf447b2885efc84061d1de30b9d1f583d25034059457a2fd739e", + ), + ( + 1073741824, + "02f8a96485e3fa791f57d7f4ef279dd3617b873efbdf673815c49dbf9ce7422b0d", + ), + ( + 2147483648, + "02ff8cf3e3de985bb2f286c98e335a175b2b53a0e0d7fa1f53d642c95a372329a2", + ), + ( + 4294967296, + "02d96196cc54e7506bfe9fdb4a0d691eed2948ecb9b8e81d28d27225287ad5debc", + ), + ( + 8589934592, + "03e64e5664f7ab843f41aaf4c0534d698b3318d140c23cbd2fcc33eece53400dac", + ), + ( + 17179869184, + "034c9a4bf7b4cb8fac6ace994624e5250ddac5ac84541b6c8bd12b71d22719bb2d", + ), + ( + 34359738368, + "0313027c2b106c7dcdee0d806c3343026260276c6793d4d1dfdf79aae30875be31", + ), + ( + 68719476736, + "03081adca96d42cb2ac4ac94e0ea2aac4d9412265ae55ed377e3c0357aa1157253", + ), + ( + 137438953472, + "02fdc4118761739425220ba87dee5ea9fdc1d581abfcb506fb5afabf76e172b798", + ), + ( + 274877906944, + "031dd7cd25f761c8f80828b487bab1cef730f68e8d6f2026b443cc7223862f6c73", + ), + ( + 549755813888, + "02da505eab15744a6fd3fa6b3257bced520d4d294ea94444528fd30d7f90948629", + ), + ( + 1099511627776, + "02bfc54369099958275376ab030f2a085532c8a00ae4d1bbfa5031c64b42d58a47", + ), + ( + 2199023255552, + "032241a5d4d1e988b8ae85f68a381df0e40065ae8c81b1c4f7ea31c87eab2c0d81", + ), + ( + 4398046511104, + "03a681e41990d350cdedd30840f26ad970b4015dd6e6b5c03f7cc99b384bee8762", + ), + ( + 8796093022208, + "033d5293a33cda29d65058d6d3a4b821472574e92414fa052c79f8bdc1cd72faba", + ), + ( + 17592186044416, + "033ddfec40622aaf62d672f43fd05ddb396afd7ad9f00daede45102c890d3a012b", + ), + ( + 35184372088832, + "02564bbdcbed18a8e2d79b2fdad6e5e8a9fe92e853ab23170934d84015cc4b96b0", + ), + ( + 70368744177664, + "02170950642b94d0ed232370d5dd3630b5eb7e73791447fb961b12d8139de975de", + ), + ( + 140737488355328, + "02b2add5a6eb5dc06f706e9dba190ba412c2c7ba240284b336b66ef38a39e51f1c", + ), + ( + 281474976710656, + "03e3e584a4bc1d0a6399f5b6b9355bd67a10ad9f46c8a4283de96854e47eb4357c", + ), + ( + 562949953421312, + "033821262e6a78f29dad81d3133845883a7632a47f51ab1d99a0eae4a5354eef45", + ), + ( + 1125899906842624, + "038db672a61c70dc66b504152ea39b607527f2f59e8ebfdf8d955c38e914661534", + ), + ( + 2251799813685248, + "03dafb9683eac036a422266ddc85b675bf13aeafe0658cad2ec1555c28f4049b28", + ), + ( + 4503599627370496, + "0351733345d4bb491e27bdb221e382d00f2248f2ee7f04dc6f3faab2692fbd296c", + ), + ( + 9007199254740992, + "03f930c1e6c154ca169370adbec7691fd9c11245867a37ae086f7547f5c9e8386f", + ), + ( + 18014398509481984, + "02d700dc30d3cd6be292bddbd5f74c09df784862c785cd763ad6c829be59c21bed", + ), + ( + 36028797018963968, + "03444b9c312900fffbd478e390aa6fdf9d3ffe230239141ecadf0bcee25e379512", + ), + ( + 72057594037927936, + "03af7acedfcfcaf83cfdb7d171ef64723286bd6e0ab90f3629e627e77955917776", + ), + ( + 144115188075855872, + "02e35aef647a881e8c318879fb81b6261df73e385dfbc5ff3fc0ab40f13f5ed560", + ), + ( + 288230376151711744, + "024558ed8e986901e05839c34d17c261c8d93b8cabb5dee83ab805bb5028e5e463", + ), + ( + 576460752303423488, + "024f60a89ba055e009d84a90a13a7860a909fb486a8ffb4315c2f59aff6fbfd929", + ), + ( + 1152921504606846976, + "0311b2a5b91dfaebab4fb125338fd38dab72ec5671e6db5f468cb1477970ea3876", + ), + ( + 2305843009213693952, + "02aeaa116d930767b5143cac922511c0e093beee5a2850f67490f5a5bb44a8af76", + ), + ( + 4611686018427387904, + "02bf7003847bc8e7ad35ea5c8975e3fdde8d1c43ef540d250cf2dc75792c733647", + ), + ( + 9223372036854775808, + "0376b06a13092fbb679f6e7a90ce877c37d5a20714a65567177a91a0479b3e86a9", + ), + ] + .into_iter() + .collect(); + + assert_eq!(keyset.id.to_string(), "00b5a0580f75cc2f".to_string()); + + for key in expected_results { + let amount = Amount::from(key.0); + let pubkey = keyset + .keys + .get(&amount) + .unwrap() + .public_key + .clone() + .to_hex(); + + assert_eq!(pubkey, key.1.to_string()); + } + } + + #[test] + fn mint_make_auth_remote_signer_keyset() { + let seed = hex::decode("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + let network = Network::Bitcoin; + let xpriv = Xpriv::new_master(network, &seed).expect("Failed to create xpriv"); + let keyset = MintKeySet::generate_from_xpriv( + &Secp256k1::new(), + xpriv, + &[1], + CurrencyUnit::Auth, + derivation_path_from_unit(CurrencyUnit::Auth, 1).unwrap(), + 0, + None, + cdk_common::nut02::KeySetVersion::Version00, + ); + + assert_eq!(keyset.unit, CurrencyUnit::Auth); + assert_eq!(keyset.keys.len(), 1); + + assert_eq!(keyset.id.to_string(), "00e1cf6079abb988".to_string()); + + let amount = Amount::from(1); + let pubkey = keyset + .keys + .get(&amount) + .unwrap() + .public_key + .clone() + .to_hex(); + assert_eq!( + pubkey, + "025b6c1ca8bb741a6f2321c953266df7bf3f3f2c3be8c54c0a6e41bb00976046a4".to_string() + ); + } } diff --git a/crates/cdk-signatory/src/proto/client.rs b/crates/cdk-signatory/src/proto/client.rs index 94eecf7da2..68bf4e91ba 100644 --- a/crates/cdk-signatory/src/proto/client.rs +++ b/crates/cdk-signatory/src/proto/client.rs @@ -1,11 +1,12 @@ use std::path::Path; use cdk_common::error::Error; -use cdk_common::grpc::VERSION_HEADER; +use cdk_common::grpc::VERSION_SIGNATORY_HEADER; use cdk_common::{BlindSignature, BlindedMessage, Proof}; use tonic::metadata::MetadataValue; use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity}; +use crate::proto; use crate::proto::signatory_client::SignatoryClient; use crate::signatory::{RotateKeyArguments, Signatory, SignatoryKeySet, SignatoryKeysets}; @@ -38,9 +39,11 @@ pub enum ClientError { /// Helper function to add version header to a request fn with_version_header(mut request: tonic::Request) -> tonic::Request { + let version_str = (proto::Constants::SchemaVersion as u8).to_string(); + let version: &'static str = Box::leak(version_str.into_boxed_str()); request.metadata_mut().insert( - VERSION_HEADER, - MetadataValue::from_static(cdk_common::SIGNATORY_PROTOCOL_VERSION), + VERSION_SIGNATORY_HEADER, + MetadataValue::from_static(version), ); request } @@ -117,8 +120,6 @@ impl Signatory for SignatoryRpcClient { .into_iter() .map(|blind_message| blind_message.into()) .collect(), - operation: super::Operation::Unspecified.into(), - correlation_id: "".to_owned(), }; self.client diff --git a/crates/cdk-signatory/src/proto/convert.rs b/crates/cdk-signatory/src/proto/convert.rs index 2c7fc45cbf..4557deed16 100644 --- a/crates/cdk-signatory/src/proto/convert.rs +++ b/crates/cdk-signatory/src/proto/convert.rs @@ -3,6 +3,7 @@ use std::collections::BTreeMap; use std::str::FromStr; use cdk_common::common::IssuerVersion; +use cdk_common::nut02::KeySetVersion; use cdk_common::secret::Secret; use cdk_common::util::hex; use cdk_common::{Amount, Id, PublicKey}; @@ -65,6 +66,7 @@ impl TryInto for KeySet { amounts: keys.keys().map(|x| x.to_u64()).collect::>(), keys: cdk_common::Keys::new(keys), final_expiry: self.final_expiry, + version: self.version, issuer_version: self .issuer_version .map(|v| IssuerVersion::from_str(&v)) @@ -95,7 +97,7 @@ impl From for KeySet { } } -impl From for Error { +impl From for super::Error { fn from(err: cdk_common::Error) -> Self { let code = match err { cdk_common::Error::AmountError(_) => ErrorCode::AmountOutsideLimit, @@ -106,15 +108,15 @@ impl From for Error { _ => ErrorCode::Unspecified, }; - Error { + super::Error { code: code.into(), detail: err.to_string(), } } } -impl From for cdk_common::Error { - fn from(val: Error) -> Self { +impl From for cdk_common::Error { + fn from(val: super::Error) -> Self { match val.code.try_into().expect("valid code") { ErrorCode::AmountOutsideLimit => { cdk_common::Error::AmountError(cdk_common::amount::Error::AmountOverflow) @@ -162,8 +164,6 @@ impl From> for Proofs { fn from(value: Vec) -> Self { Proofs { proof: value.into_iter().map(|x| x.into()).collect(), - operation: Operation::Unspecified.into(), - correlation_id: "".to_owned(), } } } @@ -345,7 +345,8 @@ impl From for RotationRequest { unit: Some(value.unit.into()), amounts: value.amounts, input_fee_ppk: value.input_fee_ppk, - use_keyset_v2: value.use_keyset_v2, + keyset_id_type: value.keyset_id_type.to_proto_i32(), + final_expiry: value.final_expiry, } } } @@ -361,7 +362,9 @@ impl TryInto for RotationRequest { .try_into()?, amounts: self.amounts, input_fee_ppk: self.input_fee_ppk, - use_keyset_v2: self.use_keyset_v2, + final_expiry: self.final_expiry, + keyset_id_type: KeySetVersion::from_proto_i32(self.keyset_id_type) + .map_err(|err| Status::invalid_argument(err.to_string()))?, }) } } diff --git a/crates/cdk-signatory/src/proto/server.rs b/crates/cdk-signatory/src/proto/server.rs index 321c15a829..d84255d2b7 100644 --- a/crates/cdk-signatory/src/proto/server.rs +++ b/crates/cdk-signatory/src/proto/server.rs @@ -267,10 +267,12 @@ where } }; + let version_str = (proto::Constants::SchemaVersion as u8).to_string(); + let version: &'static str = Box::leak(version_str.into_boxed_str()); server .add_service(signatory_server::SignatoryServer::with_interceptor( CdkSignatoryServer::new(signatory_loader), - create_version_check_interceptor(cdk_common::SIGNATORY_PROTOCOL_VERSION), + create_version_check_interceptor(cdk_common::grpc::VERSION_SIGNATORY_HEADER, version), )) .serve(addr) .await?; @@ -289,10 +291,12 @@ where IO: AsyncRead + AsyncWrite + Connected + Unpin + Send + 'static, IE: Into>, { + let version_str = (proto::Constants::SchemaVersion as u8).to_string(); + let version: &'static str = Box::leak(version_str.into_boxed_str()); Server::builder() .add_service(signatory_server::SignatoryServer::with_interceptor( CdkSignatoryServer::new(signatory_loader), - create_version_check_interceptor(cdk_common::SIGNATORY_PROTOCOL_VERSION), + create_version_check_interceptor(cdk_common::grpc::VERSION_SIGNATORY_HEADER, version), )) .serve_with_incoming(incoming) .await?; diff --git a/crates/cdk-signatory/src/proto/signatory.proto b/crates/cdk-signatory/src/proto/signatory.proto index 82da8e2dff..9509d52dea 100644 --- a/crates/cdk-signatory/src/proto/signatory.proto +++ b/crates/cdk-signatory/src/proto/signatory.proto @@ -11,11 +11,10 @@ service Signatory { rpc RotateKeyset(RotationRequest) returns (KeyRotationResponse); } -enum Operation { - OPERATION_UNSPECIFIED = 0; - OPERATION_MINT = 1; - OPERATION_MELT = 2; - OPERATION_SWAP = 3; +enum Constants { + CONSTANTS_UNSPECIFIED = 0; + // this constant should be sent on the header of the grpc code for verification + CONSTANTS_SCHEMA_VERSION = 1; } message BlindSignResponse { @@ -25,8 +24,6 @@ message BlindSignResponse { message BlindedMessages { repeated BlindedMessage blinded_messages = 1; - Operation operation = 2; - string correlation_id = 3; } // Represents a blinded message @@ -63,7 +60,7 @@ message KeySet { uint64 input_fee_ppk = 4; Keys keys = 5; optional uint64 final_expiry = 6; - uint64 version = 7; + uint32 version = 7; optional string issuer_version = 8; } @@ -71,11 +68,17 @@ message Keys { map keys = 1; } +enum KeysetVersion { + KEYSET_VERSION_UNSPECIFIED = 0; + KEYSET_VERSION_V1 = 1; + KEYSET_VERSION_V2 = 2; +} message RotationRequest { CurrencyUnit unit = 1; uint64 input_fee_ppk = 2; repeated uint64 amounts = 3; - bool use_keyset_v2 = 4; + optional uint64 final_expiry = 4; + KeysetVersion keyset_id_type = 5; } enum CurrencyUnitType { @@ -96,8 +99,6 @@ message CurrencyUnit { message Proofs { repeated Proof proof = 1; - Operation operation = 3; - string correlation_id = 4; } message Proof { @@ -107,7 +108,6 @@ message Proof { bytes c = 4; } - message BlindSignatures { repeated BlindSignature blind_signatures = 1; } diff --git a/crates/cdk-signatory/src/signatory.rs b/crates/cdk-signatory/src/signatory.rs index 1217d573a2..9e5ac36320 100644 --- a/crates/cdk-signatory/src/signatory.rs +++ b/crates/cdk-signatory/src/signatory.rs @@ -9,6 +9,7 @@ use cdk_common::common::IssuerVersion; use cdk_common::error::Error; use cdk_common::mint::MintKeySetInfo; +use cdk_common::nuts::nut02::KeySetVersion; use cdk_common::{ BlindSignature, BlindedMessage, CurrencyUnit, Id, KeySet, Keys, MintKeySet, Proof, PublicKey, }; @@ -48,7 +49,9 @@ pub struct RotateKeyArguments { /// Input fee pub input_fee_ppk: u64, /// KeySet Version - pub use_keyset_v2: bool, + pub keyset_id_type: KeySetVersion, + /// FinalExpiry + pub final_expiry: Option, } #[derive(Debug, Clone)] @@ -82,6 +85,8 @@ pub struct SignatoryKeySet { pub final_expiry: Option, /// Issuer Version pub issuer_version: Option, + /// Version is the derivation_path_index + pub version: u32, } impl From<&SignatoryKeySet> for KeySet { @@ -135,6 +140,7 @@ impl From<&(MintKeySetInfo, MintKeySet)> for SignatoryKeySet { input_fee_ppk: info.input_fee_ppk, amounts: info.amounts.clone(), keys: key.keys.clone().into(), + version: info.derivation_path_index.unwrap_or(1), final_expiry: key.final_expiry, issuer_version: info.issuer_version.clone(), } diff --git a/crates/cdk/src/mint/builder.rs b/crates/cdk/src/mint/builder.rs index f04ccf1e04..7957ffbefd 100644 --- a/crates/cdk/src/mint/builder.rs +++ b/crates/cdk/src/mint/builder.rs @@ -452,7 +452,12 @@ impl MintBuilder { unit: unit.clone(), amounts, input_fee_ppk: *fee, - use_keyset_v2: self.use_keyset_v2.unwrap_or(true), + keyset_id_type: if self.use_keyset_v2.unwrap_or(true) { + cdk_common::nut02::KeySetVersion::Version01 + } else { + cdk_common::nut02::KeySetVersion::Version00 + }, + final_expiry: None, }) .await?; } diff --git a/crates/cdk/src/mint/keysets/mod.rs b/crates/cdk/src/mint/keysets/mod.rs index 30cc5f6836..11f2006020 100644 --- a/crates/cdk/src/mint/keysets/mod.rs +++ b/crates/cdk/src/mint/keysets/mod.rs @@ -84,7 +84,12 @@ impl Mint { unit, amounts, input_fee_ppk, - use_keyset_v2, + keyset_id_type: if use_keyset_v2 { + cdk_common::nut02::KeySetVersion::Version01 + } else { + cdk_common::nut02::KeySetVersion::Version00 + }, + final_expiry: None, }) .await?; diff --git a/crates/cdk/src/mint/mod.rs b/crates/cdk/src/mint/mod.rs index b9926b7b42..3e70c6e374 100644 --- a/crates/cdk/src/mint/mod.rs +++ b/crates/cdk/src/mint/mod.rs @@ -1131,7 +1131,8 @@ mod tests { unit: unit.clone(), amounts, input_fee_ppk: *fee, - use_keyset_v2: true, + keyset_id_type: cdk_common::nut02::KeySetVersion::Version00, + final_expiry: None, }) .await .unwrap(); @@ -1227,7 +1228,8 @@ mod tests { let keys = mint.pubkeys(); - let expected_keys = r#"{"keysets":[{"id":"0189b01ca2ba7320cc876dab22142a03715a69f94ce4b0b6b40495b181f7c84987","unit":"sat","active":true,"keys":{"1":"03ce4b803140715740d78f75ac6d3d45a65869a131e5ecc30e6a82fa28d6a20c92","1024":"025ba80cb0976ffb41a489ab0802b8d800f0ed98610a383cf50c4976ba2304f522","1048576":"0393b48556736402981d593ef65b2cf515a3a3c47fafcdf53d8d760c8e408a3f15","1073741824":"0347232765cef64efad0d5ce6d6284ee0f30159560c1c99b1d5b03997e71601458","128":"0387657827149eecc59f3e9ad005c6921d146918aad2d621fcd607da491647f7af","131072":"021336ad827102d1cc3f38593e3678db3ad541c501eb00edfd2e7e3273490a907d","134217728":"0307a702d8e33120d14c4be4a7e59f2bdca85fc9a0aa44d03f046ee2e381b17370","16":"029fd0c57ea3413c6513786ce24fd9bc3d271c5dd289d44a62a4f238d249f487c1","16384":"0205e262dec067013a410be5a40db16747f63a9666ed0cd6d919dfb8414a5c0dfc","16777216":"0365e8c8e449a8505b99b385eddc6537aeef065047bfe1011174b440394b44119c","2":"030baaed63a0f7e70b8d67b6e71b0a08bbea9a76003a3202171a39f23c1b7a6cfe","2048":"03177255abc417bdfd2cc0b3f01d74721c60001d3eda5c9229741aff09e8318b44","2097152":"03c0c77353f25ced0eb614613ae71ad79953a5b6d0d2453c67261fe7b810b0d49b","2147483648":"0236fd16a9269bf9125bcfb60df63b28b45a20b95520b000364feb2028f7da35fb","256":"028e68e298c203a9234f419fe26d395943191cce026d31be93f1d0eb0087acf0a6","262144":"02e7e2a871b5b02fb3070450be5f3dc329ed759f14d6a60ec12098209fba2177e1","268435456":"037795e574ea67518bfab1a871c65db8fb7f9f330853a0ad2126441598fe2aaffb","32":"0364d5774ba9cd0a26dacc48ca162f9f2117daf76140cedd0022a4086be44b9771","32768":"02202ab81477b68d35ada9645626ca2d1ed1d03405e07204a21ef76179b953b5d9","33554432":"02669a53f43c897fc1e3fd0537a2fef7cd0028b9c17ba8b19f260f1d3d8987a680","4":"038c299183d2c117fd6f7481ff20b89c1eddf32c4bf35d0e6739fa791b8cbcdcd5","4096":"02c86b0d5a85472c8eca02ee050a0aabc22713b2f393ad8e30f236650d6f6fa44a","4194304":"03a00ff9c25e6dcf06dc2fafc3f0ca24de53d1b125852613a80c609b39482bc557","512":"02308dda7d4c70c68acd531dd3505fb5aa0d12dfa7d185b8a6ef56b9448d019e1b","524288":"0375be62f8ea713636f81d16b57d29c224219bb1ad56befd447ef2bcf08a4342ea","536870912":"0346f1c1d1deb0697afdbb1f3aee035b144d24c90a91000bc3fd0c5acabdaf8381","64":"0347c66658e7e2df639cdd6532a4f1aabf9bd331f0e3010ea9c736cb17750de18e","65536":"02c28e5d2aa58fc68297d1e4c56cbe63ea39dff243fb87c4ad1b61d54288539548","67108864":"03d17eff1ce29b40c41a9733702ad4888b1b04eaaa30967a3e91fcb5ddae32b255","8":"02177b2d66b5b3b5271252b75ebb8eb577f433889153152ec9334dba4915adab30","8192":"02c861553ac0d05415b81e0c75200306407a30242e2495e31e6c216650780f1830","8388608":"031189128904ca7473698bc1fd9435df8aece3f9915f06d5ecc82eaf117d9c71f4"},"input_fee_ppk":0}]}"#; + let expected_keys = r#"{"keysets":[{"id":"00214f2e37bdebad","unit":"sat","active":true,"keys":{"1":"024aebe0f8be04b1ba1d7d6b7fe454c9ae43e0fa22b2fdc88b172f3c5a0d19aaa4","1024":"02f050a80caa51a655a4adfcb4c9f6954d9d3b465d4535055319090948d4e89eb5","1048576":"0270b4f86ee9b5a3b3e4ee7f0067af466676c57265c65ce91fbecac7d07ee507d1","1073741824":"035f7fd86429f45f19463840cb90a6053197e808b38ca2e32e2e8690e269c6d820","128":"0214318e5f69e01babdfc0fe923801a58e6e4fd24efe13432f2fec03eae746a0ca","131072":"02aee4305555703649245920911f9da6bc86d59ca7a016b753925454288bf05758","134217728":"02647036cd6c6e93a520968a33f20599f147debc4dce1994e83dc86946ff8c0183","16":"03e41c8e640ad4bcba9d39a0553a8ac1059a0bfd65fd9c19efbe6db844d0d04440","16384":"026c140e10dbdf282f47d0889b427a18c18ef9dfc87888c4487aea7093d3a9dd2e","16777216":"0204d4854439b387fdee5ad46806e4f8a38dabc0158ddcc189912fdc76e2f13161","2":"02c36fe78839c9ccbf3ea3f43b0d38ebd0e25df5310975de879b6fe9b53a3f4038","2048":"03b80c919d38ae697f28af7b5812f38ab886072179616d2b1636708473cd351cf3","2097152":"023a0d0e3a76b085df6f02b1b454758062a097c52927e682be458d549781cbcc96","2147483648":"03e47890abf0fc06b178500dc642e01a648a3674b89634eefc0269c0561023b135","256":"0294de1d276c4b092b41eb50a6545c1d07c4fe173214fd299925774ae1dc190925","262144":"02069c8cbd7e26d7cf84d7a1e2080a1006f4541c6ec75628bf2f4f15dff31ca39a","268435456":"03667e9818b5c49d7fb9ce6ddbfee8933a6ad62b9f15297c84adb251d9cb0db4a2","32":"03d49648d7137553edb6d9d45a47e271fff7301b84e7c854844292dc67d3f39aa2","32768":"02ac41bdfbfbdbb6ebedeb2651c05a6c01e44c15f69cc728878e988eab0af98265","33554432":"03502a5360cb536d3e32abd0bddb214c2a51d9a0c6dc41b346b603206b607074ae","4":"03d8deb39a4d45d120980c973aeb2b4aa9ca1ffbf222a2b7b83f7e1bc68453e6fb","4096":"03d131e59268288cb6a9af37e6ad3f6702cefb36cd4b7fb21120355acf2563749f","4194304":"029b1766c75b5c8789c81194551a65c7902e23fc974ae39a1e2f8a69fe1a786a1f","512":"030552b9dba664409946d5f895cdda030bf7bad8dc7ce085ffb93dbfa54ef2dbfc","524288":"0276c4ca58705002ae7319b33c63e4fcd0aca83e4bf731a1a047c64d4ca3fedd8d","536870912":"028263d89b6ca5fa838a37db6ab35606b2c2955aa717956afc872b9ed3f31c48c5","64":"03966b40fb692370864afe4f161909247b589f4c572a5aa0895b0f297fe00dc894","65536":"03434e6c95715e2cce9f09a40125cbf1dec8247784c22fc5d2629092401b177835","67108864":"03c3ac346a9b0671caffcc3a16e18fbf679b024a4e5f85d7edebc2ca0152b97b7b","8":"0360bb9c61e60f998585768a8893f89963da5699d6ee76c938d148ec3815ed5419","8192":"022cc0be442980edb83fa46771dc3c4303cc572b03d3b174879ca2e10b071f2b4b","8388608":"032818f6e7d6d6dec4bd349df6e609f6c2a9555012e924356c911ee2dbfa8a3d98"},"input_fee_ppk":0}]}"#; + println!("keys: {}", serde_json::to_string(&keys.clone()).unwrap()); assert_eq!(expected_keys, serde_json::to_string(&keys.clone()).unwrap()); } @@ -1258,4 +1260,33 @@ mod tests { mint.start().await.expect("Should be able to restart"); mint.stop().await.expect("Final stop should work"); } + + #[tokio::test] + async fn mint_unit_string_collision() { + let mut supported_units = HashMap::new(); + supported_units.insert(CurrencyUnit::default(), (0, 32)); + let config = MintConfig::<'_> { + supported_units: supported_units.clone(), + ..Default::default() + }; + let mint = create_mint(config).await; + + let amounts: Vec = (0..32).map(|i| 2_u64.pow(i as u32)).collect(); + let currency_unit = CurrencyUnit::custom("sW8W2A_hTH_gapj1_vj5suO3JI_"); + let rotate_argument = RotateKeyArguments { + unit: currency_unit, + amounts, + input_fee_ppk: 100, + keyset_id_type: cdk_common::nut02::KeySetVersion::Version00, + final_expiry: None, + }; + let rotation_result = mint.signatory.rotate_keyset(rotate_argument).await; + + assert!(rotation_result.is_err()); + + assert!(matches!( + rotation_result, + Err(Error::UnitStringCollision(_currency_unit)) + )); + } }