Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 56 additions & 27 deletions crates/cashu/src/nuts/nut02.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64>) -> Self {
pub fn v2_from_data(
map: &Keys,
unit: &CurrencyUnit,
input_fee_ppk: u64,
expiry: Option<u64>,
) -> Self {
let mut keys: Vec<(&Amount, &super::PublicKey)> = map.iter().collect();
keys.sort_by_key(|(amt, _v)| *amt);

let mut pubkeys_concat: Vec<u8> = keys
let keys_string = keys
.iter()
.map(|(_, pubkey)| pubkey.to_bytes())
.collect::<Vec<[u8; 33]>>()
.concat();
.map(|(amt, pubkey)| format!("{}:{}", amt, hex::encode(pubkey.to_bytes())))
.collect::<Vec<String>>()
.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 {
Expand Down Expand Up @@ -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<bool>,
/// 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<u64>,
Expand All @@ -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!(
Expand Down Expand Up @@ -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<u64>`]
pub final_expiry: Option<u64>,
Expand All @@ -590,6 +603,7 @@ impl MintKeySet {
xpriv: Xpriv,
unit: CurrencyUnit,
amounts: &[u64],
input_fee_ppk: u64,
final_expiry: Option<u64>,
version: KeySetVersion,
) -> Self {
Expand All @@ -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,
}
}
Expand All @@ -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<C: secp256k1::Signing>(
secp: &Secp256k1<C>,
seed: &[u8],
amounts: &[u64],
currency_unit: CurrencyUnit,
derivation_path: DerivationPath,
input_fee_ppk: u64,
final_expiry: Option<u64>,
version: KeySetVersion,
) -> Self {
Expand All @@ -648,6 +667,7 @@ impl MintKeySet {
.expect("RNG busted"),
currency_unit,
amounts,
input_fee_ppk,
final_expiry,
version,
)
Expand All @@ -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<C: secp256k1::Signing>(
secp: &Secp256k1<C>,
xpriv: Xpriv,
amounts: &[u64],
currency_unit: CurrencyUnit,
derivation_path: DerivationPath,
input_fee_ppk: u64,
final_expiry: Option<u64>,
version: KeySetVersion,
) -> Self {
Expand All @@ -675,6 +697,7 @@ impl MintKeySet {
.expect("RNG busted"),
currency_unit,
amounts,
input_fee_ppk,
final_expiry,
version,
)
Expand All @@ -687,7 +710,12 @@ impl From<MintKeySet> 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,
),
}
}
}
Expand Down Expand Up @@ -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);
}
Expand Down
6 changes: 3 additions & 3 deletions crates/cdk-common/src/database/wallet/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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,
};

Expand All @@ -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,
};

Expand Down
4 changes: 3 additions & 1 deletion crates/cdk-signatory/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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,
);
Expand Down Expand Up @@ -139,6 +140,7 @@ pub fn create_new_keyset<C: secp256k1::Signing>(
.expect("RNG busted"),
unit,
amounts,
input_fee_ppk,
final_expiry,
// TODO: change this to Version01 to generate keysets v2
cdk_common::nut02::KeySetVersion::Version00,
Expand Down
3 changes: 3 additions & 0 deletions crates/cdk-signatory/src/db_signatory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)
Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -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,
);
Expand Down
2 changes: 1 addition & 1 deletion crates/cdk-signatory/src/proto/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,6 @@ impl TryInto<cdk_common::KeySet> 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()))?
Expand All @@ -326,6 +325,7 @@ impl TryInto<cdk_common::KeySet> for KeySet {
.map(|(k, v)| cdk_common::PublicKey::from_slice(&v).map(|pk| (k.into(), pk)))
.collect::<Result<BTreeMap<cdk_common::Amount, cdk_common::PublicKey>, _>>()?,
),
input_fee_ppk: self.input_fee_ppk,
final_expiry: self.final_expiry,
})
}
Expand Down
4 changes: 2 additions & 2 deletions crates/cdk-signatory/src/signatory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ pub struct SignatoryKeySet {
pub keys: Keys,
/// Amounts supported by the keyset
pub amounts: Vec<u64>,
/// 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<u64>,
Expand All @@ -91,8 +91,8 @@ impl From<SignatoryKeySet> 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,
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/cdk-sql-common/src/mint/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub(crate) fn sql_row_to_keyset_info(row: Vec<Column>) -> Result<MintKeySetInfo,
derivation_path: column_as_string!(derivation_path, DerivationPath::from_str),
derivation_path_index: column_as_nullable_number!(derivation_path_index),
amounts,
input_fee_ppk: column_as_number!(row_keyset_ppk),
input_fee_ppk: column_as_nullable_number!(row_keyset_ppk).unwrap_or(0),
final_expiry: column_as_nullable_number!(valid_to),
})
}
Expand Down
2 changes: 1 addition & 1 deletion crates/cdk-sql-common/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1368,7 +1368,7 @@ fn sql_row_to_keyset(row: Vec<Column>) -> Result<KeySetInfo, Error> {
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),
})
}
Expand Down
2 changes: 1 addition & 1 deletion crates/cdk/src/mint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
2 changes: 1 addition & 1 deletion crates/cdk/src/wallet/auth/auth_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down