1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use std::{
    io::{self, Error, ErrorKind},
    string::String,
};

use bip32::{DerivationPath, Language, Mnemonic, XPrv};
use rand_core::OsRng;

use crate::key::hot::Key;

/// Only supports "English" for now.
/// ref. https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
/// ref. https://github.com/rust-bitcoin/rust-bitcoin/blob/master/src/util/bip32.rs
/// ref. https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md
/// ref. https://iancoleman.io/bip39/
pub fn gen_24() -> String {
    let m = Mnemonic::random(&mut OsRng, Language::English);
    let s = m.phrase();
    assert_eq!(s.split(' ').count(), 24);
    String::from(s)
}

impl Key {
    pub fn from_mnemonic_phrase<S>(phrase: S, derive_path: S) -> io::Result<Self>
    where
        S: AsRef<str>,
    {
        let deriv: DerivationPath = derive_path.as_ref().parse().map_err(|e| {
            return Error::new(
                ErrorKind::Other,
                format!("failed to parse derive path ({})", e),
            );
        })?;

        let mnemonic = Mnemonic::new(phrase, Language::English).map_err(|e| {
            return Error::new(
                ErrorKind::Other,
                format!("failed to read mnemonic phrase ({})", e),
            );
        })?;
        let seed = mnemonic.to_seed("password");

        // ref. https://github.com/ava-labs/avalanche-wallet/blob/v0.3.8/src/js/wallets/MnemonicWallet.ts
        let child_xprv = XPrv::derive_from_path(&seed, &deriv).map_err(|e| {
            return Error::new(
                ErrorKind::Other,
                format!("failed to derive AVAX account path ({})", e),
            );
        })?;

        let pk = child_xprv.private_key().to_bytes();

        let mut key = Self::from_private_key_raw(&pk)?;
        key.mnemonic_phrase = Some(String::from(mnemonic.phrase()));
        Ok(key)
    }
}

/// RUST_LOG=debug cargo test --package avalanche-types --lib -- key::mnemonic::test_mnemonic --exact --show-output
#[test]
fn test_mnemonic() {
    use crate::key::hot;
    use log::info;
    use rust_embed::RustEmbed;
    let _ = env_logger::builder()
        .filter_level(log::LevelFilter::Info)
        .is_test(true)
        .try_init();

    let deriv_path = String::from("m/44'/9000'/0'");

    #[derive(RustEmbed)]
    #[folder = "artifacts/"]
    #[prefix = "artifacts/"]
    struct Asset;

    let test_keys_file =
        Asset::get("artifacts/test.insecure.secp256k1.key.infos.mnemonic.json").unwrap();
    let test_keys_file_contents = std::str::from_utf8(test_keys_file.data.as_ref()).unwrap();
    let key_infos: Vec<hot::PrivateKeyInfoEntry> =
        serde_json::from_slice(&test_keys_file_contents.as_bytes()).unwrap();

    for (pos, ki) in key_infos.iter().enumerate() {
        info!("checking the key info at {}", pos);

        let k = Key::from_private_key(&ki.private_key).unwrap();
        assert_eq!(
            k,
            Key::from_private_key_eth(&k.private_key_hex.clone()).unwrap(),
        );
        assert_eq!(
            k,
            Key::from_private_key_raw(&k.private_key.as_bytes()).unwrap(),
        );

        let mut k2 = k.clone();
        k2.mnemonic_phrase = ki.mnemonic_phrase.clone();
        assert_eq!(
            k2,
            Key::from_mnemonic_phrase(&ki.mnemonic_phrase.clone().unwrap(), &deriv_path).unwrap(),
        );

        assert_eq!(k.private_key_hex.clone(), ki.private_key_hex);

        assert_eq!(
            k.address("X", 1).unwrap(),
            ki.addresses.get("1").unwrap().x_address
        );
        assert_eq!(
            k.address("P", 1).unwrap(),
            ki.addresses.get("1").unwrap().p_address
        );
        assert_eq!(
            k.address("C", 1).unwrap(),
            ki.addresses.get("1").unwrap().c_address
        );

        assert_eq!(
            k.address("X", 9999).unwrap(),
            ki.addresses.get("9999").unwrap().x_address
        );
        assert_eq!(
            k.address("P", 9999).unwrap(),
            ki.addresses.get("9999").unwrap().p_address
        );
        assert_eq!(
            k.address("C", 9999).unwrap(),
            ki.addresses.get("9999").unwrap().c_address
        );

        assert_eq!(k.short_address, ki.short_address);
        assert_eq!(k.eth_address, ki.eth_address);
    }
}