|
@@ -162,27 +162,35 @@ impl Id {
|
|
|
///
|
|
///
|
|
|
/// This function will not panic under normal circumstances as the hash output
|
|
/// This function will not panic under normal circumstances as the hash output
|
|
|
/// is always valid hex and the correct length.
|
|
/// 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();
|
|
let mut keys: Vec<(&Amount, &super::PublicKey)> = map.iter().collect();
|
|
|
keys.sort_by_key(|(amt, _v)| *amt);
|
|
keys.sort_by_key(|(amt, _v)| *amt);
|
|
|
|
|
|
|
|
- let mut pubkeys_concat: Vec<u8> = keys
|
|
|
|
|
|
|
+ let keys_string = keys
|
|
|
.iter()
|
|
.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 {
|
|
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());
|
|
let hex_of_hash = hex::encode(hash.to_byte_array());
|
|
|
|
|
|
|
|
Self {
|
|
Self {
|
|
@@ -472,14 +480,11 @@ pub struct KeySet {
|
|
|
/// Keyset state - indicates whether the mint will sign new outputs with this keyset
|
|
/// Keyset state - indicates whether the mint will sign new outputs with this keyset
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
pub active: Option<bool>,
|
|
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`]
|
|
/// Keyset [`Keys`]
|
|
|
pub keys: Keys,
|
|
pub keys: Keys,
|
|
|
|
|
+ /// Input Fee PPK
|
|
|
|
|
+ #[serde(default)]
|
|
|
|
|
+ pub input_fee_ppk: u64,
|
|
|
/// Expiry
|
|
/// Expiry
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
pub final_expiry: Option<u64>,
|
|
pub final_expiry: Option<u64>,
|
|
@@ -490,7 +495,12 @@ impl KeySet {
|
|
|
pub fn verify_id(&self) -> Result<(), Error> {
|
|
pub fn verify_id(&self) -> Result<(), Error> {
|
|
|
let keys_id = match self.id.version {
|
|
let keys_id = match self.id.version {
|
|
|
KeySetVersion::Version00 => Id::v1_from_keys(&self.keys),
|
|
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!(
|
|
ensure_cdk!(
|
|
@@ -572,6 +582,9 @@ pub struct MintKeySet {
|
|
|
pub unit: CurrencyUnit,
|
|
pub unit: CurrencyUnit,
|
|
|
/// Keyset [`MintKeys`]
|
|
/// Keyset [`MintKeys`]
|
|
|
pub keys: MintKeys,
|
|
pub keys: MintKeys,
|
|
|
|
|
+ /// Input Fee PPK
|
|
|
|
|
+ #[serde(default)]
|
|
|
|
|
+ pub input_fee_ppk: u64,
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
/// Expiry [`Option<u64>`]
|
|
/// Expiry [`Option<u64>`]
|
|
|
pub final_expiry: Option<u64>,
|
|
pub final_expiry: Option<u64>,
|
|
@@ -590,6 +603,7 @@ impl MintKeySet {
|
|
|
xpriv: Xpriv,
|
|
xpriv: Xpriv,
|
|
|
unit: CurrencyUnit,
|
|
unit: CurrencyUnit,
|
|
|
amounts: &[u64],
|
|
amounts: &[u64],
|
|
|
|
|
+ input_fee_ppk: u64,
|
|
|
final_expiry: Option<u64>,
|
|
final_expiry: Option<u64>,
|
|
|
version: KeySetVersion,
|
|
version: KeySetVersion,
|
|
|
) -> Self {
|
|
) -> Self {
|
|
@@ -615,12 +629,15 @@ impl MintKeySet {
|
|
|
let keys = MintKeys::new(map);
|
|
let keys = MintKeys::new(map);
|
|
|
let id = match version {
|
|
let id = match version {
|
|
|
KeySetVersion::Version00 => Id::v1_from_keys(&keys.clone().into()),
|
|
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 {
|
|
Self {
|
|
|
id,
|
|
id,
|
|
|
unit,
|
|
unit,
|
|
|
keys,
|
|
keys,
|
|
|
|
|
+ input_fee_ppk,
|
|
|
final_expiry,
|
|
final_expiry,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -631,12 +648,14 @@ impl MintKeySet {
|
|
|
///
|
|
///
|
|
|
/// This function will panic if the RNG fails or if key derivation fails,
|
|
/// This function will panic if the RNG fails or if key derivation fails,
|
|
|
/// which should not happen under normal circumstances.
|
|
/// which should not happen under normal circumstances.
|
|
|
|
|
+ #[allow(clippy::too_many_arguments)]
|
|
|
pub fn generate_from_seed<C: secp256k1::Signing>(
|
|
pub fn generate_from_seed<C: secp256k1::Signing>(
|
|
|
secp: &Secp256k1<C>,
|
|
secp: &Secp256k1<C>,
|
|
|
seed: &[u8],
|
|
seed: &[u8],
|
|
|
amounts: &[u64],
|
|
amounts: &[u64],
|
|
|
currency_unit: CurrencyUnit,
|
|
currency_unit: CurrencyUnit,
|
|
|
derivation_path: DerivationPath,
|
|
derivation_path: DerivationPath,
|
|
|
|
|
+ input_fee_ppk: u64,
|
|
|
final_expiry: Option<u64>,
|
|
final_expiry: Option<u64>,
|
|
|
version: KeySetVersion,
|
|
version: KeySetVersion,
|
|
|
) -> Self {
|
|
) -> Self {
|
|
@@ -648,6 +667,7 @@ impl MintKeySet {
|
|
|
.expect("RNG busted"),
|
|
.expect("RNG busted"),
|
|
|
currency_unit,
|
|
currency_unit,
|
|
|
amounts,
|
|
amounts,
|
|
|
|
|
+ input_fee_ppk,
|
|
|
final_expiry,
|
|
final_expiry,
|
|
|
version,
|
|
version,
|
|
|
)
|
|
)
|
|
@@ -659,12 +679,14 @@ impl MintKeySet {
|
|
|
///
|
|
///
|
|
|
/// This function will panic if the RNG fails or if key derivation fails,
|
|
/// This function will panic if the RNG fails or if key derivation fails,
|
|
|
/// which should not happen under normal circumstances.
|
|
/// which should not happen under normal circumstances.
|
|
|
|
|
+ #[allow(clippy::too_many_arguments)]
|
|
|
pub fn generate_from_xpriv<C: secp256k1::Signing>(
|
|
pub fn generate_from_xpriv<C: secp256k1::Signing>(
|
|
|
secp: &Secp256k1<C>,
|
|
secp: &Secp256k1<C>,
|
|
|
xpriv: Xpriv,
|
|
xpriv: Xpriv,
|
|
|
amounts: &[u64],
|
|
amounts: &[u64],
|
|
|
currency_unit: CurrencyUnit,
|
|
currency_unit: CurrencyUnit,
|
|
|
derivation_path: DerivationPath,
|
|
derivation_path: DerivationPath,
|
|
|
|
|
+ input_fee_ppk: u64,
|
|
|
final_expiry: Option<u64>,
|
|
final_expiry: Option<u64>,
|
|
|
version: KeySetVersion,
|
|
version: KeySetVersion,
|
|
|
) -> Self {
|
|
) -> Self {
|
|
@@ -675,6 +697,7 @@ impl MintKeySet {
|
|
|
.expect("RNG busted"),
|
|
.expect("RNG busted"),
|
|
|
currency_unit,
|
|
currency_unit,
|
|
|
amounts,
|
|
amounts,
|
|
|
|
|
+ input_fee_ppk,
|
|
|
final_expiry,
|
|
final_expiry,
|
|
|
version,
|
|
version,
|
|
|
)
|
|
)
|
|
@@ -687,7 +710,12 @@ impl From<MintKeySet> for Id {
|
|
|
let keys: Keys = keyset.keys.into();
|
|
let keys: Keys = keyset.keys.into();
|
|
|
match keyset.id.version {
|
|
match keyset.id.version {
|
|
|
KeySetVersion::Version00 => Id::v1_from_keys(&keys),
|
|
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() {
|
|
fn test_v2_deserialization_and_id_generation() {
|
|
|
let unit: CurrencyUnit = CurrencyUnit::from_str("sat").unwrap();
|
|
let unit: CurrencyUnit = CurrencyUnit::from_str("sat").unwrap();
|
|
|
let expiry: u64 = 2059210353; // +10 years from now
|
|
let expiry: u64 = 2059210353; // +10 years from now
|
|
|
|
|
+ let input_fee_ppk = 100;
|
|
|
|
|
|
|
|
let keys: Keys = serde_json::from_str(SHORT_KEYSET).unwrap();
|
|
let keys: Keys = serde_json::from_str(SHORT_KEYSET).unwrap();
|
|
|
let id_from_str =
|
|
let id_from_str =
|
|
|
- Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
|
|
|
|
|
|
|
+ Id::from_str("015ba18a8adcd02e715a58358eb618da4a4b3791151a4bee5e968bb88406ccf76a")
|
|
|
.unwrap();
|
|
.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);
|
|
assert_eq!(id, id_from_str);
|
|
|
|
|
|
|
|
let keys: Keys = serde_json::from_str(KEYSET).unwrap();
|
|
let keys: Keys = serde_json::from_str(KEYSET).unwrap();
|
|
|
let id_from_str =
|
|
let id_from_str =
|
|
|
- Id::from_str("0125bc634e270ad7e937af5b957f8396bb627d73f6e1fd2ffe4294c26b57daf9e0")
|
|
|
|
|
|
|
+ Id::from_str("01ab6aa4ff30390da34986d84be5274b48ad7a74265d791095bfc39f4098d9764f")
|
|
|
.unwrap();
|
|
.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);
|
|
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 =
|
|
let id_from_str =
|
|
|
- Id::from_str("016d72f27c8d22808ad66d1959b3dab83af17e2510db7ffd57d2365d9eec3ced75")
|
|
|
|
|
|
|
+ Id::from_str("012fbb01a4e200c76df911eeba3b8fe1831202914b24664f4bccbd25852a6708f8")
|
|
|
.unwrap();
|
|
.unwrap();
|
|
|
assert_eq!(id, id_from_str);
|
|
assert_eq!(id, id_from_str);
|
|
|
}
|
|
}
|