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