iop_keyvault/secp256k1/
mod.rs

1//! SLIP-0010 and BIP-0032 compatible Secp256k1 cryptography that allows child key derivation.
2
3mod bip32;
4mod bip44;
5mod ext_pk;
6mod ext_sk;
7mod id;
8mod networks;
9mod pk;
10mod sig;
11mod sk;
12
13use super::*;
14
15use digest::generic_array::{typenum::U20, GenericArray};
16use digest::FixedOutput;
17use ripemd::Ripemd160;
18use sha2::{Digest, Sha256};
19
20fn hash160<B: AsRef<[u8]>>(input: B) -> GenericArray<u8, U20> {
21    let mut inner_hasher = Sha256::default();
22    inner_hasher.update(input);
23    let mut outer_hasher = Ripemd160::default();
24    outer_hasher.update(inner_hasher.finalize_fixed());
25    outer_hasher.finalize_fixed()
26}
27
28const CHECKSUM_LEN: usize = 4;
29
30/// Encoding binary data with BASE58 after adding a 4-byte checksum pops up in the Bitcoin
31/// ecosystem on several places. Addresses, wallet-import-format, extended public and private
32/// key serialization formats. So this transformation is pulled up here as a free function.
33pub fn to_base58check<D: AsRef<[u8]>>(data: D) -> String {
34    let data = data.as_ref();
35    let mut inner_hasher = Sha256::default();
36    inner_hasher.update(data);
37    let mut outer_hasher = Sha256::default();
38    outer_hasher.update(inner_hasher.finalize_fixed());
39    let hash = outer_hasher.finalize_fixed();
40    let checksum = &hash[..CHECKSUM_LEN];
41    let mut bytes = Vec::with_capacity(data.len() + checksum.len());
42    bytes.extend_from_slice(data);
43    bytes.extend_from_slice(checksum);
44
45    // we do not need the multibase prefix, but want to conform to multibase otherwise.
46    // Prefix is always a single character.
47    let prefixed_enc = multibase::encode(multibase::Base::Base58Btc, &bytes);
48    prefixed_enc[1..].to_owned()
49}
50
51/// Decoding string with BASE58 into binary data and verify if the 4-byte checksum at the end
52/// matches the rest of the data. Only the decoded data without checksum will be returned.
53pub fn from_base58check<S: AsRef<str>>(s: S) -> Result<Vec<u8>> {
54    let mut to_decode = String::new();
55    to_decode.push(multibase::Base::Base58Btc.code());
56    to_decode += s.as_ref();
57    let (_base, checked_data) = multibase::decode(&to_decode)?;
58    let (data, actual_checksum) = checked_data.split_at(checked_data.len() - CHECKSUM_LEN);
59
60    let mut inner_hasher = Sha256::default();
61    inner_hasher.update(data);
62    let mut outer_hasher = Sha256::default();
63    outer_hasher.update(inner_hasher.finalize_fixed());
64    let hash = outer_hasher.finalize_fixed();
65    let expected_checksum = &hash[..CHECKSUM_LEN];
66
67    ensure!(expected_checksum == actual_checksum, "Incorrect checksum");
68
69    Ok(data.to_vec())
70}
71
72/// This elliptic curve cryptography implements both the [AsymmetricCrypto](AsymmetricCrypto) and
73/// [KeyDerivationCrypto](KeyDerivationCrypto) traits so for BTC, ETH and IOP as examples.
74#[derive(Clone, Debug)]
75pub struct Secp256k1;
76
77impl Secp256k1 {
78    fn hash_message<D: AsRef<[u8]>>(data: D) -> secp::Message {
79        let mut hasher = Sha256::default();
80        hasher.update(data.as_ref());
81        let mut hash = [0u8; secp::util::MESSAGE_SIZE];
82        hash.copy_from_slice(hasher.finalize_fixed().as_slice());
83        secp::Message::parse(&hash)
84    }
85}
86
87pub use bip32::*;
88pub use bip44::*;
89pub use cc::{ChainCode, CHAIN_CODE_SIZE};
90pub use ext_pk::SecpExtPublicKey;
91pub use ext_sk::SecpExtPrivateKey;
92pub use id::{SecpKeyId, KEY_ID_SIZE, KEY_ID_VERSION1};
93pub use networks::{ark, btc, hyd, iop};
94pub use pk::{SecpPublicKey, PUBLIC_KEY_SIZE, PUBLIC_KEY_UNCOMPRESSED_SIZE};
95pub use sig::{SecpSignature, SIGNATURE_SIZE, SIGNATURE_VERSION1};
96pub use sk::{SecpPrivateKey, PRIVATE_KEY_SIZE};
97
98impl AsymmetricCrypto for Secp256k1 {
99    type KeyId = SecpKeyId;
100    type PublicKey = SecpPublicKey;
101    type PrivateKey = SecpPrivateKey;
102    type Signature = SecpSignature;
103}
104
105impl KeyDerivationCrypto for Secp256k1 {
106    type ExtendedPrivateKey = SecpExtPrivateKey;
107    type ExtendedPublicKey = SecpExtPublicKey;
108
109    fn master(seed: &Seed) -> SecpExtPrivateKey {
110        SecpExtPrivateKey::from_seed(seed.as_bytes())
111    }
112}
113
114/// Since Wigy could not find any constant expression for the length of `u8` in bytes
115/// (`std::u8::LEN` could be a good place), this is some manual trickery to define our
116/// "standard version byte length in bytes"
117pub const VERSION_SIZE: usize = 1;
118
119/// SLIP-0010 defines keyed hashing for master key derivation. This does domain separation
120/// for different cryptographic algorithms. This is the standard key for BIP-0032
121pub const SLIP10_SEED_HASH_SALT: &[u8] = b"Bitcoin seed";
122
123/// [BIP-0178](https://github.com/bitcoin/bips/blob/master/bip-0178.mediawiki) is an extension
124/// to the de-facto WIF to encode how the private key was used to generate receiving addresses.
125/// If in doubt, just use Compressed, which is compatible with most wallets.
126#[allow(non_camel_case_types)]
127#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
128pub enum Bip178 {
129    /// Addresses generated with uncompressed public keys were mostly used before bitcoin core 0.6
130    /// and are not economical since then. Still, if you have a WIF that does not start with L or K
131    /// on the BTC mainnet, [you have to use uncompressed keys](https://www.reddit.com/r/Electrum/comments/bec22p/potential_loss_of_funds_if_import_uncompressed/).
132    Uncompressed,
133    /// The most common format as of 2019Q2 which only promises that the wallet did not generate the receiving
134    /// addresses using an uncompressed public key
135    Compressed,
136    /// Not that popular format as of 2019Q2, but it promises that the wallet generated only P2PKH
137    /// receiving addresses with this private key
138    P2PKH_Only,
139    /// Not that popular format as of 2019Q2, but it promises that the wallet generated only P2WPKH
140    /// (bech32 segwit native) receiving addresses with this private key
141    P2WPKH,
142    /// Not that popular format as of 2019Q2, but it promises that the wallet generated only P2WPKH_P2SH
143    /// (segwit wrapped in a legacy script hash address) receiving addresses with this private key
144    P2WPKH_P2SH,
145}
146
147impl Bip178 {
148    /// Provides WIF suffix bytes for different usages of a private key
149    pub fn to_wif_suffix(self) -> &'static [u8] {
150        use Bip178::*;
151        match self {
152            Uncompressed => b"",
153            Compressed => b"\x01",
154            P2PKH_Only => b"\x10",
155            P2WPKH => b"\x11",
156            P2WPKH_P2SH => b"\x12",
157        }
158    }
159
160    /// Parses usage type from WIF suffix bytes
161    pub fn from_wif_suffix(data: &[u8]) -> Result<Self> {
162        use Bip178::*;
163        match data {
164            b"" => Ok(Uncompressed),
165            b"\x01" => Ok(Compressed),
166            b"\x10" => Ok(P2PKH_Only),
167            b"\x11" => Ok(P2WPKH),
168            b"\x12" => Ok(P2WPKH_P2SH),
169            _ => Err(anyhow!("Unknown wif suffix {}", hex::encode(data))),
170        }
171    }
172}
173
174#[cfg(test)]
175mod test {
176
177    #[test]
178    fn invalid_private_key() {
179        use super::SecpPrivateKey;
180        let sk_bytes =
181            hex::decode("0000000000000000000000000000000000000000000000000000000000000000")
182                .unwrap();
183        let err = SecpPrivateKey::from_bytes(sk_bytes).err().unwrap();
184        assert!(err.to_string().contains("Invalid secret key"))
185    }
186
187    mod sign_verify {
188        use crate::secp256k1::{SecpPrivateKey, SecpSignature};
189        use crate::{PrivateKey, PublicKey};
190
191        fn test(sk_hex: &str, msg: &[u8], sig_hex: &str) {
192            let sk_bytes = hex::decode(sk_hex).unwrap();
193            let sk = SecpPrivateKey::from_bytes(sk_bytes).unwrap();
194
195            let sig = sk.sign(msg);
196            let sig_bytes = sig.to_bytes();
197            assert_eq!(hex::encode(&sig_bytes), sig_hex);
198
199            let sig2 = SecpSignature::from_bytes(&sig_bytes).unwrap();
200            let pk = sk.public_key();
201            assert!(pk.verify(msg, &sig2));
202        }
203
204        #[test]
205        fn test_1() {
206            test(
207                "0000000000000000000000000000000000000000000000000000000000000001",
208                b"Satoshi Nakamoto",
209                "01934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d82442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5",
210            );
211        }
212    }
213
214    // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vector-2
215    // https://raw.githubusercontent.com/satoshilabs/slips/master/slip-0010/testvectors.py
216    // Also, you can use
217    mod slip10_test_vectors {
218        use crate::{
219            secp256k1::{btc, Network, Secp256k1, SecpExtPrivateKey},
220            ExtendedPrivateKey, KeyDerivationCrypto, Seed,
221        };
222        struct TestDerivation {
223            xprv: SecpExtPrivateKey,
224        }
225
226        impl TestDerivation {
227            fn new(seed_hex: &str) -> Self {
228                let seed_bytes = hex::decode(seed_hex).unwrap();
229                let seed = Seed::from_bytes(&seed_bytes).unwrap();
230                let master = Secp256k1::master(&seed);
231                Self { xprv: master }
232            }
233
234            fn assert_state(&self, xpub_str: &str, xprv_str: &str) {
235                let xpub = self.xprv.neuter();
236
237                assert_eq!(xpub.to_xpub(&btc::Mainnet.bip32_xpub()), xpub_str);
238                assert_eq!(self.xprv.to_xprv(&btc::Mainnet.bip32_xprv()), xprv_str);
239            }
240
241            fn derive_hardened(&mut self, idx: i32) {
242                let xprv = self.xprv.derive_hardened_child(idx).unwrap();
243                self.xprv = xprv;
244            }
245
246            fn derive_normal(&mut self, idx: i32) {
247                let xprv = self.xprv.derive_normal_child(idx).unwrap();
248                self.xprv = xprv;
249            }
250        }
251
252        #[test]
253        fn test_vector_2() {
254            let mut t = TestDerivation::new("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542");
255            t.assert_state("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U");
256            t.derive_normal(0);
257            t.assert_state("xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt");
258            t.derive_hardened(2_147_483_647);
259            t.assert_state("xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9");
260            t.derive_normal(1);
261            t.assert_state("xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef");
262            t.derive_hardened(2_147_483_646);
263            t.assert_state("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc");
264            t.derive_normal(2);
265            t.assert_state("xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j");
266        }
267
268        #[test]
269        fn test_vector_3() {
270            let mut t = TestDerivation::new("4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be");
271            t.assert_state("xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13", "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6");
272            t.derive_hardened(0);
273            t.assert_state("xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y", "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L");
274        }
275    }
276
277    // https://gobittest.appspot.com/Address is pre bitcoin-core 0.6 and uses the uncompressed public key, so
278    // avoid using it now.
279    // In https://gobittest.appspot.com/PrivateKey just ignore the 01 suffix added to WIFs only used
280    // for post-0.6 compressed public keys.
281    // You can check that these tests are valid using the following
282    // ./bitcointool -c pubfrompriv -p <WIF>
283    // ./bitcointool -c addrfrompub -k <pub>
284    mod btc_key_conversions {
285        use crate::secp256k1::{btc, Bip178, Network, SecpPrivateKey};
286        use crate::{PrivateKey, PublicKey};
287
288        fn test(sk_hex: &str, wif: &str, pk_hex: &str, id_hex: &str, address: &str) {
289            let sk_bytes = hex::decode(sk_hex).unwrap();
290            let sk = SecpPrivateKey::from_bytes(sk_bytes).unwrap();
291
292            let sk_wif = sk.to_wif(&btc::Mainnet.wif(), Bip178::Compressed);
293            assert_eq!(sk_wif, wif);
294
295            let pk = sk.public_key();
296            let pk_bytes = pk.to_bytes();
297            assert_eq!(hex::encode(&pk_bytes), pk_hex);
298
299            let id = pk.key_id();
300            let id_bytes = id.to_bytes();
301            assert_eq!(hex::encode(&id_bytes), id_hex);
302
303            let act_address = id.to_p2pkh_addr(&btc::Mainnet.p2pkh_addr());
304            assert_eq!(act_address, address);
305        }
306
307        #[test]
308        fn test_1() {
309            test(
310                "0000000000000000000000000000000000000000000000000000000000000001",
311                "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn",
312                "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
313                "01751e76e8199196d454941c45d1b3a323f1433bd6",
314                "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH",
315            );
316        }
317        #[test]
318        fn test_2() {
319            test(
320                "0000000000000000000000000000000000000000000000000000000000000002",
321                "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74NMTptX4",
322                "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
323                "0106afd46bcdfd22ef94ac122aa11f241244a37ecc",
324                "1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP",
325            );
326        }
327        #[test]
328        fn test_3() {
329            test(
330                "0000000000000000000000000000000000000000000000000000000000000003",
331                "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74sHUHy8S",
332                "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
333                "017dd65592d0ab2fe0d0257d571abf032cd9db93dc",
334                "1CUNEBjYrCn2y1SdiUMohaKUi4wpP326Lb",
335            );
336        }
337        #[test]
338        fn test_4() {
339            test(
340                "aa5e28d6a97a2479a65527f7290311a3624d4cc0fa1578598ee3c2613bf99522",
341                "L2vtCpubwLeqLNYywTUqLLmN6LiijyYWUArxvyw5DyFD8TaxqJyu",
342                "0234f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
343                "01db820065e5bd79e976f0dc09f2257e35243879cf",
344                "1M1eigHFbhtWLnc37qXQt1ao2taLhE49yg",
345            );
346        }
347        #[test]
348        fn test_5() {
349            test(
350                "7e2b897b8cebc6361663ad410835639826d590f393d90a9538881735256dfae3",
351                "L1Sy9ysFzZDXh5gXYrgJmbyhnhbJVyptuTypUnD9ofZoV3V2SpUi",
352                "03d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
353                "015716c6c9146a548ce31092f72ab24b44d8580914",
354                "18wV5EG3Hqocod1RLm9STvbUnSqb1NMo44",
355            );
356        }
357        #[test]
358        fn test_6() {
359            test(
360                "6461e6df0fe7dfd05329f41bf771b86578143d4dd1f7866fb4ca7e97c5fa945d",
361                "Kzaqk53898thvqucDWi4MqC3ogC2s2QmtZL31qjS9MRMvgHFKpDZ",
362                "03e8aecc370aedd953483719a116711963ce201ac3eb21d3f3257bb48668c6a72f",
363                "01e3281990058f008a4b6c658cb735ae2b7327daa5",
364                "1Mi6RjU7ASvudQMZkeobQ1WoiZWAtVhkd6",
365            );
366        }
367    }
368
369    mod blockchain_com_derivation {
370        use crate::secp256k1::*;
371
372        #[test]
373        fn btc_wallet() {
374            let phrase =
375                "hint replace increase neglect egg wood ill alert beef rich install potato";
376            let xpub = "xpub6DVu7eWDWJkczyrDQdo2i99MKdns8idTeVfzwuHCaA3rKfSWaSCnpKigU21vJ2TdCT5MiLgbKWzpxZUx4gFx6zpNWukDzX8yRGU3UKyE9fC";
377            let xpub2 = "xpub6DVu7eWDWJkd4MbGacHiWGFKT2amHnJwQ7SmxWpD2YuacVtaQz7XPsRYxR5PqsGa2TiLYWPi3jsimnruvJ5LvMBxjc96jgZniP7peLHcEdM";
378            let old_addr0 = "17kxMsME7f8CVVWqPadgQNsDEMaHDBpCbv";
379            let bip44_addr0 = "17Y82siUGdY8KEWGpUTW7uF5kj6cEMRYfo";
380            let net = &btc::Mainnet;
381
382            let seed = Bip39::new().short_phrase(phrase).unwrap().password("");
383            let coin = Bip44.network(&seed, net).unwrap();
384            let account: Bip44Account<Secp256k1> = coin.account(0).unwrap();
385
386            assert_eq!(account.neuter().to_xpub(), xpub);
387
388            let old_pk0 = account.node().derive_normal(0).unwrap().neuter();
389
390            assert_eq!(old_pk0.to_p2pkh_addr(net), old_addr0);
391
392            let pk0 = account.key(0).unwrap().neuter();
393
394            assert_eq!(pk0.to_p2pkh_addr(), bip44_addr0);
395
396            let account2 = coin.account(1).unwrap();
397
398            assert_eq!(account2.neuter().to_xpub(), xpub2);
399        }
400    }
401
402    mod coinomi_derivation {
403        use crate::{
404            secp256k1::{btc, hyd},
405            *,
406        };
407
408        #[test]
409        fn hyd_derive() {
410            let mnemonic = "blast cargo razor option vote shoe stock cruel mansion boy spot never album crop reflect kangaroo blouse slam empty shoot cable vital crane manual";
411            let pk0_hex = "02f946d10106f55c755c1f836b63bef35fb0015603e1870c8dbdcacf62f178587e";
412            let addr0 = "hWNN8ymcsLdJivbwbBaPS8X1vekxB2pdwV";
413
414            let seed = Bip39::new().phrase(mnemonic).unwrap().password(Seed::PASSWORD);
415            let account = Bip44.network(&seed, &hyd::Mainnet).unwrap().account(0).unwrap();
416            let key0 = account.key(0).unwrap().neuter();
417
418            assert_eq!(hex::encode(key0.to_public_key().to_bytes()), pk0_hex);
419            assert_eq!(key0.to_p2pkh_addr(), addr0);
420        }
421
422        #[test]
423        fn tbtc_derive() {
424            let mnemonic = "blast cargo razor option vote shoe stock cruel mansion boy spot never album crop reflect kangaroo blouse slam empty shoot cable vital crane manual";
425            let xpub = "tpubDDfA4LQYyG71KmPW65gktxjvzxFAFcdhDAzj4zc6y5hpeX3rZu3nPh1GuvgWCyj4VWKfuFbnnCvFXyTuDLD6mmFA5yVTe2UUcSoNy7kgcYm";
426            let xpub_coinomi_bug = "xpub6DHXY6asFz9Z3yCedthfh3QZfq9WGE8dfYQRiSYxdAwvuEP9VgChFFLSftbigUmphbnmHg5vF76CqnyHpUMrtKns2Nk9xVpF2VCyD7Uej6C";
427            let addr0 = "mgH9VjC6uGTt1cWDDZEXisASAtCE8D6Xar";
428            let net = &btc::Testnet;
429
430            let seed = Bip39::new().phrase(mnemonic).unwrap().password(Seed::PASSWORD);
431            let account = Bip44.network(&seed, net).unwrap().account(0).unwrap().neuter();
432
433            assert_eq!(account.to_xpub(), xpub);
434            assert_eq!(account.node().to_xpub(&btc::Mainnet), xpub_coinomi_bug);
435
436            let key0 = account.key(0).unwrap();
437
438            assert_eq!(key0.to_p2pkh_addr(), addr0);
439        }
440    }
441
442    mod ark_desktop_hyd_address {
443        use super::super::*;
444
445        #[test]
446        fn test() {
447            let phrase = "boss slice draft close detail mix nation casino judge cigar melody catch";
448            let pk_hex = "03fdd041ed3e51d8909c44ef6b9d1268a412161d8e6544c5cd4d87ef78bb49e2f7";
449            let addr = "hFxvDfqQfXHzhvEebTJGkds8DBBGBCKpY9";
450            let addr_dev = "dEatNarXZifEXaqnMFy5uP9Fv8bq7aB3Vn";
451            let addr_test = "tXRoniBUYaGXc495HCdCL9V9qJPgBMywaH";
452
453            let sk = SecpPrivateKey::from_ark_passphrase(phrase).unwrap();
454            let pk = sk.public_key();
455
456            assert_eq!(hex::encode(pk.to_bytes()), pk_hex);
457
458            let key_id = pk.ark_key_id();
459
460            assert_eq!(key_id.to_p2pkh_addr(&hyd::Mainnet.p2pkh_addr()), addr);
461            assert_eq!(key_id.to_p2pkh_addr(&hyd::Devnet.p2pkh_addr()), addr_dev);
462            assert_eq!(key_id.to_p2pkh_addr(&hyd::Testnet.p2pkh_addr()), addr_test);
463        }
464    }
465
466    // ARK is special 😉 These tests are cross-checked with their JavaScript crpyto implementation and use private keys
467    // of their testnet genesis delegates.
468    mod ark_key_conversions {
469        use super::super::*;
470
471        fn test(passphrase: &str, pk_hex: &str, main_addr: &str, dev_addr: &str) {
472            let sk = SecpPrivateKey::from_ark_passphrase(passphrase).unwrap();
473            let pk = sk.public_key();
474            assert_eq!(hex::encode(pk.to_bytes()), pk_hex);
475
476            let key_id = pk.ark_key_id();
477
478            assert_eq!(key_id.to_p2pkh_addr(&ark::Mainnet.p2pkh_addr()), main_addr);
479            assert_eq!(key_id.to_p2pkh_addr(&ark::Devnet.p2pkh_addr()), dev_addr);
480        }
481
482        #[test]
483        fn test_delegate_1() {
484            test(
485                "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire",
486                "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37",
487                "ANBkoGqWeTSiaEVgVzSKZd3jS7UWzv9PSo",
488                "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x",
489            );
490        }
491
492        #[test]
493        fn test_delegate_2() {
494            test(
495                "venue below waste gather spin cruise title still boost mother flash tuna",
496                "02def27da9336e7fbf63131b8d7e5c9f45b296235db035f1f4242c507398f0f21d",
497                "AbfQq8iRSf9TFQRzQWo33dHYU7HFMS17Zd",
498                "DR2ditoSQvPaySQbaT8GSWC3se5rLyfq4T",
499            );
500        }
501    }
502}