1+ use std:: path:: Path ;
2+
3+ use anyhow:: bail;
14use anyhow:: Result ;
25use bdk:: bitcoin;
36use bdk:: bitcoin:: util:: bip32:: ExtendedPrivKey ;
@@ -9,47 +12,84 @@ use sha2::Sha256;
912
1013#[ derive( Clone ) ]
1114pub struct Bip39Seed {
12- pub seed : [ u8 ; 64 ] ,
13- pub mnemonic : Mnemonic ,
15+ mnemonic : Mnemonic ,
1416}
1517
1618impl Bip39Seed {
17- pub fn new ( ) -> Result < Bip39Seed > {
19+ pub fn new ( ) -> Result < Self > {
1820 let mut rng = rand:: thread_rng ( ) ;
1921 let mnemonic = Mnemonic :: generate_in_with ( & mut rng, Language :: English , 12 ) ?;
22+ Ok ( Self { mnemonic } )
23+ }
2024
25+ /// Initialise a [`Seed`] from a path.
26+ /// Generates new seed if there was no seed found in the given path
27+ pub fn initialize ( seed_file : & Path ) -> Result < Self > {
28+ let seed = if !seed_file. exists ( ) {
29+ tracing:: info!( "No seed found. Generating new seed" ) ;
30+ let seed = Self :: new ( ) ?;
31+ seed. write_to ( seed_file) ?;
32+ seed
33+ } else {
34+ Bip39Seed :: read_from ( seed_file) ?
35+ } ;
36+ Ok ( seed)
37+ }
38+
39+ pub fn seed ( & self ) -> [ u8 ; 64 ] {
2140 // passing an empty string here is the expected argument if the seed should not be
2241 // additionally password protected (according to https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#from-mnemonic-to-seed)
23- let seed = mnemonic. to_seed_normalized ( "" ) ;
24-
25- Ok ( Bip39Seed { seed, mnemonic } )
42+ self . mnemonic . to_seed_normalized ( "" )
2643 }
2744
2845 pub fn derive_extended_priv_key ( & self , network : Network ) -> Result < ExtendedPrivKey > {
2946 let mut ext_priv_key_seed = [ 0u8 ; 64 ] ;
3047
31- Hkdf :: < Sha256 > :: new ( None , & self . seed )
48+ Hkdf :: < Sha256 > :: new ( None , & self . seed ( ) )
3249 . expand ( b"BITCOIN_WALLET_SEED" , & mut ext_priv_key_seed)
3350 . expect ( "array is of correct length" ) ;
3451
3552 let ext_priv_key = ExtendedPrivKey :: new_master ( network, & ext_priv_key_seed) ?;
36-
3753 Ok ( ext_priv_key)
3854 }
3955
4056 pub fn get_seed_phrase ( & self ) -> Vec < String > {
41- let phrase = self
42- . mnemonic
43- . to_string ( )
44- . split ( ' ' )
45- . map ( |word| word. into ( ) )
46- . collect ( ) ;
47- phrase
57+ self . mnemonic . word_iter ( ) . map ( |word| word. into ( ) ) . collect ( )
58+ }
59+
60+ // Read the entropy used to generate Mnemonic from disk
61+ fn read_from ( path : & Path ) -> Result < Self > {
62+ let bytes = std:: fs:: read ( path) ?;
63+
64+ let seed: Bip39Seed = TryInto :: try_into ( bytes)
65+ . map_err ( |_| anyhow:: anyhow!( "Cannot read the stored entropy" ) ) ?;
66+ Ok ( seed)
67+ }
68+
69+ // Store the entropy used to generate Mnemonic on disk
70+ fn write_to ( & self , path : & Path ) -> Result < ( ) > {
71+ if path. exists ( ) {
72+ let path = path. display ( ) ;
73+ bail ! ( "Refusing to overwrite file at {path}" )
74+ }
75+ std:: fs:: write ( path, & self . mnemonic . to_entropy ( ) ) ?;
76+
77+ Ok ( ( ) )
78+ }
79+ }
80+
81+ impl TryFrom < Vec < u8 > > for Bip39Seed {
82+ type Error = anyhow:: Error ;
83+ fn try_from ( bytes : Vec < u8 > ) -> Result < Self , Self :: Error > {
84+ let mnemonic = Mnemonic :: from_entropy ( & bytes) ?;
85+ Ok ( Bip39Seed { mnemonic } )
4886 }
4987}
5088
5189#[ cfg( test) ]
5290mod tests {
91+ use std:: env:: temp_dir;
92+
5393 use crate :: seed:: Bip39Seed ;
5494
5595 #[ test]
@@ -58,4 +98,21 @@ mod tests {
5898 let phrase = seed. get_seed_phrase ( ) ;
5999 assert_eq ! ( 12 , phrase. len( ) ) ;
60100 }
101+
102+ #[ test]
103+ fn reinitialised_seed_is_the_same ( ) {
104+ let mut path = temp_dir ( ) ;
105+ path. push ( "seed" ) ;
106+ let seed_1 = Bip39Seed :: initialize ( & path) . unwrap ( ) ;
107+ let seed_2 = Bip39Seed :: initialize ( & path) . unwrap ( ) ;
108+ assert_eq ! (
109+ seed_1. mnemonic, seed_2. mnemonic,
110+ "Reinitialised wallet should contain the same mnemonic"
111+ ) ;
112+ assert_eq ! (
113+ seed_1. seed( ) ,
114+ seed_2. seed( ) ,
115+ "Seed derived from mnemonic should be the same"
116+ ) ;
117+ }
61118}
0 commit comments