1use std::{fmt, num::NonZeroU32, str::FromStr};
2
3use anyhow::{Context, bail, ensure};
4use bitcoin::{
5 bip32::{self, ChildNumber},
6 secp256k1,
7};
8use lexe_crypto::{
9 aes::{self, AesMasterKey},
10 ed25519, password,
11 rng::{Crng, RngExt},
12};
13use lexe_hex::hex;
14use lexe_std::array;
15use secrecy::{ExposeSecret, Secret, SecretVec, Zeroize};
16use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
17
18use crate::{
19 api::user::{NodePk, UserPk},
20 ln::network::Network,
21 secp256k1_ctx::SECP256K1,
22};
23
24pub struct RootSeed(Secret<[u8; Self::LENGTH]>);
32
33impl RootSeed {
34 pub const LENGTH: usize = 32;
35
36 const HKDF_MAX_OUT_LEN: usize = 8160 ;
39
40 const HKDF_SALT: [u8; 32] = array::pad(*b"LEXE-REALM::RootSeed");
42
43 const BIP39_MNEMONIC_BUF_SIZE: usize = 216;
46
47 pub fn new(bytes: Secret<[u8; Self::LENGTH]>) -> Self {
48 Self(bytes)
49 }
50
51 #[cfg(any(test, feature = "test-utils"))]
53 pub fn from_u64(v: u64) -> Self {
54 let mut seed = [0u8; 32];
55 seed[0..8].copy_from_slice(&v.to_le_bytes());
56 Self::new(Secret::new(seed))
57 }
58
59 pub fn from_rng<R: Crng>(rng: &mut R) -> Self {
60 Self(Secret::new(rng.gen_bytes()))
61 }
62
63 pub fn to_mnemonic(&self) -> bip39::Mnemonic {
69 bip39::Mnemonic::from_entropy_in(
70 bip39::Language::English,
71 self.0.expose_secret().as_slice(),
72 )
73 .expect("Always succeeds for 256 bits")
74 }
75
76 pub fn derive_bip39_seed(&self) -> Secret<[u8; 64]> {
90 let mnemonic = self.to_mnemonic();
92
93 let mut buf = [0u8; Self::BIP39_MNEMONIC_BUF_SIZE];
96 let mut len = 0;
97 for (i, word) in mnemonic.words().enumerate() {
98 if i > 0 {
99 buf[len] = b' ';
100 len += 1;
101 }
102 let word_bytes = word.as_bytes();
103 buf[len..len + word_bytes.len()].copy_from_slice(word_bytes);
104 len += word_bytes.len();
105 }
106 let mnemonic_bytes = &buf[..len];
107
108 let salt = b"mnemonic";
110
111 let mut seed = [0u8; 64];
113 ring::pbkdf2::derive(
114 ring::pbkdf2::PBKDF2_HMAC_SHA512,
115 const { NonZeroU32::new(2048).unwrap() },
116 salt,
117 mnemonic_bytes,
118 &mut seed,
119 );
120
121 buf.zeroize();
123
124 Secret::new(seed)
125 }
126
127 fn extract(&self) -> ring::hkdf::Prk {
130 let salted_hkdf = ring::hkdf::Salt::new(
131 ring::hkdf::HKDF_SHA256,
132 Self::HKDF_SALT.as_slice(),
133 );
134 salted_hkdf.extract(self.0.expose_secret().as_slice())
135 }
136
137 pub fn derive_to_slice(&self, label: &[&[u8]], out: &mut [u8]) {
139 struct OkmLength(usize);
140
141 impl ring::hkdf::KeyType for OkmLength {
142 fn len(&self) -> usize {
143 self.0
144 }
145 }
146
147 assert!(out.len() <= Self::HKDF_MAX_OUT_LEN);
148
149 self.extract()
150 .expand(label, OkmLength(out.len()))
151 .expect("should not fail")
152 .fill(out)
153 .expect("should not fail")
154 }
155
156 pub fn derive(&self, label: &[&[u8]]) -> Secret<[u8; 32]> {
158 let mut out = [0u8; 32];
159 self.derive_to_slice(label, &mut out);
160 Secret::new(out)
161 }
162
163 pub fn derive_vec(&self, label: &[&[u8]], out_len: usize) -> SecretVec<u8> {
166 let mut out = vec![0u8; out_len];
167 self.derive_to_slice(label, &mut out);
168 SecretVec::new(out)
169 }
170
171 pub fn derive_ephemeral_issuing_ca_key_pair(&self) -> ed25519::KeyPair {
174 let seed = self.derive(&[b"shared seed tls ca key pair"]);
178 ed25519::KeyPair::from_seed(seed.expose_secret())
179 }
180
181 pub fn derive_revocable_issuing_ca_key_pair(&self) -> ed25519::KeyPair {
184 let seed = self.derive(&[b"revocable issuing ca key pair"]);
185 ed25519::KeyPair::from_seed(seed.expose_secret())
186 }
187
188 pub fn derive_user_key_pair(&self) -> ed25519::KeyPair {
194 let seed = self.derive(&[b"user key pair"]);
195 ed25519::KeyPair::from_seed(seed.expose_secret())
196 }
197
198 pub fn derive_user_pk(&self) -> UserPk {
200 UserPk::new(self.derive_user_key_pair().public_key().into_inner())
201 }
202
203 pub fn derive_bip32_master_xprv(&self, network: Network) -> bip32::Xpriv {
212 let bip39_seed = self.derive_bip39_seed();
213 bip32::Xpriv::new_master(
214 network.to_bitcoin(),
215 bip39_seed.expose_secret(),
216 )
217 .expect("Should never fail")
218 }
219
220 pub fn derive_legacy_master_xprv(&self, network: Network) -> bip32::Xpriv {
232 bip32::Xpriv::new_master(network.to_bitcoin(), self.0.expose_secret())
233 .expect("Should never fail")
234 }
235
236 pub fn derive_ldk_seed(&self) -> Secret<[u8; 32]> {
239 let master_xprv = self.derive_legacy_master_xprv(Network::Mainnet);
242
243 let m_535h =
245 ChildNumber::from_hardened_idx(535).expect("Is within [0, 2^31-1]");
246 let ldk_xprv = master_xprv
247 .derive_priv(&SECP256K1, &m_535h)
248 .expect("Should always succeed");
249
250 Secret::new(ldk_xprv.private_key.secret_bytes())
251 }
252
253 pub fn derive_node_key_pair(&self) -> secp256k1::Keypair {
256 let ldk_seed = self.derive_ldk_seed();
258
259 let ldk_xprv = bip32::Xpriv::new_master(
262 bitcoin::Network::Bitcoin,
263 ldk_seed.expose_secret(),
264 )
265 .expect("should never fail; the sizes match up");
266
267 let m_0h = ChildNumber::from_hardened_idx(0)
268 .expect("should never fail; index is in range");
269 let node_sk = ldk_xprv
270 .derive_priv(&SECP256K1, &m_0h)
271 .expect("should never fail")
272 .private_key;
273
274 secp256k1::Keypair::from_secret_key(&SECP256K1, &node_sk)
275 }
276
277 pub fn derive_node_pk(&self) -> NodePk {
279 NodePk(self.derive_node_key_pair().public_key())
280 }
281
282 #[cfg(any(test, feature = "test-utils"))]
293 pub fn derive_receive_auth_key(&self) -> [u8; 32] {
294 let ldk_seed = self.derive_ldk_seed();
296
297 let ldk_xprv = bip32::Xpriv::new_master(
300 bitcoin::Network::Bitcoin,
301 ldk_seed.expose_secret(),
302 )
303 .expect("should never fail; the sizes match up");
304
305 let m_7h = ChildNumber::from_hardened_idx(7)
306 .expect("should never fail; index is in range");
307 let sk = ldk_xprv
308 .derive_priv(&SECP256K1, &m_7h)
309 .expect("should never fail")
310 .private_key;
311
312 sk.secret_bytes()
313 }
314
315 pub fn derive_vfs_master_key(&self) -> AesMasterKey {
316 let secret = self.derive(&[b"vfs master key"]);
317 AesMasterKey::new(secret.expose_secret())
318 }
319
320 #[cfg(any(test, feature = "test-utils"))]
321 pub fn as_bytes(&self) -> &[u8] {
322 self.0.expose_secret().as_slice()
323 }
324
325 pub fn password_encrypt(
338 &self,
339 rng: &mut impl Crng,
340 password: &str,
341 ) -> anyhow::Result<Vec<u8>> {
342 let salt = rng.gen_bytes();
344
345 let mut aes_ciphertext =
347 password::encrypt(rng, password, &salt, self.0.expose_secret())
348 .context("Password encryption failed")?;
349
350 let mut combined = Vec::from(salt);
352 combined.append(&mut aes_ciphertext);
353
354 let expected_combined_len = 32 + aes::encrypted_len(32);
357 assert!(combined.len() == expected_combined_len);
358
359 Ok(combined)
360 }
361
362 pub fn password_decrypt(
367 password: &str,
368 mut combined: Vec<u8>,
369 ) -> anyhow::Result<Self> {
370 let expected_combined_len = 32 + aes::encrypted_len(32);
372 ensure!(
373 combined.len() == expected_combined_len,
374 "Combined bytes had the wrong length"
375 );
376
377 let aes_ciphertext = combined.split_off(32);
379 let unsized_salt = combined.into_boxed_slice();
380 let salt = Box::<[u8; 32]>::try_from(unsized_salt)
381 .expect("We split off at 32, so there are exactly 32 bytes");
382
383 let root_seed_bytes =
385 password::decrypt(password, &salt, aes_ciphertext)
386 .map(Secret::new)
387 .context("Password decryption failed")?;
388
389 Self::try_from(root_seed_bytes.expose_secret().as_slice())
391 }
392}
393
394impl ExposeSecret<[u8; Self::LENGTH]> for RootSeed {
395 fn expose_secret(&self) -> &[u8; Self::LENGTH] {
396 self.0.expose_secret()
397 }
398}
399
400impl FromStr for RootSeed {
401 type Err = hex::DecodeError;
402
403 fn from_str(hex: &str) -> Result<Self, Self::Err> {
404 let mut bytes = [0u8; Self::LENGTH];
405 hex::decode_to_slice(hex, bytes.as_mut_slice())
406 .map(|()| Self::new(Secret::new(bytes)))
407 }
408}
409
410impl fmt::Debug for RootSeed {
411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412 f.write_str("RootSeed(..)")
414 }
415}
416
417impl TryFrom<&[u8]> for RootSeed {
418 type Error = anyhow::Error;
419
420 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
421 if bytes.len() != Self::LENGTH {
422 bail!("input must be {} bytes", Self::LENGTH);
423 }
424 let mut out = [0u8; Self::LENGTH];
425 out[..].copy_from_slice(bytes);
426 Ok(Self::new(Secret::new(out)))
427 }
428}
429
430impl TryFrom<bip39::Mnemonic> for RootSeed {
431 type Error = anyhow::Error;
432
433 fn try_from(mnemonic: bip39::Mnemonic) -> Result<Self, Self::Error> {
434 use lexe_std::array::ArrayExt;
435
436 let (entropy, entropy_len) = mnemonic.to_entropy_array();
438 let entropy = secrecy::zeroize::Zeroizing::new(entropy);
439
440 ensure!(entropy_len == 32, "Should contain exactly 32 bytes");
441
442 let (seed_buf, _remainder) = entropy.split_array_ref_stable::<32>();
443
444 Ok(Self(Secret::new(*seed_buf)))
445 }
446}
447
448struct RootSeedVisitor;
449
450impl de::Visitor<'_> for RootSeedVisitor {
451 type Value = RootSeed;
452
453 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
454 f.write_str("hex-encoded RootSeed or raw bytes")
455 }
456
457 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
458 where
459 E: de::Error,
460 {
461 RootSeed::from_str(v).map_err(serde::de::Error::custom)
462 }
463
464 fn visit_bytes<E>(self, b: &[u8]) -> Result<Self::Value, E>
465 where
466 E: de::Error,
467 {
468 RootSeed::try_from(b).map_err(de::Error::custom)
469 }
470}
471
472impl<'de> Deserialize<'de> for RootSeed {
473 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
474 where
475 D: Deserializer<'de>,
476 {
477 if deserializer.is_human_readable() {
478 deserializer.deserialize_str(RootSeedVisitor)
479 } else {
480 deserializer.deserialize_bytes(RootSeedVisitor)
481 }
482 }
483}
484
485impl Serialize for RootSeed {
486 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
487 where
488 S: Serializer,
489 {
490 if serializer.is_human_readable() {
491 let hex_str = hex::encode(self.0.expose_secret());
492 serializer.serialize_str(&hex_str)
493 } else {
494 serializer.serialize_bytes(self.0.expose_secret())
495 }
496 }
497}
498
499#[cfg(any(test, feature = "test-utils"))]
500mod test_impls {
501 use proptest::{
502 arbitrary::{Arbitrary, any},
503 strategy::{BoxedStrategy, Strategy},
504 };
505
506 use super::*;
507
508 impl Arbitrary for RootSeed {
509 type Strategy = BoxedStrategy<Self>;
510 type Parameters = ();
511
512 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
513 any::<[u8; 32]>()
514 .prop_map(|buf| Self::new(Secret::new(buf)))
515 .no_shrink()
516 .boxed()
517 }
518 }
519
520 impl PartialEq for RootSeed {
523 fn eq(&self, other: &Self) -> bool {
524 self.expose_secret() == other.expose_secret()
525 }
526 }
527}
528
529#[cfg(test)]
530mod test {
531 use std::path::Path;
532
533 use bitcoin::NetworkKind;
534 use lexe_crypto::rng::FastRng;
535 use lexe_sha256::sha256;
536 use proptest::{
537 arbitrary::any, collection::vec, prop_assert_eq, proptest,
538 strategy::Strategy, test_runner::Config,
539 };
540
541 use super::*;
542 use crate::ln::network::Network;
543
544 fn hmac_sha256(key: &[u8], msg: &[u8]) -> sha256::Hash {
548 let h_key = sha256::digest(key);
549 let mut zero_pad_key = [0u8; 64];
550
551 let key = match key.len() {
553 len if len > 64 => h_key.as_ref(),
554 _ => key,
555 };
556 zero_pad_key[..key.len()].copy_from_slice(key);
557 let key = zero_pad_key.as_slice();
558 assert_eq!(key.len(), 64);
559
560 let mut o_key = [0u8; 64];
562 for (o_key_i, key_i) in o_key.iter_mut().zip(key) {
563 *o_key_i = key_i ^ 0x5c;
564 }
565
566 let mut i_key = [0u8; 64];
568 for (i_key_i, key_i) in i_key.iter_mut().zip(key) {
569 *i_key_i = key_i ^ 0x36;
570 }
571
572 let h_i = sha256::digest_many(&[&i_key, msg]);
574
575 sha256::digest_many(&[&o_key, h_i.as_ref()])
577 }
578
579 fn hkdf_sha256(
581 ikm: &[u8],
582 salt: &[u8],
583 info: &[&[u8]],
584 out_len: usize,
585 ) -> Vec<u8> {
586 let prk = hmac_sha256(salt, ikm);
587
588 let n = (out_len.saturating_sub(1) / 32) + 1;
591 let n = u8::try_from(n).expect("out_len too large");
592
593 let mut t_i = [0u8; 32];
598 let mut out = Vec::new();
599
600 for i in 1..=n {
601 let mut m_i = if i == 1 { Vec::new() } else { t_i.to_vec() };
603 for info_part in info {
604 m_i.extend_from_slice(info_part);
605 }
606 m_i.extend_from_slice(&[i]);
607
608 let h_i = hmac_sha256(prk.as_ref(), &m_i);
609 t_i.copy_from_slice(h_i.as_ref());
610
611 if i < n {
612 out.extend_from_slice(&t_i[..]);
613 } else {
614 let l = 32 - (((n as usize) * 32) - out_len);
615 out.extend_from_slice(&t_i[..l]);
616 }
617 }
618
619 out
620 }
621
622 #[ignore]
626 #[test]
627 fn dump_root_seed() {
628 let root_seed = RootSeed::from_u64(20240506);
629 let root_seed_hex = hex::encode(root_seed.expose_secret());
630 let user_pk = root_seed.derive_user_pk();
631 let node_pk = root_seed.derive_node_pk();
632
633 println!(
634 "root_seed: '{root_seed_hex}', \
635 user_pk: '{user_pk}', node_pk: '{node_pk}'"
636 );
637 }
638
639 #[test]
640 fn test_root_seed_serde() {
641 let input =
642 "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069";
643 let input_json = format!("\"{input}\"");
644 let seed_bytes = hex::decode(input).unwrap();
645
646 let seed = RootSeed::from_str(input).unwrap();
647 assert_eq!(seed.as_bytes(), &seed_bytes);
648
649 let seed2: RootSeed = serde_json::from_str(&input_json).unwrap();
650 assert_eq!(seed2.as_bytes(), &seed_bytes);
651
652 #[derive(Deserialize)]
653 struct Foo {
654 x: u32,
655 seed: RootSeed,
656 y: String,
657 }
658
659 let foo_json = format!(
660 "{{\n\
661 \"x\": 123,\n\
662 \"seed\": \"{input}\",\n\
663 \"y\": \"asdf\"\n\
664 }}"
665 );
666
667 let foo2: Foo = serde_json::from_str(&foo_json).unwrap();
668 assert_eq!(foo2.x, 123);
669 assert_eq!(foo2.seed.as_bytes(), &seed_bytes);
670 assert_eq!(foo2.y, "asdf");
671 }
672
673 #[test]
674 fn test_root_seed_derive() {
675 let seed = RootSeed::from_u64(0x42);
676
677 let out8 = seed.derive_vec(&[b"very cool secret"], 8);
678 let out16 = seed.derive_vec(&[b"very cool secret"], 16);
679 let out32 = seed.derive_vec(&[b"very cool secret"], 32);
680 let out32_2 = seed.derive(&[b"very cool secret"]);
681
682 assert_eq!("c724f46ae4c48017", hex::encode(out8.expose_secret()));
683 assert_eq!(
684 "c724f46ae4c480172a75cf775dbb64b1",
685 hex::encode(out16.expose_secret())
686 );
687 assert_eq!(
688 "c724f46ae4c480172a75cf775dbb64b160beb74137eb7d0cef72fde0523674de",
689 hex::encode(out32.expose_secret())
690 );
691 assert_eq!(out32.expose_secret(), out32_2.expose_secret());
692 }
693
694 #[test]
696 fn test_root_seed_derive_equiv() {
697 let arb_seed = any::<RootSeed>();
698 let arb_label = vec(vec(any::<u8>(), 0..=64), 0..=4);
699 let arb_len = 0_usize..=1024;
700
701 proptest!(|(seed in arb_seed, label in arb_label, len in arb_len)| {
702 let label = label
703 .iter()
704 .map(|x| x.as_slice())
705 .collect::<Vec<_>>();
706
707 let expected = hkdf_sha256(
708 seed.as_bytes(),
709 RootSeed::HKDF_SALT.as_slice(),
710 &label,
711 len,
712 );
713
714 let actual = seed.derive_vec(&label, len);
715
716 assert_eq!(&expected, actual.expose_secret());
717 });
718 }
719
720 #[test]
725 fn when_does_network_matter() {
726 proptest!(|(
727 root_seed in any::<RootSeed>(),
728 network1 in any::<Network>(),
729 network2 in any::<Network>(),
730 )| {
731 let network_kind1 = NetworkKind::from(network1.to_bitcoin());
732 let network_kind2 = NetworkKind::from(network2.to_bitcoin());
733
734 let master_xprv1 = root_seed.derive_legacy_master_xprv(network1);
738 let master_xprv2 = root_seed.derive_legacy_master_xprv(network2);
739 let master_xprvs_equal = master_xprv1 == master_xprv2;
741 let network_kinds_equal = network_kind1 == network_kind2;
742 prop_assert_eq!(master_xprvs_equal, network_kinds_equal);
743
744 let m_535h = ChildNumber::from_hardened_idx(535)
747 .expect("Is within [0, 2^31-1]");
748 let ldk_seed1 = master_xprv1
749 .derive_priv(&SECP256K1, &m_535h)
750 .expect("Should always succeed")
751 .private_key
752 .secret_bytes();
753 let ldk_seed2 = master_xprv2
754 .derive_priv(&SECP256K1, &m_535h)
755 .expect("Should always succeed")
756 .private_key
757 .secret_bytes();
758 prop_assert_eq!(ldk_seed1, ldk_seed2);
759 let ldk_seed = ldk_seed1;
760
761 let ldk_xprv1 = bip32::Xpriv::new_master(network1.to_bitcoin(), &ldk_seed)
765 .expect("Should never fail");
766 let ldk_xprv2 = bip32::Xpriv::new_master(network2.to_bitcoin(), &ldk_seed)
767 .expect("Should never fail");
768 let ldk_xprvs_equal = ldk_xprv1 == ldk_xprv2;
770 prop_assert_eq!(ldk_xprvs_equal, network_kinds_equal);
771 let m_0h = ChildNumber::from_hardened_idx(0)
773 .expect("should never fail; index is in range");
774 let node_sk1 = ldk_xprv1
775 .derive_priv(&SECP256K1, &m_0h)
776 .expect("should never fail")
777 .private_key;
778 let node_sk2 = ldk_xprv2
779 .derive_priv(&SECP256K1, &m_0h)
780 .expect("should never fail")
781 .private_key;
782 prop_assert_eq!(node_sk1, node_sk2);
783 let keypair1 =
785 secp256k1::Keypair::from_secret_key(&SECP256K1, &node_sk1);
786 let keypair2 =
787 secp256k1::Keypair::from_secret_key(&SECP256K1, &node_sk2);
788 prop_assert_eq!(keypair1, keypair2);
789 let node_pk1 = NodePk(secp256k1::PublicKey::from(keypair1));
791 let node_pk2 = NodePk(secp256k1::PublicKey::from(keypair2));
792 prop_assert_eq!(node_pk1, node_pk2);
793 let node_pk1_str = node_pk1.to_string();
795 let node_pk2_str = node_pk2.to_string();
796 prop_assert_eq!(node_pk1_str, node_pk2_str);
797 });
798 }
799
800 #[test]
801 fn password_encryption_roundtrip() {
802 use password::{MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH};
803
804 let password_length_range = MIN_PASSWORD_LENGTH..MAX_PASSWORD_LENGTH;
805 let any_valid_password =
806 proptest::collection::vec(any::<char>(), password_length_range)
807 .prop_map(String::from_iter);
808
809 let config = Config::with_cases(4);
811 proptest!(config, |(
812 mut rng in any::<FastRng>(),
813 password in any_valid_password,
814 )| {
815 let root_seed1 = RootSeed::from_rng(&mut rng);
816 let encrypted = root_seed1.password_encrypt(&mut rng, &password)
817 .unwrap();
818 let root_seed2 = RootSeed::password_decrypt(&password, encrypted)
819 .unwrap();
820 assert_eq!(root_seed1, root_seed2);
821 })
822 }
823
824 #[test]
825 fn password_decryption_compatibility() {
826 let root_seed1 = RootSeed::new(Secret::new([69u8; 32]));
827 let password1 = "password1234";
828 let encrypted = hex::decode("adcfc4aef26858bacfae83dd19e735bb145203ab18183cbe932cd742b4446e7300b561678b0652666b316288bbb57552c4f40e91d8e440fd1085cba610204ca982f52fce471de27fe360e9560cee0996e55ce7ac323201908b7ff261b8ff425a87d215e83870e45062d988627c8cb7216b").unwrap();
836 let root_seed1_decrypted =
837 RootSeed::password_decrypt(password1, encrypted).unwrap();
838 assert_eq!(root_seed1, root_seed1_decrypted);
839
840 let root_seed2 = RootSeed::new(Secret::new([0u8; 32]));
841 let password2 = " ";
842 let encrypted = hex::decode("adcfc4aef26858bacfae83dd19e735bb145203ab18183cbe932cd742b4446e7300b561678b0652666b316288bbb57552c4f40e91d8e440fd1085cba610204ca982062fbcb21c14cdb9d107f2f359e0f272e473d2cdb71a870d8fb19d1169c160876ee1ccde4f73a8f2b4ebc9bed68f6139").unwrap();
850 let root_seed2_decrypted =
851 RootSeed::password_decrypt(password2, encrypted).unwrap();
852 assert_eq!(root_seed2, root_seed2_decrypted);
853 }
854
855 #[test]
856 fn root_seed_mnemonic_round_trip() {
857 proptest!(|(root_seed1 in any::<RootSeed>())| {
858 let mnemonic = root_seed1.to_mnemonic();
859
860 prop_assert_eq!(mnemonic.word_count(), 24);
862
863 let root_seed2 = RootSeed::try_from(mnemonic).unwrap();
864 prop_assert_eq!(
865 root_seed1.expose_secret(), root_seed2.expose_secret()
866 );
867 });
868 }
869
870 #[test]
872 fn mnemonic_fromstr_display_roundtrip() {
873 proptest!(|(root_seed in any::<RootSeed>())| {
874 let mnemonic1 = root_seed.to_mnemonic();
875 let mnemonic2 = bip39::Mnemonic::from_str(&mnemonic1.to_string()).unwrap();
876 prop_assert_eq!(mnemonic1, mnemonic2)
877 })
878 }
879
880 #[test]
885 fn mnemonic_compatibility_test() {
886 let seed1 = RootSeed::new(Secret::new(hex::decode_const(
906 b"91f24ce8326abc2e9faef6a3b866021ce9574c11210e86b0f457a31ed8ad4cba",
907 )));
908 let seed2 = RootSeed::new(Secret::new(hex::decode_const(
909 b"5c2aa5fdd678112c8b13d745b5c1d1e1a81ace76721ec72f1424bd2eb387a8af",
910 )));
911 let seed3 = RootSeed::new(Secret::new(hex::decode_const(
912 b"51ddba4775fc71fb1dba65dfc2ffab7526dd61bae7a9b13e9f3aa550bee19360",
913 )));
914
915 let str1 = String::from(
917 "music mystery deliver gospel profit blanket leaf tell \
918 photo segment letter degree nice plastic duty canyon \
919 mammal marble bicycle economy unique find cream dune",
920 );
921 let str2 = String::from(
922 "found festival legal provide library north clump kit \
923 east puppy inner select like grunt supply duck \
924 shrimp judge ankle kid twenty sense pencil tray",
925 );
926 let str3 = String::from(
927 "fade universe mushroom typical shove work ivory erosion \
928 thank blood turn tumble horse radio twist vivid \
929 raise visual solid enjoy armor ignore eternal arrange",
930 );
931
932 let mnemonic_from_str1 = bip39::Mnemonic::from_str(&str1).unwrap();
934 let mnemonic_from_str2 = bip39::Mnemonic::from_str(&str2).unwrap();
935 let mnemonic_from_str3 = bip39::Mnemonic::from_str(&str3).unwrap();
936 assert_eq!(seed1.to_mnemonic(), mnemonic_from_str1);
937 assert_eq!(seed2.to_mnemonic(), mnemonic_from_str2);
938 assert_eq!(seed3.to_mnemonic(), mnemonic_from_str3);
939
940 let seed_from_str1 =
942 RootSeed::try_from(mnemonic_from_str1.clone()).unwrap();
943 let seed_from_str2 =
944 RootSeed::try_from(mnemonic_from_str2.clone()).unwrap();
945 let seed_from_str3 =
946 RootSeed::try_from(mnemonic_from_str3.clone()).unwrap();
947 assert_eq!(seed1.as_bytes(), seed_from_str1.as_bytes());
948 assert_eq!(seed2.as_bytes(), seed_from_str2.as_bytes());
949 assert_eq!(seed3.as_bytes(), seed_from_str3.as_bytes());
950
951 assert_eq!(str1, seed1.to_mnemonic().to_string());
953 assert_eq!(str2, seed2.to_mnemonic().to_string());
954 assert_eq!(str3, seed3.to_mnemonic().to_string());
955 }
956
957 #[test]
960 fn derive_snapshots() {
961 let seed = RootSeed::from_u64(20240506);
962
963 let user_pk = seed.derive_user_pk();
965 assert_eq!(
966 user_pk.to_string(),
967 "a9edf9596ddf589918beca32d148a7d0ba59273b419ccf63a910f1b75861ff06",
968 );
969
970 let node_pk = seed.derive_node_pk();
972 assert_eq!(
973 node_pk.to_string(),
974 "035a70d45eec7efb270319f116a9684250acb4ef282a26d21874878e7c5088f73b",
975 );
976
977 let ldk_seed = seed.derive_ldk_seed();
979 assert_eq!(
980 hex::encode(ldk_seed.expose_secret()),
981 "551444699ae8acbebe67d5b54da844e8297b83e26e205203a65f29564eaf3787",
982 );
983
984 let bip39_seed = seed.derive_bip39_seed();
986 assert_eq!(
987 hex::encode(bip39_seed.expose_secret()),
988 "30dc1cca6811e6f52a6efba751db4fe9495883b778c72b28ee248f0076cf03b9\
989 dc3c3d7d662c98806ce59c0e59911a249533ca0c82dea3780cdf040f9a3dfe09",
990 );
991
992 let bip39_master_xpriv =
994 seed.derive_bip32_master_xprv(Network::Mainnet);
995 assert_eq!(
996 bip39_master_xpriv.to_string(),
997 "xprv9s21ZrQH143K3BwTSDGEpsQA99b5fmckcX2s4dBbxojs287ApWXGThVTu9\
998 TmogYG8A1JiUnbD6gHSfw5hXsTduny878ygutaCaCvg1KTvgM",
999 );
1000
1001 let bip39_testnet_xpriv =
1003 seed.derive_bip32_master_xprv(Network::Testnet3);
1004 assert_eq!(
1005 bip39_testnet_xpriv.to_string(),
1006 "tprv8ZgxMBicQKsPe1Az6n7jzX29TH1HuHekx4wyw3c4SnELoirFoss1ySrupK\
1007 dRp3vaVbY5iaQMNTG5uXUppkDQSy4ZekMHMGcd7fxM7h7WWqo"
1008 );
1009
1010 let master_xpriv = seed.derive_legacy_master_xprv(Network::Mainnet);
1012 assert_eq!(
1013 master_xpriv.to_string(),
1014 "xprv9s21ZrQH143K42JPXVa2Q7nAp6XB3FVwyYdGkQetMYRcprZXKvt52p1tqg\
1015 9fwyFJaL6Ki92bCdRNDPAnyddy7CzpQAEktM8nMtNGw4Xj6vt",
1016 );
1017
1018 let master_xpriv_testnet =
1020 seed.derive_legacy_master_xprv(Network::Testnet3);
1021 assert_eq!(
1022 master_xpriv_testnet.to_string(),
1023 "tprv8ZgxMBicQKsPeqXvC4RXZmQA8DwPGmXxK6YPcq5LqWv6cTJcKJDpYZPLk\
1024 rKKxLdcwmd6iEeMMz1AgEiY6qyuvGGQvoT4YhrqGz7hNoR5R4G",
1025 );
1026
1027 let ephemeral_ca = seed.derive_ephemeral_issuing_ca_key_pair();
1029 assert_eq!(
1030 ephemeral_ca.public_key().to_string(),
1031 "70656b5a6084c457bf004dad264cecc131879b7e6791fe0cc828c38cc0df6e92",
1032 );
1033
1034 let revocable_ca = seed.derive_revocable_issuing_ca_key_pair();
1036 assert_eq!(
1037 revocable_ca.public_key().to_string(),
1038 "efe6e020ba9ca4a50467cdbaff469f9d465f21d1c6fe976868a20d97bbaa2ee3",
1039 );
1040
1041 let vfs_ctxt = seed.derive_vfs_master_key().encrypt(
1043 &mut FastRng::from_u64(1234),
1044 &[],
1045 None,
1046 &|out: &mut Vec<u8>| out.extend_from_slice(b"test"),
1047 );
1048 assert_eq!(
1049 hex::encode(&vfs_ctxt),
1050 "0000a7e6a0514440b57fcf6df97b46132adde062f1a5a224aacf4fa0f286b4c56\
1051 fe2768b7dad22333936638c5734f0d529a74880aa",
1052 );
1053 }
1054
1055 #[test]
1057 fn bip39_mnemonic_buf_size() {
1058 let words = bip39::Language::English.word_list();
1059 let max_word_len = words.iter().map(|w| w.len()).max().unwrap();
1060 assert_eq!(max_word_len, 8);
1061
1062 let root_seed = RootSeed::from_u64(20240506);
1063 let mnemonic = root_seed.to_mnemonic();
1064 let num_words = mnemonic.words().count();
1065 assert_eq!(num_words, 24);
1066
1067 assert!(
1069 (max_word_len + 1) * num_words <= RootSeed::BIP39_MNEMONIC_BUF_SIZE
1070 );
1071 }
1072
1073 #[test]
1075 fn derive_bip39_seed_matches_rust_bip39() {
1076 proptest!(|(root_seed in any::<RootSeed>())| {
1077 let mnemonic = root_seed.to_mnemonic();
1078
1079 let our_seed = root_seed.derive_bip39_seed();
1081
1082 let their_seed = mnemonic.to_seed_normalized("");
1084
1085 prop_assert_eq!(our_seed.expose_secret(), &their_seed);
1086 });
1087 }
1088
1089 #[test]
1095 #[ignore]
1096 fn test_decrypt_root_seed() {
1097 let password = std::env::var("PASSWORD").expect("`$PASSWORD` not set");
1098 let in_path = std::env::var_os("IN_PATH").expect("`$IN_PATH` not set");
1099 let in_path = Path::new(&in_path);
1100
1101 let ciphertext = std::fs::read(in_path).unwrap();
1102 let root_seed = RootSeed::password_decrypt(&password, ciphertext)
1103 .expect("Failed to decrypt");
1104
1105 let root_seed_bytes = root_seed.expose_secret().as_slice();
1106 let mut root_seed_hex = hex::encode(root_seed_bytes);
1107 println!("{root_seed_hex}");
1108
1109 root_seed_hex.zeroize();
1110 }
1111}