Skip to main content

iris_crypto/
lib.rs

1#![no_std]
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5#[cfg(feature = "alloc")]
6use alloc::string::{String, ToString};
7
8pub mod cheetah;
9pub mod slip10;
10
11pub use cheetah::{PrivateKey, PublicKey, Signature};
12pub use slip10::{derive_master_key, ExtendedKey};
13
14#[cfg(feature = "mnemonic")]
15pub use bip39::Mnemonic;
16pub use iris_ztd::{Digest, Hashable};
17
18#[cfg(feature = "argon2")]
19use argon2::{Algorithm, Argon2, Params, Version};
20
21/// Generate master key from entropy and salt using Argon2 + BIP39 + SLIP-10
22#[cfg(all(feature = "argon2", feature = "mnemonic"))]
23pub fn gen_master_key(entropy: &[u8], salt: &[u8]) -> (String, ExtendedKey) {
24    let mut argon_output = [0u8; 32];
25    let params = Params::new(
26        786_432,  // m_cost: 768 MiB in KiB
27        6,        // t_cost: 6 iterations
28        4,        // p_cost: 4 threads
29        Some(32), // output length
30    )
31    .expect("Invalid Argon2 parameters");
32
33    Argon2::new(Algorithm::Argon2d, Version::V0x13, params)
34        .hash_password_into(entropy, salt, &mut argon_output)
35        .expect("Invalid entropy and/or salt");
36
37    argon_output.reverse();
38
39    let mnemonic = Mnemonic::from_entropy(&argon_output).unwrap();
40    (
41        mnemonic.to_string(),
42        derive_master_key(&mnemonic.to_seed("")),
43    )
44}
45
46#[cfg(test)]
47mod tests {
48    extern crate alloc;
49    use alloc::string::{String, ToString};
50    use alloc::{vec, vec::Vec};
51    use ibig::UBig;
52    use iris_ztd::Hashable;
53
54    use super::*;
55
56    fn parse_byts_decimal(wid: usize, decimal: &str) -> Vec<u8> {
57        let cleaned: String = decimal.chars().filter(|c| c.is_ascii_digit()).collect();
58        let n = UBig::from_str_radix(&cleaned, 10).expect("Invalid decimal value");
59        let bytes_be = n.to_be_bytes();
60        let mut res = vec![0u8; wid];
61        let mut started = false;
62        let mut idx = 0;
63        for byte in bytes_be.iter() {
64            if *byte != 0 || started {
65                started = true;
66                if idx < wid {
67                    res[idx] = *byte;
68                    idx += 1;
69                }
70            }
71        }
72        res
73    }
74
75    #[cfg(feature = "alloc")]
76    #[test]
77    fn test_keygen() {
78        const LOG_ENTROPY_DEC: &str =
79            "31944036134313954129336387727597658952065175074761089084822804536972439767490";
80        const LOG_SALT_DEC: &str = "143851195137845551434793173733272547792";
81        const LOG_MNEMONIC: &str = "pass destroy hub reject cricket flight camp garden scale liquid increase pool miracle fly tower file door cage vault tone night zero push crime";
82
83        let entropy = parse_byts_decimal(32, LOG_ENTROPY_DEC);
84        let salt = parse_byts_decimal(16, LOG_SALT_DEC);
85
86        let (mnemonic, keypair) = gen_master_key(&entropy, &salt);
87        assert_eq!(mnemonic, LOG_MNEMONIC);
88
89        // check private key, chain code and pkh
90        assert_eq!(
91            hex::encode(keypair.private_key.unwrap().to_be_bytes()),
92            "362b4073814e43f427983a83f11efcceb6741082c18f0d64b7e47340ba4485ba"
93        );
94        assert_eq!(
95            hex::encode(keypair.chain_code),
96            "95b522320f4dfae7486155b9529c582af3d7898ece606a802c43415786ced8d9"
97        );
98        assert_eq!(
99            keypair.public_key.hash().to_string(),
100            "AyzPiJoqcqmdZdjxZ9aGLnVsbYcCphidHERKBWVXyKhNqTirshTmicG"
101        );
102    }
103
104    #[cfg(feature = "alloc")]
105    #[test]
106    fn test_ledger_keygen() {
107        // This private key is derived from the default seed and default path for CX_CURVE_Ed25519
108        // on ledger devices
109        let speculos_default_seed =
110            hex::decode("781FB0E232257AE8F9471884436EBC5617E33BF9B8316C35E489052917D40055575003F3D82B18415AE90D24A70FCDE604FF27AD0D8E0FFDF10EF0C41447516D").unwrap();
111        let keypair = derive_master_key(&speculos_default_seed);
112        assert_eq!(
113            keypair.public_key.hash().to_string(),
114            "4EUr383qLuCEzejdmCigFhrsoDkHG2crGxBBFhR35zjVfzn5QdhazGb"
115        );
116    }
117}