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, entropy, hmac, 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 #[cfg(any(test, feature = "test-utils"))]
62 pub fn from_rng<R: Crng>(rng: &mut R) -> Self {
63 Self(Secret::new(rng.gen_bytes()))
64 }
65
66 pub fn from_rng_with_extra_entropy<R: Crng>(rng: &mut R) -> Self {
71 Self(Secret::new(entropy::gen_seed(rng)))
72 }
73
74 pub fn to_mnemonic(&self) -> bip39::Mnemonic {
80 bip39::Mnemonic::from_entropy_in(
81 bip39::Language::English,
82 self.0.expose_secret().as_slice(),
83 )
84 .expect("Always succeeds for 256 bits")
85 }
86
87 pub fn derive_bip39_seed(&self) -> Secret<[u8; 64]> {
101 let mnemonic = self.to_mnemonic();
103
104 let mut buf = [0u8; Self::BIP39_MNEMONIC_BUF_SIZE];
107 let mut len = 0;
108 for (i, word) in mnemonic.words().enumerate() {
109 if i > 0 {
110 buf[len] = b' ';
111 len += 1;
112 }
113 let word_bytes = word.as_bytes();
114 buf[len..len + word_bytes.len()].copy_from_slice(word_bytes);
115 len += word_bytes.len();
116 }
117 let mnemonic_bytes = &buf[..len];
118
119 let salt = b"mnemonic";
121
122 let mut seed = [0u8; 64];
124 ring::pbkdf2::derive(
125 ring::pbkdf2::PBKDF2_HMAC_SHA512,
126 const { NonZeroU32::new(2048).unwrap() },
127 salt,
128 mnemonic_bytes,
129 &mut seed,
130 );
131
132 buf.zeroize();
134
135 Secret::new(seed)
136 }
137
138 fn extract(&self) -> ring::hkdf::Prk {
141 let salted_hkdf = ring::hkdf::Salt::new(
142 ring::hkdf::HKDF_SHA256,
143 Self::HKDF_SALT.as_slice(),
144 );
145 salted_hkdf.extract(self.0.expose_secret().as_slice())
146 }
147
148 pub fn derive_to_slice(&self, label: &[&[u8]], out: &mut [u8]) {
150 struct OkmLength(usize);
151
152 impl ring::hkdf::KeyType for OkmLength {
153 fn len(&self) -> usize {
154 self.0
155 }
156 }
157
158 assert!(out.len() <= Self::HKDF_MAX_OUT_LEN);
159
160 self.extract()
161 .expand(label, OkmLength(out.len()))
162 .expect("should not fail")
163 .fill(out)
164 .expect("should not fail")
165 }
166
167 pub fn derive(&self, label: &[&[u8]]) -> Secret<[u8; 32]> {
169 let mut out = [0u8; 32];
170 self.derive_to_slice(label, &mut out);
171 Secret::new(out)
172 }
173
174 pub fn derive_vec(&self, label: &[&[u8]], out_len: usize) -> SecretVec<u8> {
177 let mut out = vec![0u8; out_len];
178 self.derive_to_slice(label, &mut out);
179 SecretVec::new(out)
180 }
181
182 pub fn derive_ephemeral_issuing_ca_key_pair(&self) -> ed25519::KeyPair {
185 let seed = self.derive(&[b"shared seed tls ca key pair"]);
189 ed25519::KeyPair::from_seed(seed.expose_secret())
190 }
191
192 pub fn derive_revocable_issuing_ca_key_pair(&self) -> ed25519::KeyPair {
195 let seed = self.derive(&[b"revocable issuing ca key pair"]);
196 ed25519::KeyPair::from_seed(seed.expose_secret())
197 }
198
199 pub fn derive_user_key_pair(&self) -> ed25519::KeyPair {
205 let seed = self.derive(&[b"user key pair"]);
206 ed25519::KeyPair::from_seed(seed.expose_secret())
207 }
208
209 pub fn derive_user_pk(&self) -> UserPk {
211 UserPk::new(self.derive_user_key_pair().public_key().to_array())
212 }
213
214 pub fn derive_bip32_master_xprv(&self, network: Network) -> bip32::Xpriv {
223 let bip39_seed = self.derive_bip39_seed();
224 bip32::Xpriv::new_master(
225 network.to_bitcoin(),
226 bip39_seed.expose_secret(),
227 )
228 .expect("Should never fail")
229 }
230
231 pub fn derive_legacy_master_xprv(&self, network: Network) -> bip32::Xpriv {
243 bip32::Xpriv::new_master(network.to_bitcoin(), self.0.expose_secret())
244 .expect("Should never fail")
245 }
246
247 pub fn derive_ldk_seed(&self) -> Secret<[u8; 32]> {
250 let master_xprv = self.derive_legacy_master_xprv(Network::Mainnet);
253
254 let m_535h =
256 ChildNumber::from_hardened_idx(535).expect("Is within [0, 2^31-1]");
257 let ldk_xprv = master_xprv
258 .derive_priv(&SECP256K1, &m_535h)
259 .expect("Should always succeed");
260
261 Secret::new(ldk_xprv.private_key.secret_bytes())
262 }
263
264 pub fn derive_node_key_pair(&self) -> secp256k1::Keypair {
267 let ldk_seed = self.derive_ldk_seed();
269
270 let ldk_xprv = bip32::Xpriv::new_master(
273 bitcoin::Network::Bitcoin,
274 ldk_seed.expose_secret(),
275 )
276 .expect("should never fail; the sizes match up");
277
278 let m_0h = ChildNumber::from_hardened_idx(0)
279 .expect("should never fail; index is in range");
280 let node_sk = ldk_xprv
281 .derive_priv(&SECP256K1, &m_0h)
282 .expect("should never fail")
283 .private_key;
284
285 secp256k1::Keypair::from_secret_key(&SECP256K1, &node_sk)
286 }
287
288 pub fn derive_node_pk(&self) -> NodePk {
290 NodePk(self.derive_node_key_pair().public_key())
291 }
292
293 #[cfg(any(test, feature = "test-utils"))]
304 pub fn derive_receive_auth_key(&self) -> [u8; 32] {
305 let ldk_seed = self.derive_ldk_seed();
307
308 let ldk_xprv = bip32::Xpriv::new_master(
311 bitcoin::Network::Bitcoin,
312 ldk_seed.expose_secret(),
313 )
314 .expect("should never fail; the sizes match up");
315
316 let m_7h = ChildNumber::from_hardened_idx(7)
317 .expect("should never fail; index is in range");
318 let sk = ldk_xprv
319 .derive_priv(&SECP256K1, &m_7h)
320 .expect("should never fail")
321 .private_key;
322
323 sk.secret_bytes()
324 }
325
326 pub fn derive_vfs_master_key(&self) -> AesMasterKey {
327 let secret = self.derive(&[b"vfs master key"]);
328 AesMasterKey::new(secret.expose_secret())
329 }
330
331 pub fn derive_vss_auth_key(&self, hostname: &str) -> secp256k1::SecretKey {
337 let secret = self.derive(&[b"vss auth key\0", hostname.as_bytes()]);
338 secp256k1::SecretKey::from_slice(secret.expose_secret())
339 .expect("HKDF output should be a valid secp256k1 secret key")
340 }
341
342 pub fn derive_continuation_mac_key(&self) -> hmac::Key {
346 let secret = self.derive(&[b"continuation mac key"]);
347 hmac::Key::from_seed(secret.expose_secret())
348 }
349
350 #[cfg(any(test, feature = "test-utils"))]
351 pub fn as_bytes(&self) -> &[u8] {
352 self.0.expose_secret().as_slice()
353 }
354
355 pub fn password_encrypt(
368 &self,
369 rng: &mut impl Crng,
370 password: &str,
371 ) -> anyhow::Result<Vec<u8>> {
372 let salt = rng.gen_bytes();
374
375 let mut aes_ciphertext =
377 password::encrypt(rng, password, &salt, self.0.expose_secret())
378 .context("Password encryption failed")?;
379
380 let mut combined = Vec::from(salt);
382 combined.append(&mut aes_ciphertext);
383
384 let expected_combined_len = 32 + aes::encrypted_len(32);
387 assert!(combined.len() == expected_combined_len);
388
389 Ok(combined)
390 }
391
392 pub fn password_decrypt(
397 password: &str,
398 mut combined: Vec<u8>,
399 ) -> anyhow::Result<Self> {
400 let expected_combined_len = 32 + aes::encrypted_len(32);
402 ensure!(
403 combined.len() == expected_combined_len,
404 "Combined bytes had the wrong length"
405 );
406
407 let aes_ciphertext = combined.split_off(32);
409 let unsized_salt = combined.into_boxed_slice();
410 let salt = Box::<[u8; 32]>::try_from(unsized_salt)
411 .expect("We split off at 32, so there are exactly 32 bytes");
412
413 let root_seed_bytes =
415 password::decrypt(password, &salt, aes_ciphertext)
416 .map(Secret::new)
417 .context("Password decryption failed")?;
418
419 Self::try_from(root_seed_bytes.expose_secret().as_slice())
421 }
422}
423
424impl ExposeSecret<[u8; Self::LENGTH]> for RootSeed {
425 fn expose_secret(&self) -> &[u8; Self::LENGTH] {
426 self.0.expose_secret()
427 }
428}
429
430impl FromStr for RootSeed {
431 type Err = hex::DecodeError;
432
433 fn from_str(hex: &str) -> Result<Self, Self::Err> {
434 let mut bytes = [0u8; Self::LENGTH];
435 hex::decode_to_slice(hex, bytes.as_mut_slice())
436 .map(|()| Self::new(Secret::new(bytes)))
437 }
438}
439
440impl fmt::Debug for RootSeed {
441 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442 f.write_str("RootSeed(..)")
444 }
445}
446
447impl TryFrom<&[u8]> for RootSeed {
448 type Error = anyhow::Error;
449
450 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
451 if bytes.len() != Self::LENGTH {
452 bail!("input must be {} bytes", Self::LENGTH);
453 }
454 let mut out = [0u8; Self::LENGTH];
455 out[..].copy_from_slice(bytes);
456 Ok(Self::new(Secret::new(out)))
457 }
458}
459
460impl TryFrom<bip39::Mnemonic> for RootSeed {
461 type Error = anyhow::Error;
462
463 fn try_from(mnemonic: bip39::Mnemonic) -> Result<Self, Self::Error> {
464 use lexe_std::array::ArrayExt;
465
466 let (entropy, entropy_len) = mnemonic.to_entropy_array();
468 let entropy = secrecy::zeroize::Zeroizing::new(entropy);
469
470 ensure!(entropy_len == 32, "Should contain exactly 32 bytes");
471
472 let (seed_buf, _remainder) = entropy.split_array_ref_stable::<32>();
473
474 Ok(Self(Secret::new(*seed_buf)))
475 }
476}
477
478struct RootSeedVisitor;
479
480impl de::Visitor<'_> for RootSeedVisitor {
481 type Value = RootSeed;
482
483 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
484 f.write_str("hex-encoded RootSeed or raw bytes")
485 }
486
487 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
488 where
489 E: de::Error,
490 {
491 RootSeed::from_str(v).map_err(serde::de::Error::custom)
492 }
493
494 fn visit_bytes<E>(self, b: &[u8]) -> Result<Self::Value, E>
495 where
496 E: de::Error,
497 {
498 RootSeed::try_from(b).map_err(de::Error::custom)
499 }
500}
501
502impl<'de> Deserialize<'de> for RootSeed {
503 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
504 where
505 D: Deserializer<'de>,
506 {
507 if deserializer.is_human_readable() {
508 deserializer.deserialize_str(RootSeedVisitor)
509 } else {
510 deserializer.deserialize_bytes(RootSeedVisitor)
511 }
512 }
513}
514
515impl Serialize for RootSeed {
516 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
517 where
518 S: Serializer,
519 {
520 if serializer.is_human_readable() {
521 let hex_str = hex::encode(self.0.expose_secret());
522 serializer.serialize_str(&hex_str)
523 } else {
524 serializer.serialize_bytes(self.0.expose_secret())
525 }
526 }
527}
528
529#[cfg(any(test, feature = "test-utils"))]
530mod test_impls {
531 use proptest::{
532 arbitrary::{Arbitrary, any},
533 strategy::{BoxedStrategy, Strategy},
534 };
535
536 use super::*;
537
538 impl Arbitrary for RootSeed {
539 type Strategy = BoxedStrategy<Self>;
540 type Parameters = ();
541
542 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
543 any::<[u8; 32]>()
544 .prop_map(|buf| Self::new(Secret::new(buf)))
545 .no_shrink()
546 .boxed()
547 }
548 }
549
550 impl PartialEq for RootSeed {
553 fn eq(&self, other: &Self) -> bool {
554 self.expose_secret() == other.expose_secret()
555 }
556 }
557}
558
559#[cfg(test)]
560mod test {
561 use std::path::Path;
562
563 use bitcoin::NetworkKind;
564 use lexe_crypto::rng::FastRng;
565 use lexe_sha256::sha256;
566 use proptest::{
567 arbitrary::any, collection::vec, prop_assert_eq, proptest,
568 strategy::Strategy, test_runner::Config,
569 };
570
571 use super::*;
572 use crate::ln::network::Network;
573
574 fn hmac_sha256(key: &[u8], msg: &[u8]) -> sha256::Hash {
578 let h_key = sha256::digest(key);
579 let mut zero_pad_key = [0u8; 64];
580
581 let key = match key.len() {
583 len if len > 64 => h_key.as_ref(),
584 _ => key,
585 };
586 zero_pad_key[..key.len()].copy_from_slice(key);
587 let key = zero_pad_key.as_slice();
588 assert_eq!(key.len(), 64);
589
590 let mut o_key = [0u8; 64];
592 for (o_key_i, key_i) in o_key.iter_mut().zip(key) {
593 *o_key_i = key_i ^ 0x5c;
594 }
595
596 let mut i_key = [0u8; 64];
598 for (i_key_i, key_i) in i_key.iter_mut().zip(key) {
599 *i_key_i = key_i ^ 0x36;
600 }
601
602 let h_i = sha256::digest_many(&[&i_key, msg]);
604
605 sha256::digest_many(&[&o_key, h_i.as_ref()])
607 }
608
609 fn hkdf_sha256(
611 ikm: &[u8],
612 salt: &[u8],
613 info: &[&[u8]],
614 out_len: usize,
615 ) -> Vec<u8> {
616 let prk = hmac_sha256(salt, ikm);
617
618 let n = (out_len.saturating_sub(1) / 32) + 1;
621 let n = u8::try_from(n).expect("out_len too large");
622
623 let mut t_i = [0u8; 32];
628 let mut out = Vec::new();
629
630 for i in 1..=n {
631 let mut m_i = if i == 1 { Vec::new() } else { t_i.to_vec() };
633 for info_part in info {
634 m_i.extend_from_slice(info_part);
635 }
636 m_i.extend_from_slice(&[i]);
637
638 let h_i = hmac_sha256(prk.as_ref(), &m_i);
639 t_i.copy_from_slice(h_i.as_ref());
640
641 if i < n {
642 out.extend_from_slice(&t_i[..]);
643 } else {
644 let l = 32 - (((n as usize) * 32) - out_len);
645 out.extend_from_slice(&t_i[..l]);
646 }
647 }
648
649 out
650 }
651
652 #[ignore]
656 #[test]
657 fn dump_root_seed() {
658 let root_seed = RootSeed::from_u64(20240506);
659 let root_seed_hex = hex::encode(root_seed.expose_secret());
660 let user_pk = root_seed.derive_user_pk();
661 let node_pk = root_seed.derive_node_pk();
662
663 println!(
664 "root_seed: '{root_seed_hex}', \
665 user_pk: '{user_pk}', node_pk: '{node_pk}'"
666 );
667 }
668
669 #[test]
670 fn test_root_seed_serde() {
671 let input =
672 "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069";
673 let input_json = format!("\"{input}\"");
674 let seed_bytes = hex::decode(input).unwrap();
675
676 let seed = RootSeed::from_str(input).unwrap();
677 assert_eq!(seed.as_bytes(), &seed_bytes);
678
679 let seed2: RootSeed = serde_json::from_str(&input_json).unwrap();
680 assert_eq!(seed2.as_bytes(), &seed_bytes);
681
682 #[derive(Deserialize)]
683 struct Foo {
684 x: u32,
685 seed: RootSeed,
686 y: String,
687 }
688
689 let foo_json = format!(
690 "{{\n\
691 \"x\": 123,\n\
692 \"seed\": \"{input}\",\n\
693 \"y\": \"asdf\"\n\
694 }}"
695 );
696
697 let foo2: Foo = serde_json::from_str(&foo_json).unwrap();
698 assert_eq!(foo2.x, 123);
699 assert_eq!(foo2.seed.as_bytes(), &seed_bytes);
700 assert_eq!(foo2.y, "asdf");
701 }
702
703 #[test]
704 fn test_root_seed_derive() {
705 let seed = RootSeed::from_u64(0x42);
706
707 let out8 = seed.derive_vec(&[b"very cool secret"], 8);
708 let out16 = seed.derive_vec(&[b"very cool secret"], 16);
709 let out32 = seed.derive_vec(&[b"very cool secret"], 32);
710 let out32_2 = seed.derive(&[b"very cool secret"]);
711
712 assert_eq!("c724f46ae4c48017", hex::encode(out8.expose_secret()));
713 assert_eq!(
714 "c724f46ae4c480172a75cf775dbb64b1",
715 hex::encode(out16.expose_secret())
716 );
717 assert_eq!(
718 "c724f46ae4c480172a75cf775dbb64b160beb74137eb7d0cef72fde0523674de",
719 hex::encode(out32.expose_secret())
720 );
721 assert_eq!(out32.expose_secret(), out32_2.expose_secret());
722 }
723
724 #[test]
726 fn test_root_seed_derive_equiv() {
727 let arb_seed = any::<RootSeed>();
728 let arb_label = vec(vec(any::<u8>(), 0..=64), 0..=4);
729 let arb_len = 0_usize..=1024;
730
731 proptest!(|(seed in arb_seed, label in arb_label, len in arb_len)| {
732 let label = label
733 .iter()
734 .map(|x| x.as_slice())
735 .collect::<Vec<_>>();
736
737 let expected = hkdf_sha256(
738 seed.as_bytes(),
739 RootSeed::HKDF_SALT.as_slice(),
740 &label,
741 len,
742 );
743
744 let actual = seed.derive_vec(&label, len);
745
746 assert_eq!(&expected, actual.expose_secret());
747 });
748 }
749
750 #[test]
755 fn when_does_network_matter() {
756 proptest!(|(
757 root_seed in any::<RootSeed>(),
758 network1 in any::<Network>(),
759 network2 in any::<Network>(),
760 )| {
761 let network_kind1 = NetworkKind::from(network1.to_bitcoin());
762 let network_kind2 = NetworkKind::from(network2.to_bitcoin());
763
764 let master_xprv1 = root_seed.derive_legacy_master_xprv(network1);
768 let master_xprv2 = root_seed.derive_legacy_master_xprv(network2);
769 let master_xprvs_equal = master_xprv1 == master_xprv2;
771 let network_kinds_equal = network_kind1 == network_kind2;
772 prop_assert_eq!(master_xprvs_equal, network_kinds_equal);
773
774 let m_535h = ChildNumber::from_hardened_idx(535)
777 .expect("Is within [0, 2^31-1]");
778 let ldk_seed1 = master_xprv1
779 .derive_priv(&SECP256K1, &m_535h)
780 .expect("Should always succeed")
781 .private_key
782 .secret_bytes();
783 let ldk_seed2 = master_xprv2
784 .derive_priv(&SECP256K1, &m_535h)
785 .expect("Should always succeed")
786 .private_key
787 .secret_bytes();
788 prop_assert_eq!(ldk_seed1, ldk_seed2);
789 let ldk_seed = ldk_seed1;
790
791 let ldk_xprv1 = bip32::Xpriv::new_master(network1.to_bitcoin(), &ldk_seed)
795 .expect("Should never fail");
796 let ldk_xprv2 = bip32::Xpriv::new_master(network2.to_bitcoin(), &ldk_seed)
797 .expect("Should never fail");
798 let ldk_xprvs_equal = ldk_xprv1 == ldk_xprv2;
800 prop_assert_eq!(ldk_xprvs_equal, network_kinds_equal);
801 let m_0h = ChildNumber::from_hardened_idx(0)
803 .expect("should never fail; index is in range");
804 let node_sk1 = ldk_xprv1
805 .derive_priv(&SECP256K1, &m_0h)
806 .expect("should never fail")
807 .private_key;
808 let node_sk2 = ldk_xprv2
809 .derive_priv(&SECP256K1, &m_0h)
810 .expect("should never fail")
811 .private_key;
812 prop_assert_eq!(node_sk1, node_sk2);
813 let keypair1 =
815 secp256k1::Keypair::from_secret_key(&SECP256K1, &node_sk1);
816 let keypair2 =
817 secp256k1::Keypair::from_secret_key(&SECP256K1, &node_sk2);
818 prop_assert_eq!(keypair1, keypair2);
819 let node_pk1 = NodePk(secp256k1::PublicKey::from(keypair1));
821 let node_pk2 = NodePk(secp256k1::PublicKey::from(keypair2));
822 prop_assert_eq!(node_pk1, node_pk2);
823 let node_pk1_str = node_pk1.to_string();
825 let node_pk2_str = node_pk2.to_string();
826 prop_assert_eq!(node_pk1_str, node_pk2_str);
827 });
828 }
829
830 #[test]
831 fn password_encryption_roundtrip() {
832 use password::{MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH};
833
834 let password_length_range = MIN_PASSWORD_LENGTH..MAX_PASSWORD_LENGTH;
835 let any_valid_password =
836 proptest::collection::vec(any::<char>(), password_length_range)
837 .prop_map(String::from_iter);
838
839 let config = Config::with_cases(4);
841 proptest!(config, |(
842 mut rng in any::<FastRng>(),
843 password in any_valid_password,
844 )| {
845 let root_seed1 = RootSeed::from_rng(&mut rng);
846 let encrypted = root_seed1.password_encrypt(&mut rng, &password)
847 .unwrap();
848 let root_seed2 = RootSeed::password_decrypt(&password, encrypted)
849 .unwrap();
850 assert_eq!(root_seed1, root_seed2);
851 })
852 }
853
854 #[test]
855 fn password_decryption_compatibility() {
856 let root_seed1 = RootSeed::new(Secret::new([69u8; 32]));
857 let password1 = "password1234";
858 let encrypted = hex::decode("adcfc4aef26858bacfae83dd19e735bb145203ab18183cbe932cd742b4446e7300b561678b0652666b316288bbb57552c4f40e91d8e440fd1085cba610204ca982f52fce471de27fe360e9560cee0996e55ce7ac323201908b7ff261b8ff425a87d215e83870e45062d988627c8cb7216b").unwrap();
866 let root_seed1_decrypted =
867 RootSeed::password_decrypt(password1, encrypted).unwrap();
868 assert_eq!(root_seed1, root_seed1_decrypted);
869
870 let root_seed2 = RootSeed::new(Secret::new([0u8; 32]));
871 let password2 = " ";
872 let encrypted = hex::decode("adcfc4aef26858bacfae83dd19e735bb145203ab18183cbe932cd742b4446e7300b561678b0652666b316288bbb57552c4f40e91d8e440fd1085cba610204ca982062fbcb21c14cdb9d107f2f359e0f272e473d2cdb71a870d8fb19d1169c160876ee1ccde4f73a8f2b4ebc9bed68f6139").unwrap();
880 let root_seed2_decrypted =
881 RootSeed::password_decrypt(password2, encrypted).unwrap();
882 assert_eq!(root_seed2, root_seed2_decrypted);
883 }
884
885 #[test]
886 fn root_seed_mnemonic_round_trip() {
887 proptest!(|(root_seed1 in any::<RootSeed>())| {
888 let mnemonic = root_seed1.to_mnemonic();
889
890 prop_assert_eq!(mnemonic.word_count(), 24);
892
893 let root_seed2 = RootSeed::try_from(mnemonic).unwrap();
894 prop_assert_eq!(
895 root_seed1.expose_secret(), root_seed2.expose_secret()
896 );
897 });
898 }
899
900 #[test]
902 fn mnemonic_fromstr_display_roundtrip() {
903 proptest!(|(root_seed in any::<RootSeed>())| {
904 let mnemonic1 = root_seed.to_mnemonic();
905 let mnemonic2 = bip39::Mnemonic::from_str(&mnemonic1.to_string()).unwrap();
906 prop_assert_eq!(mnemonic1, mnemonic2)
907 })
908 }
909
910 #[test]
915 fn mnemonic_compatibility_test() {
916 let seed1 = RootSeed::new(Secret::new(hex::decode_const(
936 b"91f24ce8326abc2e9faef6a3b866021ce9574c11210e86b0f457a31ed8ad4cba",
937 )));
938 let seed2 = RootSeed::new(Secret::new(hex::decode_const(
939 b"5c2aa5fdd678112c8b13d745b5c1d1e1a81ace76721ec72f1424bd2eb387a8af",
940 )));
941 let seed3 = RootSeed::new(Secret::new(hex::decode_const(
942 b"51ddba4775fc71fb1dba65dfc2ffab7526dd61bae7a9b13e9f3aa550bee19360",
943 )));
944
945 let str1 = String::from(
947 "music mystery deliver gospel profit blanket leaf tell \
948 photo segment letter degree nice plastic duty canyon \
949 mammal marble bicycle economy unique find cream dune",
950 );
951 let str2 = String::from(
952 "found festival legal provide library north clump kit \
953 east puppy inner select like grunt supply duck \
954 shrimp judge ankle kid twenty sense pencil tray",
955 );
956 let str3 = String::from(
957 "fade universe mushroom typical shove work ivory erosion \
958 thank blood turn tumble horse radio twist vivid \
959 raise visual solid enjoy armor ignore eternal arrange",
960 );
961
962 let mnemonic_from_str1 = bip39::Mnemonic::from_str(&str1).unwrap();
964 let mnemonic_from_str2 = bip39::Mnemonic::from_str(&str2).unwrap();
965 let mnemonic_from_str3 = bip39::Mnemonic::from_str(&str3).unwrap();
966 assert_eq!(seed1.to_mnemonic(), mnemonic_from_str1);
967 assert_eq!(seed2.to_mnemonic(), mnemonic_from_str2);
968 assert_eq!(seed3.to_mnemonic(), mnemonic_from_str3);
969
970 let seed_from_str1 =
972 RootSeed::try_from(mnemonic_from_str1.clone()).unwrap();
973 let seed_from_str2 =
974 RootSeed::try_from(mnemonic_from_str2.clone()).unwrap();
975 let seed_from_str3 =
976 RootSeed::try_from(mnemonic_from_str3.clone()).unwrap();
977 assert_eq!(seed1.as_bytes(), seed_from_str1.as_bytes());
978 assert_eq!(seed2.as_bytes(), seed_from_str2.as_bytes());
979 assert_eq!(seed3.as_bytes(), seed_from_str3.as_bytes());
980
981 assert_eq!(str1, seed1.to_mnemonic().to_string());
983 assert_eq!(str2, seed2.to_mnemonic().to_string());
984 assert_eq!(str3, seed3.to_mnemonic().to_string());
985 }
986
987 #[test]
990 fn derive_snapshots() {
991 let seed = RootSeed::from_u64(20240506);
992
993 let user_pk = seed.derive_user_pk();
995 assert_eq!(
996 user_pk.to_string(),
997 "a9edf9596ddf589918beca32d148a7d0ba59273b419ccf63a910f1b75861ff06",
998 );
999
1000 let node_pk = seed.derive_node_pk();
1002 assert_eq!(
1003 node_pk.to_string(),
1004 "035a70d45eec7efb270319f116a9684250acb4ef282a26d21874878e7c5088f73b",
1005 );
1006
1007 let lexe_vss_key = seed.derive_vss_auth_key("vss.lexe.app");
1009 assert_eq!(
1010 hex::encode(&lexe_vss_key.secret_bytes()),
1011 "29646f43becb6541b08dcc018911fc268826d13808080870739be43c9acac923",
1012 );
1013 let external_vss_key = seed.derive_vss_auth_key("vss.example");
1014 assert_eq!(
1015 hex::encode(&external_vss_key.secret_bytes()),
1016 "a10dd5d440f79f87d64526402772b2fcc97687f110cf79e224eca16605c7386f",
1017 );
1018
1019 let ldk_seed = seed.derive_ldk_seed();
1021 assert_eq!(
1022 hex::encode(ldk_seed.expose_secret()),
1023 "551444699ae8acbebe67d5b54da844e8297b83e26e205203a65f29564eaf3787",
1024 );
1025
1026 let bip39_seed = seed.derive_bip39_seed();
1028 assert_eq!(
1029 hex::encode(bip39_seed.expose_secret()),
1030 "30dc1cca6811e6f52a6efba751db4fe9495883b778c72b28ee248f0076cf03b9\
1031 dc3c3d7d662c98806ce59c0e59911a249533ca0c82dea3780cdf040f9a3dfe09",
1032 );
1033
1034 let bip39_master_xpriv =
1036 seed.derive_bip32_master_xprv(Network::Mainnet);
1037 assert_eq!(
1038 bip39_master_xpriv.to_string(),
1039 "xprv9s21ZrQH143K3BwTSDGEpsQA99b5fmckcX2s4dBbxojs287ApWXGThVTu9\
1040 TmogYG8A1JiUnbD6gHSfw5hXsTduny878ygutaCaCvg1KTvgM",
1041 );
1042
1043 let bip39_testnet_xpriv =
1045 seed.derive_bip32_master_xprv(Network::Testnet3);
1046 assert_eq!(
1047 bip39_testnet_xpriv.to_string(),
1048 "tprv8ZgxMBicQKsPe1Az6n7jzX29TH1HuHekx4wyw3c4SnELoirFoss1ySrupK\
1049 dRp3vaVbY5iaQMNTG5uXUppkDQSy4ZekMHMGcd7fxM7h7WWqo"
1050 );
1051
1052 let master_xpriv = seed.derive_legacy_master_xprv(Network::Mainnet);
1054 assert_eq!(
1055 master_xpriv.to_string(),
1056 "xprv9s21ZrQH143K42JPXVa2Q7nAp6XB3FVwyYdGkQetMYRcprZXKvt52p1tqg\
1057 9fwyFJaL6Ki92bCdRNDPAnyddy7CzpQAEktM8nMtNGw4Xj6vt",
1058 );
1059
1060 let master_xpriv_testnet =
1062 seed.derive_legacy_master_xprv(Network::Testnet3);
1063 assert_eq!(
1064 master_xpriv_testnet.to_string(),
1065 "tprv8ZgxMBicQKsPeqXvC4RXZmQA8DwPGmXxK6YPcq5LqWv6cTJcKJDpYZPLk\
1066 rKKxLdcwmd6iEeMMz1AgEiY6qyuvGGQvoT4YhrqGz7hNoR5R4G",
1067 );
1068
1069 let ephemeral_ca = seed.derive_ephemeral_issuing_ca_key_pair();
1071 assert_eq!(
1072 ephemeral_ca.public_key().to_string(),
1073 "70656b5a6084c457bf004dad264cecc131879b7e6791fe0cc828c38cc0df6e92",
1074 );
1075
1076 let revocable_ca = seed.derive_revocable_issuing_ca_key_pair();
1078 assert_eq!(
1079 revocable_ca.public_key().to_string(),
1080 "efe6e020ba9ca4a50467cdbaff469f9d465f21d1c6fe976868a20d97bbaa2ee3",
1081 );
1082
1083 let vfs_ctxt = seed.derive_vfs_master_key().encrypt(
1085 &mut FastRng::from_u64(1234),
1086 &[],
1087 None,
1088 &|out: &mut Vec<u8>| out.extend_from_slice(b"test"),
1089 );
1090 assert_eq!(
1091 hex::encode(&vfs_ctxt),
1092 "0000a7e6a0514440b57fcf6df97b46132adde062f1a5a224aacf4fa0f286b4c56\
1093 fe2768b7dad22333936638c5734f0d529a74880aa",
1094 );
1095 }
1096
1097 #[test]
1099 fn bip39_mnemonic_buf_size() {
1100 let words = bip39::Language::English.word_list();
1101 let max_word_len = words.iter().map(|w| w.len()).max().unwrap();
1102 assert_eq!(max_word_len, 8);
1103
1104 let root_seed = RootSeed::from_u64(20240506);
1105 let mnemonic = root_seed.to_mnemonic();
1106 let num_words = mnemonic.words().count();
1107 assert_eq!(num_words, 24);
1108
1109 assert!(
1111 (max_word_len + 1) * num_words <= RootSeed::BIP39_MNEMONIC_BUF_SIZE
1112 );
1113 }
1114
1115 #[test]
1117 fn derive_bip39_seed_matches_rust_bip39() {
1118 proptest!(|(root_seed in any::<RootSeed>())| {
1119 let mnemonic = root_seed.to_mnemonic();
1120
1121 let our_seed = root_seed.derive_bip39_seed();
1123
1124 let their_seed = mnemonic.to_seed_normalized("");
1126
1127 prop_assert_eq!(our_seed.expose_secret(), &their_seed);
1128 });
1129 }
1130
1131 #[test]
1137 #[ignore]
1138 fn test_decrypt_root_seed() {
1139 let password = std::env::var("PASSWORD").expect("`$PASSWORD` not set");
1140 let in_path = std::env::var_os("IN_PATH").expect("`$IN_PATH` not set");
1141 let in_path = Path::new(&in_path);
1142
1143 let ciphertext = std::fs::read(in_path).unwrap();
1144 let root_seed = RootSeed::password_decrypt(&password, ciphertext)
1145 .expect("Failed to decrypt");
1146
1147 let root_seed_bytes = root_seed.expose_secret().as_slice();
1148 let mut root_seed_hex = hex::encode(root_seed_bytes);
1149 println!("{root_seed_hex}");
1150
1151 root_seed_hex.zeroize();
1152 }
1153}