Skip to main content

cashu/nuts/
nut02.rs

1//! NUT-02: Keysets and keyset ID
2//!
3//! <https://github.com/cashubtc/nuts/blob/main/02.md>
4
5use core::fmt;
6use core::str::FromStr;
7use std::array::TryFromSliceError;
8#[cfg(feature = "mint")]
9use std::collections::BTreeMap;
10
11#[cfg(feature = "mint")]
12use bitcoin::bip32::{ChildNumber, DerivationPath, Xpriv};
13use bitcoin::hashes::sha256::Hash as Sha256;
14use bitcoin::hashes::Hash;
15#[cfg(feature = "mint")]
16use bitcoin::key::Secp256k1;
17#[cfg(feature = "mint")]
18use bitcoin::secp256k1;
19use serde::{Deserialize, Deserializer, Serialize};
20use serde_with::{serde_as, VecSkipError};
21use thiserror::Error;
22
23use super::nut01::Keys;
24#[cfg(feature = "mint")]
25use super::nut01::{MintKeyPair, MintKeys};
26use crate::nuts::nut00::CurrencyUnit;
27use crate::util::hex;
28use crate::{ensure_cdk, Amount};
29
30/// NUT02 Error
31#[derive(Debug, Error)]
32pub enum Error {
33    /// Hex Error
34    #[error(transparent)]
35    HexError(#[from] hex::Error),
36    /// Keyset length error
37    #[error("NUT02: ID length invalid, expected 8 bytes (short/v1) or 33 bytes (v2)")]
38    Length,
39    /// Unknown version
40    #[error("NUT02: Unknown Version")]
41    UnknownVersion,
42    /// Keyset id does not match
43    #[error("Keyset id incorrect")]
44    IncorrectKeysetId,
45    /// Short keyset id does not match any of the provided IDv2s
46    #[error("Short keyset id does not match any of the provided IDv2s")]
47    UnknownShortKeysetId,
48    /// Short keyset id is ill-formed
49    #[error("Short keyset id is ill-formed")]
50    MalformedShortKeysetId,
51    /// Slice Error
52    #[error(transparent)]
53    Slice(#[from] TryFromSliceError),
54}
55
56/// Keyset version
57#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
58pub enum KeySetVersion {
59    /// Version 00
60    Version00,
61    /// Version 01
62    Version01,
63}
64
65impl KeySetVersion {
66    /// [`KeySetVersion`] to byte
67    pub fn to_byte(&self) -> u8 {
68        match self {
69            Self::Version00 => 0,
70            Self::Version01 => 1,
71        }
72    }
73
74    /// [`KeySetVersion`] from byte
75    pub fn from_byte(byte: &u8) -> Result<Self, Error> {
76        match byte {
77            0 => Ok(Self::Version00),
78            1 => Ok(Self::Version01),
79            _ => Err(Error::UnknownVersion),
80        }
81    }
82
83    /// [`KeySetVersion`] from proto value
84    pub fn from_proto_i32(value: i32) -> Result<Self, Error> {
85        match value {
86            1 => Ok(Self::Version00),
87            2 => Ok(Self::Version01),
88            _ => Err(Error::UnknownVersion),
89        }
90    }
91
92    /// [`KeySetVersion`] to proto value
93    pub fn to_proto_i32(&self) -> i32 {
94        match self {
95            Self::Version00 => 1,
96            Self::Version01 => 2,
97        }
98    }
99}
100
101impl fmt::Display for KeySetVersion {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        match self {
104            KeySetVersion::Version00 => f.write_str("00"),
105            KeySetVersion::Version01 => f.write_str("01"),
106        }
107    }
108}
109
110/// Keyset ID bytes
111#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
112pub enum IdBytes {
113    /// Bytes for v1
114    V1([u8; 7]),
115    /// Bytes for v2
116    V2([u8; 32]),
117}
118
119impl IdBytes {
120    /// Convert [`IdBytes`] to [`Vec<u8>`]
121    pub fn to_vec(&self) -> Vec<u8> {
122        match self {
123            IdBytes::V1(bytes) => bytes.to_vec(),
124            IdBytes::V2(bytes) => bytes.to_vec(),
125        }
126    }
127}
128
129/// A keyset ID is an identifier for a specific keyset. It can be derived by
130/// anyone who knows the set of public keys of a mint. The keyset ID **CAN**
131/// be stored in a Cashu token such that the token can be used to identify
132/// which mint or keyset it was generated from.
133#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
134#[serde(into = "String", try_from = "String")]
135pub struct Id {
136    version: KeySetVersion,
137    id: IdBytes,
138}
139
140impl Id {
141    const STRLEN_V1: usize = 14;
142    const BYTELEN_V1: usize = 7;
143    const STRLEN_V2: usize = 64;
144    const BYTELEN_V2: usize = 32;
145
146    /// [`Id`] to bytes
147    pub fn to_bytes(&self) -> Vec<u8> {
148        [vec![self.version.to_byte()], self.id.to_vec()].concat()
149    }
150
151    /// [`Id`] from bytes
152    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
153        if bytes.is_empty() {
154            return Err(Error::Length);
155        }
156
157        let version = KeySetVersion::from_byte(&bytes[0])?;
158        let id = match version {
159            KeySetVersion::Version00 => IdBytes::V1(bytes[1..].try_into()?),
160            KeySetVersion::Version01 => IdBytes::V2(bytes[1..].try_into()?),
161        };
162        Ok(Self { version, id })
163    }
164
165    /// Get the version of the keyset
166    pub fn get_version(&self) -> KeySetVersion {
167        self.version
168    }
169
170    /// *** V2 KEYSET ***
171    /// create [`Id`] v2 from keys, unit and (optionally) expiry
172    /// 1 - sort public keys by their amount in ascending order
173    /// 2 - concatenate all public keys to one byte array
174    /// 3 - concatenate the lowercase unit string to the byte array (e.g. "unit:sat")
175    /// 4 - If a final expiration is specified, convert it into a radix-10 string and concatenate it (e.g "final_expiry:1896187313")
176    /// 5 - HASH_SHA256 the concatenated byte array and take the first 31 bytes
177    /// 6 - prefix it with a keyset ID version byte
178    ///
179    /// # Panics
180    ///
181    /// This function will not panic under normal circumstances as the hash output
182    /// is always valid hex and the correct length.
183    pub fn v2_from_data(
184        map: &Keys,
185        unit: &CurrencyUnit,
186        input_fee_ppk: u64,
187        expiry: Option<u64>,
188    ) -> Self {
189        let mut keys: Vec<(&Amount, &super::PublicKey)> = map.iter().collect();
190        keys.sort_by_key(|(amt, _v)| *amt);
191
192        let keys_string = keys
193            .iter()
194            .map(|(amt, pubkey)| format!("{}:{}", amt, hex::encode(pubkey.to_bytes())))
195            .collect::<Vec<String>>()
196            .join(",");
197
198        let mut data = keys_string;
199        data.push_str(&format!("|unit:{}", unit));
200
201        if input_fee_ppk > 0 {
202            data.push_str(&format!("|input_fee_ppk:{}", input_fee_ppk));
203        }
204
205        if let Some(expiry) = expiry {
206            if expiry > 0 {
207                data.push_str(&format!("|final_expiry:{}", expiry));
208            }
209        }
210
211        let hash = Sha256::hash(data.as_bytes());
212        let hex_of_hash = hex::encode(hash.to_byte_array());
213
214        Self {
215            version: KeySetVersion::Version01,
216            id: IdBytes::V2(
217                hex::decode(&hex_of_hash[0..Self::STRLEN_V2])
218                    .expect("Keys hash could not be hex decoded")
219                    .try_into()
220                    .expect("Invalid length of hex id"),
221            ),
222        }
223    }
224
225    /// *** V1 VERSION ***
226    /// As per NUT-02:
227    ///   1. sort public keys by their amount in ascending order
228    ///   2. concatenate all public keys to one string
229    ///   3. HASH_SHA256 the concatenated public keys
230    ///   4. take the first 14 characters of the hex-encoded hash
231    ///   5. prefix it with a keyset ID version byte
232    ///
233    /// # Panics
234    ///
235    /// This function will not panic under normal circumstances as the hash output
236    /// is always valid hex and the correct length.
237    pub fn v1_from_keys(map: &Keys) -> Self {
238        let mut keys: Vec<(&Amount, &super::PublicKey)> = map.iter().collect();
239        keys.sort_by_key(|(amt, _v)| *amt);
240
241        let pubkeys_concat: Vec<u8> = keys
242            .iter()
243            .map(|(_, pubkey)| pubkey.to_bytes())
244            .collect::<Vec<[u8; 33]>>()
245            .concat();
246
247        let hash = Sha256::hash(&pubkeys_concat);
248        let hex_of_hash = hex::encode(hash.to_byte_array());
249
250        Self {
251            version: KeySetVersion::Version00,
252            id: IdBytes::V1(
253                hex::decode(&hex_of_hash[0..Self::STRLEN_V1])
254                    .expect("Keys hash could not be hex decoded")
255                    .try_into()
256                    .expect("Invalid length of hex id"),
257            ),
258        }
259    }
260
261    /// Selects the correct IDv2 from a list of keysets and the given short-id
262    /// or returns the short-id in the case of v1.
263    pub fn from_short_keyset_id(
264        short_id: &ShortKeysetId,
265        keysets_info: &[KeySetInfo],
266    ) -> Result<Self, Error> {
267        // Check prefix length
268        if short_id.prefix.len() < Self::BYTELEN_V1 || short_id.prefix.len() > Self::BYTELEN_V2 {
269            return Err(Error::MalformedShortKeysetId);
270        }
271
272        match short_id.version {
273            KeySetVersion::Version00 => {
274                let mut idbytes: [u8; Self::BYTELEN_V1] = [0u8; Self::BYTELEN_V1];
275                idbytes.copy_from_slice(&short_id.prefix[..Self::BYTELEN_V1]);
276                Ok(Self {
277                    version: short_id.version,
278                    id: IdBytes::V1(idbytes),
279                })
280            }
281            KeySetVersion::Version01 => {
282                // We return the first match or error
283                for keyset_info in keysets_info.iter() {
284                    if keyset_info.id.version == KeySetVersion::Version01
285                        && keyset_info.id.id.to_vec().starts_with(&short_id.prefix)
286                    {
287                        return Ok(keyset_info.id);
288                    }
289                }
290                Err(Error::UnknownShortKeysetId)
291            }
292        }
293    }
294}
295
296// Used to generate a compressed unique identifier as part of the NUT13 spec
297// This is a one-way function
298impl From<Id> for u32 {
299    fn from(value: Id) -> Self {
300        let id_bytes = value.to_bytes();
301        let mut hex_bytes: [u8; 8] = [0; 8];
302        hex_bytes.copy_from_slice(&id_bytes[..8]);
303
304        let int = u64::from_be_bytes(hex_bytes);
305
306        (int % (2_u64.pow(31) - 1)) as u32
307    }
308}
309
310impl fmt::Display for Id {
311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312        let hex_id = match self.id {
313            IdBytes::V1(id) => hex::encode(id),
314            IdBytes::V2(id) => hex::encode(id),
315        };
316        f.write_str(&format!("{}{}", self.version, hex_id))
317    }
318}
319
320impl fmt::Debug for Id {
321    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
322        let hex_id = match self.id {
323            IdBytes::V1(id) => hex::encode(id),
324            IdBytes::V2(id) => hex::encode(id),
325        };
326        f.write_str(&format!("{}{}", self.version, hex_id))
327    }
328}
329
330impl TryFrom<String> for Id {
331    type Error = Error;
332
333    fn try_from(s: String) -> Result<Self, Self::Error> {
334        // Check that string is ASCII (required for hex) to avoid panics on byte slicing
335        // with multi-byte UTF-8 characters
336        ensure_cdk!(
337            s.is_ascii(),
338            Error::HexError(hex::Error::InvalidHexCharacter {
339                c: s.chars().find(|c| !c.is_ascii()).unwrap_or('\0'),
340                index: s.chars().position(|c| !c.is_ascii()).unwrap_or(0),
341            })
342        );
343
344        ensure_cdk!(
345            s.len() == Self::STRLEN_V1 + 2 || s.len() == Self::STRLEN_V2 + 2,
346            Error::Length
347        );
348
349        let version: KeySetVersion = KeySetVersion::from_byte(&hex::decode(&s[..2])?[0])?;
350        let id = match version {
351            KeySetVersion::Version00 => IdBytes::V1(
352                hex::decode(&s[2..])?
353                    .try_into()
354                    .map_err(|_| Error::Length)?,
355            ),
356            KeySetVersion::Version01 => IdBytes::V2(
357                hex::decode(&s[2..])?
358                    .try_into()
359                    .map_err(|_| Error::Length)?,
360            ),
361        };
362
363        Ok(Self { version, id })
364    }
365}
366
367impl FromStr for Id {
368    type Err = Error;
369
370    fn from_str(s: &str) -> Result<Self, Self::Err> {
371        Self::try_from(s.to_string())
372    }
373}
374
375impl From<Id> for String {
376    fn from(value: Id) -> Self {
377        value.to_string()
378    }
379}
380
381/// Improper prefix of the keyset ID. In case of v1, this is the whole ID.
382/// In case of v2, this is the 8-byte prefix
383#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
384#[serde(into = "String", try_from = "String")]
385pub struct ShortKeysetId {
386    /// The version of the short keyset
387    version: KeySetVersion,
388    /// The improper prefix of the keyset ID bytes
389    prefix: Vec<u8>,
390}
391
392impl ShortKeysetId {
393    /// [`ShortKeysetId`] to bytes
394    pub fn to_bytes(&self) -> Vec<u8> {
395        [vec![self.version.to_byte()], self.prefix.clone()].concat()
396    }
397
398    /// [`ShortKeysetId`] from bytes
399    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
400        if bytes.is_empty() {
401            return Err(Error::Length);
402        }
403
404        let version = KeySetVersion::from_byte(&bytes[0])?;
405        let prefix = bytes[1..].to_vec();
406        Ok(Self { version, prefix })
407    }
408}
409
410impl From<Id> for ShortKeysetId {
411    fn from(id: Id) -> Self {
412        let version = id.version;
413        let prefix: Vec<u8> = match id.version {
414            KeySetVersion::Version00 => match id.id {
415                IdBytes::V1(idbytes) => Vec::from(&idbytes),
416                _ => panic!("Unexpected IdBytes length"),
417            },
418            KeySetVersion::Version01 => match id.id {
419                IdBytes::V2(idbytes) => Vec::from(&idbytes[..7]),
420                _ => panic!("Unexpected IdBytes length"),
421            },
422        };
423
424        Self { version, prefix }
425    }
426}
427
428impl fmt::Display for ShortKeysetId {
429    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430        let hex_id = hex::encode(&self.prefix);
431        f.write_str(&format!("{}{}", self.version, hex_id))
432    }
433}
434
435impl fmt::Debug for ShortKeysetId {
436    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
437        let hex_id = hex::encode(&self.prefix);
438        f.write_str(&format!("{}{}", self.version, hex_id))
439    }
440}
441
442impl TryFrom<String> for ShortKeysetId {
443    type Error = Error;
444
445    fn try_from(s: String) -> Result<Self, Self::Error> {
446        // Check that string is ASCII (required for hex) to avoid panics on byte slicing
447        // with multi-byte UTF-8 characters
448        ensure_cdk!(
449            s.is_ascii(),
450            Error::HexError(hex::Error::InvalidHexCharacter {
451                c: s.chars().find(|c| !c.is_ascii()).unwrap_or('\0'),
452                index: s.chars().position(|c| !c.is_ascii()).unwrap_or(0),
453            })
454        );
455
456        let bytes = hex::decode(s)?;
457
458        Self::from_bytes(&bytes)
459    }
460}
461
462impl FromStr for ShortKeysetId {
463    type Err = Error;
464
465    fn from_str(s: &str) -> Result<Self, Self::Err> {
466        Self::try_from(s.to_string())
467    }
468}
469
470impl From<ShortKeysetId> for String {
471    fn from(value: ShortKeysetId) -> Self {
472        value.to_string()
473    }
474}
475
476/// Mint Keysets [NUT-02]
477/// Ids of mints keyset ids
478#[serde_as]
479#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
480pub struct KeysetResponse {
481    /// set of public key ids that the mint generates
482    #[serde_as(as = "VecSkipError<_>")]
483    pub keysets: Vec<KeySetInfo>,
484}
485
486/// Keyset
487#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
488pub struct KeySet {
489    /// Keyset [`Id`]
490    pub id: Id,
491    /// Keyset [`CurrencyUnit`]
492    pub unit: CurrencyUnit,
493    /// Keyset state - indicates whether the mint will sign new outputs with this keyset
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub active: Option<bool>,
496    /// Keyset [`Keys`]
497    pub keys: Keys,
498    /// Input Fee PPK
499    #[serde(default)]
500    pub input_fee_ppk: u64,
501    /// Expiry
502    #[serde(skip_serializing_if = "Option::is_none")]
503    pub final_expiry: Option<u64>,
504}
505
506impl KeySet {
507    /// Verify the keyset id matches keys
508    pub fn verify_id(&self) -> Result<(), Error> {
509        let keys_id = match self.id.version {
510            KeySetVersion::Version00 => Id::v1_from_keys(&self.keys),
511            KeySetVersion::Version01 => Id::v2_from_data(
512                &self.keys,
513                &self.unit,
514                self.input_fee_ppk,
515                self.final_expiry,
516            ),
517        };
518
519        ensure_cdk!(
520            u32::from(keys_id) == u32::from(self.id),
521            Error::IncorrectKeysetId
522        );
523
524        ensure_cdk!(keys_id == self.id, Error::IncorrectKeysetId);
525
526        Ok(())
527    }
528}
529
530/// KeySetInfo
531#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
532pub struct KeySetInfo {
533    /// Keyset [`Id`]
534    pub id: Id,
535    /// Keyset [`CurrencyUnit`]
536    pub unit: CurrencyUnit,
537    /// Keyset state
538    /// Mint will only sign from an active keyset
539    pub active: bool,
540    /// Input Fee PPK
541    #[serde(
542        deserialize_with = "deserialize_input_fee_ppk",
543        default = "default_input_fee_ppk"
544    )]
545    pub input_fee_ppk: u64,
546    /// Expiry of the keyset
547    #[serde(skip_serializing_if = "Option::is_none")]
548    pub final_expiry: Option<u64>,
549}
550
551/// List of [KeySetInfo]
552pub type KeySetInfos = Vec<KeySetInfo>;
553
554/// Utility methods for [KeySetInfos]
555pub trait KeySetInfosMethods {
556    /// Filter for active keysets
557    fn active(&self) -> impl Iterator<Item = &KeySetInfo> + '_;
558
559    /// Filter keysets for specific unit
560    fn unit(&self, unit: CurrencyUnit) -> impl Iterator<Item = &KeySetInfo> + '_;
561}
562
563impl KeySetInfosMethods for KeySetInfos {
564    fn active(&self) -> impl Iterator<Item = &KeySetInfo> + '_ {
565        self.iter().filter(|k| k.active)
566    }
567
568    fn unit(&self, unit: CurrencyUnit) -> impl Iterator<Item = &KeySetInfo> + '_ {
569        self.iter().filter(move |k| k.unit == unit)
570    }
571}
572
573fn deserialize_input_fee_ppk<'de, D>(deserializer: D) -> Result<u64, D::Error>
574where
575    D: Deserializer<'de>,
576{
577    // This will either give us a u64 or null (which becomes None)
578    let opt = Option::<u64>::deserialize(deserializer)?;
579    Ok(opt.unwrap_or_else(default_input_fee_ppk))
580}
581
582fn default_input_fee_ppk() -> u64 {
583    0
584}
585
586#[cfg(feature = "mint")]
587/// Mint keyset.
588///
589/// This type intentionally does not implement [`Serialize`] because it contains
590/// mint signing keys.
591///
592/// ```compile_fail
593/// fn assert_serialize<T: serde::Serialize>() {}
594/// assert_serialize::<cashu::nuts::nut02::MintKeySet>();
595/// ```
596#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
597pub struct MintKeySet {
598    /// Keyset [`Id`]
599    pub id: Id,
600    /// Keyset [`CurrencyUnit`]
601    pub unit: CurrencyUnit,
602    /// Keyset [`MintKeys`]
603    pub keys: MintKeys,
604    /// Input Fee PPK
605    #[serde(default)]
606    pub input_fee_ppk: u64,
607    /// Expiry [`Option<u64>`]
608    pub final_expiry: Option<u64>,
609}
610
611#[cfg(feature = "mint")]
612impl MintKeySet {
613    /// Generate new [`MintKeySet`]
614    ///
615    /// # Panics
616    ///
617    /// This function will panic if the RNG fails or if key derivation fails,
618    /// which should not happen under normal circumstances.
619    pub fn generate<C: secp256k1::Signing>(
620        secp: &Secp256k1<C>,
621        xpriv: Xpriv,
622        unit: CurrencyUnit,
623        amounts: &[u64],
624        input_fee_ppk: u64,
625        final_expiry: Option<u64>,
626        version: KeySetVersion,
627    ) -> Self {
628        let mut map = BTreeMap::new();
629        for (i, amount) in amounts.iter().enumerate() {
630            let secret_key = xpriv
631                .derive_priv(
632                    secp,
633                    &[ChildNumber::from_hardened_idx(i as u32).expect("order is valid index")],
634                )
635                .expect("RNG busted")
636                .private_key;
637            let public_key = secret_key.public_key(secp);
638            map.insert(
639                amount.into(),
640                MintKeyPair {
641                    secret_key: secret_key.into(),
642                    public_key: public_key.into(),
643                },
644            );
645        }
646
647        let keys = MintKeys::new(map);
648        let id = match version {
649            KeySetVersion::Version00 => Id::v1_from_keys(&keys.clone().into()),
650            KeySetVersion::Version01 => {
651                Id::v2_from_data(&keys.clone().into(), &unit, input_fee_ppk, final_expiry)
652            }
653        };
654        Self {
655            id,
656            unit,
657            keys,
658            input_fee_ppk,
659            final_expiry,
660        }
661    }
662
663    /// Generate new [`MintKeySet`] from seed
664    ///
665    /// # Panics
666    ///
667    /// This function will panic if the RNG fails or if key derivation fails,
668    /// which should not happen under normal circumstances.
669    #[allow(clippy::too_many_arguments)]
670    pub fn generate_from_seed<C: secp256k1::Signing>(
671        secp: &Secp256k1<C>,
672        seed: &[u8],
673        amounts: &[u64],
674        currency_unit: CurrencyUnit,
675        derivation_path: DerivationPath,
676        input_fee_ppk: u64,
677        final_expiry: Option<u64>,
678        version: KeySetVersion,
679    ) -> Self {
680        let xpriv = Xpriv::new_master(bitcoin::Network::Bitcoin, seed).expect("RNG busted");
681        Self::generate(
682            secp,
683            xpriv
684                .derive_priv(secp, &derivation_path)
685                .expect("RNG busted"),
686            currency_unit,
687            amounts,
688            input_fee_ppk,
689            final_expiry,
690            version,
691        )
692    }
693
694    /// Generate new [`MintKeySet`] from xpriv
695    ///
696    /// # Panics
697    ///
698    /// This function will panic if the RNG fails or if key derivation fails,
699    /// which should not happen under normal circumstances.
700    #[allow(clippy::too_many_arguments)]
701    pub fn generate_from_xpriv<C: secp256k1::Signing>(
702        secp: &Secp256k1<C>,
703        xpriv: Xpriv,
704        amounts: &[u64],
705        currency_unit: CurrencyUnit,
706        derivation_path: DerivationPath,
707        input_fee_ppk: u64,
708        final_expiry: Option<u64>,
709        version: KeySetVersion,
710    ) -> Self {
711        Self::generate(
712            secp,
713            xpriv
714                .derive_priv(secp, &derivation_path)
715                .expect("RNG busted"),
716            currency_unit,
717            amounts,
718            input_fee_ppk,
719            final_expiry,
720            version,
721        )
722    }
723}
724
725#[cfg(feature = "mint")]
726impl From<MintKeySet> for Id {
727    fn from(keyset: MintKeySet) -> Id {
728        let keys: Keys = keyset.keys.into();
729        match keyset.id.version {
730            KeySetVersion::Version00 => Id::v1_from_keys(&keys),
731            KeySetVersion::Version01 => Id::v2_from_data(
732                &keys,
733                &keyset.unit,
734                keyset.input_fee_ppk,
735                keyset.final_expiry,
736            ),
737        }
738    }
739}
740
741#[cfg(feature = "mint")]
742impl From<&MintKeys> for Id {
743    fn from(map: &MintKeys) -> Self {
744        let keys: super::Keys = map.clone().into();
745
746        Id::v1_from_keys(&keys)
747    }
748}
749
750#[cfg(test)]
751mod test {
752    use std::str::FromStr;
753
754    use bitcoin::secp256k1::rand::{self, RngCore};
755
756    use super::{KeySetInfo, KeySetVersion, Keys, KeysetResponse, ShortKeysetId};
757    use crate::nuts::nut02::{Error, Id};
758    use crate::nuts::KeysResponse;
759    use crate::util::hex;
760    use crate::CurrencyUnit;
761
762    const SHORT_KEYSET_ID: &str = "00456a94ab4e1c46";
763    const SHORT_KEYSET: &str = r#"
764        {
765            "1":"03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc",
766            "2":"03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de",
767            "4":"02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303",
768            "8":"02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528"
769        }
770    "#;
771
772    const KEYSET_ID: &str = "000f01df73ea149a";
773    const KEYSET: &str = r#"
774        {
775            "1":"03ba786a2c0745f8c30e490288acd7a72dd53d65afd292ddefa326a4a3fa14c566",
776            "2":"03361cd8bd1329fea797a6add1cf1990ffcf2270ceb9fc81eeee0e8e9c1bd0cdf5",
777            "4":"036e378bcf78738ddf68859293c69778035740e41138ab183c94f8fee7572214c7",
778            "8":"03909d73beaf28edfb283dbeb8da321afd40651e8902fcf5454ecc7d69788626c0",
779            "16":"028a36f0e6638ea7466665fe174d958212723019ec08f9ce6898d897f88e68aa5d",
780            "32":"03a97a40e146adee2687ac60c2ba2586a90f970de92a9d0e6cae5a4b9965f54612",
781            "64":"03ce86f0c197aab181ddba0cfc5c5576e11dfd5164d9f3d4a3fc3ffbbf2e069664",
782            "128":"0284f2c06d938a6f78794814c687560a0aabab19fe5e6f30ede38e113b132a3cb9",
783            "256":"03b99f475b68e5b4c0ba809cdecaae64eade2d9787aa123206f91cd61f76c01459",
784            "512":"03d4db82ea19a44d35274de51f78af0a710925fe7d9e03620b84e3e9976e3ac2eb",
785            "1024":"031fbd4ba801870871d46cf62228a1b748905ebc07d3b210daf48de229e683f2dc",
786            "2048":"0276cedb9a3b160db6a158ad4e468d2437f021293204b3cd4bf6247970d8aff54b",
787            "4096":"02fc6b89b403ee9eb8a7ed457cd3973638080d6e04ca8af7307c965c166b555ea2",
788            "8192":"0320265583e916d3a305f0d2687fcf2cd4e3cd03a16ea8261fda309c3ec5721e21",
789            "16384":"036e41de58fdff3cb1d8d713f48c63bc61fa3b3e1631495a444d178363c0d2ed50",
790            "32768":"0365438f613f19696264300b069d1dad93f0c60a37536b72a8ab7c7366a5ee6c04",
791            "65536":"02408426cfb6fc86341bac79624ba8708a4376b2d92debdf4134813f866eb57a8d",
792            "131072":"031063e9f11c94dc778c473e968966eac0e70b7145213fbaff5f7a007e71c65f41",
793            "262144":"02f2a3e808f9cd168ec71b7f328258d0c1dda250659c1aced14c7f5cf05aab4328",
794            "524288":"038ac10de9f1ff9395903bb73077e94dbf91e9ef98fd77d9a2debc5f74c575bc86",
795            "1048576":"0203eaee4db749b0fc7c49870d082024b2c31d889f9bc3b32473d4f1dfa3625788",
796            "2097152":"033cdb9d36e1e82ae652b7b6a08e0204569ec7ff9ebf85d80a02786dc7fe00b04c",
797            "4194304":"02c8b73f4e3a470ae05e5f2fe39984d41e9f6ae7be9f3b09c9ac31292e403ac512",
798            "8388608":"025bbe0cfce8a1f4fbd7f3a0d4a09cb6badd73ef61829dc827aa8a98c270bc25b0",
799            "16777216":"037eec3d1651a30a90182d9287a5c51386fe35d4a96839cf7969c6e2a03db1fc21",
800            "33554432":"03280576b81a04e6abd7197f305506476f5751356b7643988495ca5c3e14e5c262",
801            "67108864":"03268bfb05be1dbb33ab6e7e00e438373ca2c9b9abc018fdb452d0e1a0935e10d3",
802            "134217728":"02573b68784ceba9617bbcc7c9487836d296aa7c628c3199173a841e7a19798020",
803            "268435456":"0234076b6e70f7fbf755d2227ecc8d8169d662518ee3a1401f729e2a12ccb2b276",
804            "536870912":"03015bd88961e2a466a2163bd4248d1d2b42c7c58a157e594785e7eb34d880efc9",
805            "1073741824":"02c9b076d08f9020ebee49ac8ba2610b404d4e553a4f800150ceb539e9421aaeee",
806            "2147483648":"034d592f4c366afddc919a509600af81b489a03caf4f7517c2b3f4f2b558f9a41a",
807            "4294967296":"037c09ecb66da082981e4cbdb1ac65c0eb631fc75d85bed13efb2c6364148879b5",
808            "8589934592":"02b4ebb0dda3b9ad83b39e2e31024b777cc0ac205a96b9a6cfab3edea2912ed1b3",
809            "17179869184":"026cc4dacdced45e63f6e4f62edbc5779ccd802e7fabb82d5123db879b636176e9",
810            "34359738368":"02b2cee01b7d8e90180254459b8f09bbea9aad34c3a2fd98c85517ecfc9805af75",
811            "68719476736":"037a0c0d564540fc574b8bfa0253cca987b75466e44b295ed59f6f8bd41aace754",
812            "137438953472":"021df6585cae9b9ca431318a713fd73dbb76b3ef5667957e8633bca8aaa7214fb6",
813            "274877906944":"02b8f53dde126f8c85fa5bb6061c0be5aca90984ce9b902966941caf963648d53a",
814            "549755813888":"029cc8af2840d59f1d8761779b2496623c82c64be8e15f9ab577c657c6dd453785",
815            "1099511627776":"03e446fdb84fad492ff3a25fc1046fb9a93a5b262ebcd0151caa442ea28959a38a",
816            "2199023255552":"02d6b25bd4ab599dd0818c55f75702fde603c93f259222001246569018842d3258",
817            "4398046511104":"03397b522bb4e156ec3952d3f048e5a986c20a00718e5e52cd5718466bf494156a",
818            "8796093022208":"02d1fb9e78262b5d7d74028073075b80bb5ab281edcfc3191061962c1346340f1e",
819            "17592186044416":"030d3f2ad7a4ca115712ff7f140434f802b19a4c9b2dd1c76f3e8e80c05c6a9310",
820            "35184372088832":"03e325b691f292e1dfb151c3fb7cad440b225795583c32e24e10635a80e4221c06",
821            "70368744177664":"03bee8f64d88de3dee21d61f89efa32933da51152ddbd67466bef815e9f93f8fd1",
822            "140737488355328":"0327244c9019a4892e1f04ba3bf95fe43b327479e2d57c25979446cc508cd379ed",
823            "281474976710656":"02fb58522cd662f2f8b042f8161caae6e45de98283f74d4e99f19b0ea85e08a56d",
824            "562949953421312":"02adde4b466a9d7e59386b6a701a39717c53f30c4810613c1b55e6b6da43b7bc9a",
825            "1125899906842624":"038eeda11f78ce05c774f30e393cda075192b890d68590813ff46362548528dca9",
826            "2251799813685248":"02ec13e0058b196db80f7079d329333b330dc30c000dbdd7397cbbc5a37a664c4f",
827            "4503599627370496":"02d2d162db63675bd04f7d56df04508840f41e2ad87312a3c93041b494efe80a73",
828            "9007199254740992":"0356969d6aef2bb40121dbd07c68b6102339f4ea8e674a9008bb69506795998f49",
829            "18014398509481984":"02f4e667567ebb9f4e6e180a4113bb071c48855f657766bb5e9c776a880335d1d6",
830            "36028797018963968":"0385b4fe35e41703d7a657d957c67bb536629de57b7e6ee6fe2130728ef0fc90b0",
831            "72057594037927936":"02b2bc1968a6fddbcc78fb9903940524824b5f5bed329c6ad48a19b56068c144fd",
832            "144115188075855872":"02e0dbb24f1d288a693e8a49bc14264d1276be16972131520cf9e055ae92fba19a",
833            "288230376151711744":"03efe75c106f931a525dc2d653ebedddc413a2c7d8cb9da410893ae7d2fa7d19cc",
834            "576460752303423488":"02c7ec2bd9508a7fc03f73c7565dc600b30fd86f3d305f8f139c45c404a52d958a",
835            "1152921504606846976":"035a6679c6b25e68ff4e29d1c7ef87f21e0a8fc574f6a08c1aa45ff352c1d59f06",
836            "2305843009213693952":"033cdc225962c052d485f7cfbf55a5b2367d200fe1fe4373a347deb4cc99e9a099",
837            "4611686018427387904":"024a4b806cf413d14b294719090a9da36ba75209c7657135ad09bc65328fba9e6f",
838            "9223372036854775808":"0377a6fe114e291a8d8e991627c38001c8305b23b9e98b1c7b1893f5cd0dda6cad"
839        }
840    "#;
841
842    #[test]
843    fn test_deserialization_and_id_generation() {
844        let _id = Id::from_str("009a1f293253e41e").unwrap();
845
846        let keys: Keys = serde_json::from_str(SHORT_KEYSET).unwrap();
847
848        let id: Id = Id::v1_from_keys(&keys);
849
850        assert_eq!(id, Id::from_str(SHORT_KEYSET_ID).unwrap());
851
852        let keys: Keys = serde_json::from_str(KEYSET).unwrap();
853
854        let id: Id = Id::v1_from_keys(&keys);
855
856        assert_eq!(id, Id::from_str(KEYSET_ID).unwrap());
857    }
858
859    #[test]
860    fn test_v2_deserialization_and_id_generation() {
861        let unit: CurrencyUnit = CurrencyUnit::from_str("sat").unwrap();
862        let expiry: u64 = 2059210353; // +10 years from now
863        let input_fee_ppk = 100;
864
865        let keys: Keys = serde_json::from_str(SHORT_KEYSET).unwrap();
866        let id_from_str =
867            Id::from_str("015ba18a8adcd02e715a58358eb618da4a4b3791151a4bee5e968bb88406ccf76a")
868                .unwrap();
869        let id = Id::v2_from_data(&keys, &unit, input_fee_ppk, Some(expiry));
870        assert_eq!(id, id_from_str);
871
872        let keys: Keys = serde_json::from_str(KEYSET).unwrap();
873        let id_from_str =
874            Id::from_str("01ab6aa4ff30390da34986d84be5274b48ad7a74265d791095bfc39f4098d9764f")
875                .unwrap();
876        let id = Id::v2_from_data(&keys, &unit, 0, Some(expiry));
877        assert_eq!(id, id_from_str);
878
879        let id = Id::v2_from_data(&keys, &unit, 0, None);
880        let id_from_str =
881            Id::from_str("012fbb01a4e200c76df911eeba3b8fe1831202914b24664f4bccbd25852a6708f8")
882                .unwrap();
883        assert_eq!(id, id_from_str);
884    }
885
886    #[test]
887    fn test_deserialization_keyset_info() {
888        let h = r#"{"id":"009a1f293253e41e","unit":"sat","active":true}"#;
889
890        let _keyset_response: KeySetInfo = serde_json::from_str(h).unwrap();
891
892        let h = r#"{"id":"009a1f293253e41e","unit":"sat","active":true, "input_fee_ppk":null}"#;
893
894        let _keyset_response: KeySetInfo = serde_json::from_str(h).unwrap();
895    }
896
897    #[test]
898    fn test_deserialization_of_keyset_response() {
899        let h = r#"{"keysets":[{"id":"009a1f293253e41e","unit":"sat","active":true, "input_fee_ppk": 100},{"id":"eGnEWtdJ0PIM","unit":"sat","active":true},{"id":"003dfdf4e5e35487","unit":"sat","active":true},{"id":"0066ad1a4b6fc57c","unit":"sat","active":true},{"id":"00f7ca24d44c3e5e","unit":"sat","active":true},{"id":"001fcea2931f2d85","unit":"sat","active":true},{"id":"00d095959d940edb","unit":"sat","active":true},{"id":"000d7f730d657125","unit":"sat","active":true},{"id":"0007208d861d7295","unit":"sat","active":true},{"id":"00bfdf8889b719dd","unit":"sat","active":true},{"id":"00ca9b17da045f21","unit":"sat","active":true}]}"#;
900
901        let _keyset_response: KeysetResponse = serde_json::from_str(h).unwrap();
902    }
903
904    #[test]
905    fn test_to_int() {
906        let id = Id::from_str("009a1f293253e41e").unwrap();
907
908        let id_int = u32::from(id);
909        assert_eq!(864559728, id_int)
910    }
911
912    #[test]
913    fn test_v2_to_int() {
914        let id = Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
915            .unwrap();
916
917        let id_int = u32::from(id);
918        assert_eq!(2113471806, id_int);
919    }
920
921    #[test]
922    fn test_id_from_invalid_byte_length() {
923        let three_bytes = [0x01, 0x02, 0x03];
924        let result = Id::from_bytes(&three_bytes);
925        assert!(result.is_err(), "Expected an invalid byte length error");
926    }
927
928    #[test]
929    fn test_id_from_empty_bytes() {
930        let result = Id::from_bytes(&[]);
931        assert!(
932            matches!(result, Err(Error::Length)),
933            "Expected an Error::Length error but got {:?}",
934            result
935        );
936    }
937
938    #[test]
939    fn test_keyset_bytes() {
940        let id = Id::from_str("009a1f293253e41e").unwrap();
941
942        let id_bytes = id.to_bytes();
943
944        assert_eq!(id_bytes.len(), 8);
945
946        let id_from_bytes = Id::from_bytes(&id_bytes).unwrap();
947
948        assert_eq!(id_from_bytes, id);
949    }
950
951    #[test]
952    fn test_keyset_version_proto_mapping() {
953        assert_eq!(
954            KeySetVersion::from_proto_i32(1).unwrap(),
955            KeySetVersion::Version00
956        );
957        assert_eq!(
958            KeySetVersion::from_proto_i32(2).unwrap(),
959            KeySetVersion::Version01
960        );
961        assert!(matches!(
962            KeySetVersion::from_proto_i32(0),
963            Err(Error::UnknownVersion)
964        ));
965
966        assert_eq!(KeySetVersion::Version00.to_proto_i32(), 1);
967        assert_eq!(KeySetVersion::Version01.to_proto_i32(), 2);
968    }
969
970    #[test]
971    fn test_deserialization_keys_response() {
972        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"}}]}"#;
973
974        let keys_response: KeysResponse = serde_json::from_str(keys).unwrap();
975
976        assert_eq!(keys_response.keysets.len(), 2);
977    }
978
979    fn generate_random_id(version: KeySetVersion) -> Id {
980        match version {
981            KeySetVersion::Version00 => {
982                let mut rand_bytes = vec![0u8; 8];
983                rand::thread_rng().fill_bytes(&mut rand_bytes[1..]);
984                Id::from_bytes(&rand_bytes).unwrap_or_else(|e| {
985                    panic!("Failed to create Id from {}: {e}", hex::encode(rand_bytes))
986                })
987            }
988            KeySetVersion::Version01 => {
989                let mut rand_bytes = vec![1u8; 33];
990                rand::thread_rng().fill_bytes(&mut rand_bytes[1..]);
991                Id::from_bytes(&rand_bytes).unwrap_or_else(|e| {
992                    panic!("Failed to create Id from {}: {e}", hex::encode(rand_bytes))
993                })
994            }
995        }
996    }
997
998    #[test]
999    fn test_id_serialization() {
1000        let id = generate_random_id(KeySetVersion::Version00);
1001        let id_str = id.to_string();
1002
1003        assert!(id_str.chars().all(|c| c.is_ascii_hexdigit()));
1004        assert_eq!(16, id_str.len());
1005        assert_eq!(id_str.to_lowercase(), id_str);
1006    }
1007
1008    #[test]
1009    fn test_id_v2_serialization() {
1010        let id = generate_random_id(KeySetVersion::Version01);
1011        let id_str = id.to_string();
1012
1013        assert!(id_str.chars().all(|c| c.is_ascii_hexdigit()));
1014        assert_eq!(66, id_str.len());
1015        assert_eq!(id_str.to_lowercase(), id_str);
1016    }
1017
1018    #[test]
1019    fn test_id_deserialization() {
1020        let id_from_short_str = Id::from_str("00123");
1021        assert!(matches!(id_from_short_str, Err(Error::Length)));
1022
1023        let id_from_non_hex_str = Id::from_str(&SHORT_KEYSET_ID.replace('a', "x"));
1024        assert!(matches!(id_from_non_hex_str, Err(Error::HexError(_))));
1025
1026        let id_invalid_version = Id::from_str(&SHORT_KEYSET_ID.replace("00", "99"));
1027        assert!(matches!(id_invalid_version, Err(Error::UnknownVersion)));
1028
1029        let id_from_uppercase = Id::from_str(&SHORT_KEYSET_ID.to_uppercase());
1030        assert!(id_from_uppercase.is_ok());
1031    }
1032
1033    #[test]
1034    fn test_short_keyset_id_from_id() {
1035        let idv1 = Id::from_str("009a1f293253e41e").unwrap();
1036        let idv2 =
1037            Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
1038                .unwrap();
1039
1040        let short_id_1: ShortKeysetId = idv1.into();
1041        let short_id_2: ShortKeysetId = idv2.into();
1042
1043        assert!(short_id_1.to_string() == "009a1f293253e41e");
1044        assert!(short_id_2.to_string() == "01adc013fa9d8517");
1045    }
1046
1047    #[test]
1048    fn test_id_with_non_ascii_chars() {
1049        // Test that non-ASCII characters in ID strings don't cause panics
1050        // but instead return proper errors. This was found by fuzzing.
1051        // The character 'ǝ' is a 2-byte UTF-8 sequence that could cause
1052        // panics when slicing by byte index.
1053        let id_with_non_ascii = Id::from_str("0ǝfa73302d12ff{");
1054        assert!(matches!(id_with_non_ascii, Err(Error::HexError(_))));
1055
1056        // Also test ShortKeysetId
1057        let short_id_with_non_ascii = ShortKeysetId::from_str("0ǝfa73302d12ff");
1058        assert!(matches!(short_id_with_non_ascii, Err(Error::HexError(_))));
1059    }
1060
1061    #[test]
1062    fn test_v2_from_data_expiry_zero_is_same_as_none() {
1063        let unit: CurrencyUnit = CurrencyUnit::from_str("sat").unwrap();
1064        let keys: Keys = serde_json::from_str(SHORT_KEYSET).unwrap();
1065
1066        let id_with_none = Id::v2_from_data(&keys, &unit, 0, None);
1067        let id_with_zero = Id::v2_from_data(&keys, &unit, 0, Some(0));
1068        assert_eq!(
1069            id_with_none, id_with_zero,
1070            "expiry=Some(0) should be treated the same as expiry=None"
1071        );
1072    }
1073
1074    #[test]
1075    fn test_v2_from_data_input_fee_zero_is_same_as_no_fee_component() {
1076        let unit: CurrencyUnit = CurrencyUnit::from_str("sat").unwrap();
1077        let keys: Keys = serde_json::from_str(SHORT_KEYSET).unwrap();
1078
1079        let id_without_fee = Id::v2_from_data(&keys, &unit, 0, Some(2059210353));
1080        let id_with_same_inputs = Id::v2_from_data(&keys, &unit, 0, Some(2059210353));
1081
1082        assert_eq!(id_without_fee, id_with_same_inputs);
1083    }
1084
1085    #[test]
1086    fn test_from_short_keyset_id_prefix_too_short() {
1087        let v1_info = KeySetInfo {
1088            id: Id::from_str("00009a1f293253e4").unwrap(),
1089            unit: CurrencyUnit::Sat,
1090            active: true,
1091            input_fee_ppk: 0,
1092            final_expiry: None,
1093        };
1094        let keysets = vec![v1_info];
1095
1096        let short_id = ShortKeysetId::from_bytes(&[0x00, 0x01, 0x02, 0x03]).unwrap();
1097        let res = Id::from_short_keyset_id(&short_id, &keysets);
1098        assert!(
1099            matches!(res, Err(Error::MalformedShortKeysetId)),
1100            "Expected MalformedShortKeysetId for prefix < 7 bytes, got {:?}",
1101            res
1102        );
1103    }
1104
1105    #[test]
1106    fn test_from_short_keyset_id_prefix_too_long() {
1107        let v2_id =
1108            Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
1109                .unwrap();
1110        let v2_info = KeySetInfo {
1111            id: v2_id,
1112            unit: CurrencyUnit::Sat,
1113            active: true,
1114            input_fee_ppk: 0,
1115            final_expiry: None,
1116        };
1117        let keysets = vec![v2_info];
1118
1119        let mut bytes = vec![0x01u8];
1120        bytes.extend_from_slice(&[0xAA; 33]);
1121        let short_id = ShortKeysetId::from_bytes(&bytes).unwrap();
1122        let res = Id::from_short_keyset_id(&short_id, &keysets);
1123        assert!(
1124            matches!(res, Err(Error::MalformedShortKeysetId)),
1125            "Expected MalformedShortKeysetId for prefix > 32 bytes, got {:?}",
1126            res
1127        );
1128    }
1129
1130    #[test]
1131    fn test_from_short_keyset_id_panic_regression() {
1132        // v1 id - 7 bytes + 1 byte version prefix = 16 hex chars
1133        let v1_id = Id::from_str("00009a1f293253e4").unwrap();
1134        let v1_info = KeySetInfo {
1135            id: v1_id,
1136            unit: CurrencyUnit::Sat,
1137            active: true,
1138            input_fee_ppk: 0,
1139            final_expiry: None,
1140        };
1141
1142        // v2 id - 32 bytes
1143        let v2_id =
1144            Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
1145                .unwrap();
1146        let v2_info = KeySetInfo {
1147            id: v2_id,
1148            unit: CurrencyUnit::Sat,
1149            active: true,
1150            input_fee_ppk: 0,
1151            final_expiry: None,
1152        };
1153
1154        let keysets = vec![v1_info, v2_info];
1155
1156        // This is a v2 short id with 8 bytes prefix
1157        // Before the fix, this would panic when checking against v1_info
1158        let short_id = ShortKeysetId::from_str("01000000003b00000e").unwrap();
1159
1160        let res = Id::from_short_keyset_id(&short_id, &keysets);
1161        assert!(res.is_err());
1162    }
1163
1164    #[test]
1165    fn test_from_short_keyset_id_accepts_boundary_prefix_lengths() {
1166        let v1_id = Id::from_str("00009a1f293253e4").unwrap();
1167        let v2_id =
1168            Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
1169                .unwrap();
1170
1171        let keysets = vec![
1172            KeySetInfo {
1173                id: v1_id,
1174                unit: CurrencyUnit::Sat,
1175                active: true,
1176                input_fee_ppk: 0,
1177                final_expiry: None,
1178            },
1179            KeySetInfo {
1180                id: v2_id,
1181                unit: CurrencyUnit::Sat,
1182                active: true,
1183                input_fee_ppk: 0,
1184                final_expiry: None,
1185            },
1186        ];
1187
1188        let short_v1 = ShortKeysetId::from_bytes(&v1_id.to_bytes()).unwrap();
1189        let short_v2 = ShortKeysetId::from(v2_id);
1190
1191        assert_eq!(
1192            Id::from_short_keyset_id(&short_v1, &keysets).unwrap(),
1193            v1_id
1194        );
1195        assert_eq!(
1196            Id::from_short_keyset_id(&short_v2, &keysets).unwrap(),
1197            v2_id
1198        );
1199    }
1200
1201    #[test]
1202    fn test_from_short_keyset_id_returns_unknown_when_v2_prefix_does_not_match() {
1203        let v2_id =
1204            Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
1205                .unwrap();
1206        let keysets = vec![KeySetInfo {
1207            id: v2_id,
1208            unit: CurrencyUnit::Sat,
1209            active: true,
1210            input_fee_ppk: 0,
1211            final_expiry: None,
1212        }];
1213
1214        let short_id = ShortKeysetId::from_str("01ffffffffffffff").unwrap();
1215        let res = Id::from_short_keyset_id(&short_id, &keysets);
1216
1217        assert!(matches!(res, Err(Error::UnknownShortKeysetId)));
1218    }
1219
1220    #[test]
1221    fn test_from_short_keyset_id_accepts_exact_32_byte_v2_prefix() {
1222        // Boundary test: a short id whose prefix is exactly 32 bytes (BYTELEN_V2)
1223        // must be accepted and match the full v2 id (which is itself 32 bytes).
1224        let v2_id =
1225            Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
1226                .unwrap();
1227        let keysets = vec![KeySetInfo {
1228            id: v2_id,
1229            unit: CurrencyUnit::Sat,
1230            active: true,
1231            input_fee_ppk: 0,
1232            final_expiry: None,
1233        }];
1234
1235        // Build a ShortKeysetId whose prefix equals the full 32-byte id.
1236        // The byte representation is: [version_byte, 32-byte id...], total 33 bytes.
1237        let short_id = ShortKeysetId::from_bytes(&v2_id.to_bytes()).unwrap();
1238        assert_eq!(short_id.prefix.len(), 32);
1239
1240        let res = Id::from_short_keyset_id(&short_id, &keysets).unwrap();
1241        assert_eq!(res, v2_id);
1242    }
1243
1244    #[test]
1245    fn test_id_debug_format_equals_display() {
1246        // Ensure Debug impl is not a no-op. Mutants that replace the body with
1247        // Ok(Default::default()) would produce an empty string.
1248        let id_v1 = Id::from_str(SHORT_KEYSET_ID).unwrap();
1249        assert_eq!(format!("{:?}", id_v1), id_v1.to_string());
1250        assert!(!format!("{:?}", id_v1).is_empty());
1251
1252        let id_v2 =
1253            Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
1254                .unwrap();
1255        assert_eq!(format!("{:?}", id_v2), id_v2.to_string());
1256        assert!(!format!("{:?}", id_v2).is_empty());
1257    }
1258
1259    #[test]
1260    fn test_short_keyset_id_debug_format_equals_display() {
1261        // Mutants replacing the body with Ok(Default::default()) would produce "".
1262        let short_v1 = ShortKeysetId::from_str(SHORT_KEYSET_ID).unwrap();
1263        assert_eq!(format!("{:?}", short_v1), short_v1.to_string());
1264        assert!(!format!("{:?}", short_v1).is_empty());
1265
1266        let v2_id =
1267            Id::from_str("01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035")
1268                .unwrap();
1269        let short_v2: ShortKeysetId = v2_id.into();
1270        assert_eq!(format!("{:?}", short_v2), short_v2.to_string());
1271        assert!(!format!("{:?}", short_v2).is_empty());
1272    }
1273
1274    #[test]
1275    fn test_keyset_verify_id_valid_returns_ok() {
1276        use super::KeySet;
1277
1278        // v1 keyset
1279        let keys: Keys = serde_json::from_str(SHORT_KEYSET).unwrap();
1280        let id = Id::v1_from_keys(&keys);
1281        let keyset = KeySet {
1282            id,
1283            unit: CurrencyUnit::Sat,
1284            active: Some(true),
1285            keys: keys.clone(),
1286            input_fee_ppk: 0,
1287            final_expiry: None,
1288        };
1289        assert!(keyset.verify_id().is_ok(), "valid v1 keyset should verify");
1290
1291        // v2 keyset
1292        let unit = CurrencyUnit::Sat;
1293        let input_fee_ppk = 100u64;
1294        let expiry = Some(2059210353u64);
1295        let id_v2 = Id::v2_from_data(&keys, &unit, input_fee_ppk, expiry);
1296        let keyset_v2 = KeySet {
1297            id: id_v2,
1298            unit,
1299            active: Some(true),
1300            keys,
1301            input_fee_ppk,
1302            final_expiry: expiry,
1303        };
1304        assert!(
1305            keyset_v2.verify_id().is_ok(),
1306            "valid v2 keyset should verify"
1307        );
1308    }
1309
1310    #[test]
1311    fn test_keyset_verify_id_mismatch_returns_error() {
1312        use super::KeySet;
1313
1314        // Use SHORT_KEYSET's keys but mislabel it with KEYSET_ID (from a different keyset).
1315        let keys: Keys = serde_json::from_str(SHORT_KEYSET).unwrap();
1316        let wrong_id = Id::from_str(KEYSET_ID).unwrap();
1317
1318        let keyset = KeySet {
1319            id: wrong_id,
1320            unit: CurrencyUnit::Sat,
1321            active: Some(true),
1322            keys,
1323            input_fee_ppk: 0,
1324            final_expiry: None,
1325        };
1326
1327        let res = keyset.verify_id();
1328        assert!(
1329            matches!(res, Err(Error::IncorrectKeysetId)),
1330            "expected IncorrectKeysetId, got {:?}",
1331            res
1332        );
1333    }
1334
1335    #[test]
1336    fn test_keyset_infos_active_filter() {
1337        use super::KeySetInfosMethods;
1338
1339        let id_a = Id::from_str("009a1f293253e41e").unwrap();
1340        let id_b = Id::from_str("00456a94ab4e1c46").unwrap();
1341        let id_c = Id::from_str("000f01df73ea149a").unwrap();
1342
1343        let infos: Vec<KeySetInfo> = vec![
1344            KeySetInfo {
1345                id: id_a,
1346                unit: CurrencyUnit::Sat,
1347                active: true,
1348                input_fee_ppk: 0,
1349                final_expiry: None,
1350            },
1351            KeySetInfo {
1352                id: id_b,
1353                unit: CurrencyUnit::Sat,
1354                active: false,
1355                input_fee_ppk: 0,
1356                final_expiry: None,
1357            },
1358            KeySetInfo {
1359                id: id_c,
1360                unit: CurrencyUnit::Usd,
1361                active: true,
1362                input_fee_ppk: 0,
1363                final_expiry: None,
1364            },
1365        ];
1366
1367        let active_ids: Vec<Id> = infos.active().map(|k| k.id).collect();
1368        assert_eq!(active_ids.len(), 2, "expected two active keysets");
1369        assert!(active_ids.contains(&id_a));
1370        assert!(active_ids.contains(&id_c));
1371        assert!(!active_ids.contains(&id_b));
1372    }
1373
1374    #[test]
1375    fn test_keyset_infos_unit_filter() {
1376        use super::KeySetInfosMethods;
1377
1378        let id_a = Id::from_str("009a1f293253e41e").unwrap();
1379        let id_b = Id::from_str("00456a94ab4e1c46").unwrap();
1380        let id_c = Id::from_str("000f01df73ea149a").unwrap();
1381
1382        let infos: Vec<KeySetInfo> = vec![
1383            KeySetInfo {
1384                id: id_a,
1385                unit: CurrencyUnit::Sat,
1386                active: true,
1387                input_fee_ppk: 0,
1388                final_expiry: None,
1389            },
1390            KeySetInfo {
1391                id: id_b,
1392                unit: CurrencyUnit::Usd,
1393                active: true,
1394                input_fee_ppk: 0,
1395                final_expiry: None,
1396            },
1397            KeySetInfo {
1398                id: id_c,
1399                unit: CurrencyUnit::Sat,
1400                active: false,
1401                input_fee_ppk: 0,
1402                final_expiry: None,
1403            },
1404        ];
1405
1406        let sat_ids: Vec<Id> = infos.unit(CurrencyUnit::Sat).map(|k| k.id).collect();
1407        assert_eq!(sat_ids.len(), 2, "expected two Sat keysets");
1408        assert!(sat_ids.contains(&id_a));
1409        assert!(sat_ids.contains(&id_c));
1410        assert!(
1411            !sat_ids.contains(&id_b),
1412            "Usd keyset should not appear in Sat filter"
1413        );
1414
1415        // A unit that does not match any entry must return an empty iterator.
1416        let none_ids: Vec<Id> = infos.unit(CurrencyUnit::Eur).map(|k| k.id).collect();
1417        assert!(none_ids.is_empty());
1418    }
1419
1420    #[test]
1421    fn test_default_input_fee_ppk_is_zero() {
1422        // KeySetInfo missing the input_fee_ppk field must default to 0.
1423        let json = r#"{"id":"009a1f293253e41e","unit":"sat","active":true}"#;
1424        let info: KeySetInfo = serde_json::from_str(json).unwrap();
1425        assert_eq!(info.input_fee_ppk, 0);
1426
1427        // Explicit null should also default to 0 via the custom deserializer.
1428        let json_null =
1429            r#"{"id":"009a1f293253e41e","unit":"sat","active":true,"input_fee_ppk":null}"#;
1430        let info_null: KeySetInfo = serde_json::from_str(json_null).unwrap();
1431        assert_eq!(info_null.input_fee_ppk, 0);
1432    }
1433}