44
55use super :: * ;
66
7+ /// Supported bitcoin witness versions.
8+ #[ derive( Debug , Default , Copy , Clone , PartialEq , Eq , Deserialize , Serialize ) ]
9+ pub enum WitnessVersion {
10+ /// Segregated Witness version 0
11+ SegWitV0 ,
12+ /// Segregated Witness version 1
13+ #[ default]
14+ Taproot ,
15+ }
16+
17+ impl WitnessVersion {
18+ pub ( crate ) fn purpose ( & self ) -> u32 {
19+ match self {
20+ WitnessVersion :: SegWitV0 => 84 ,
21+ WitnessVersion :: Taproot => 86 ,
22+ }
23+ }
24+
25+ pub ( crate ) fn descriptor_fn ( & self ) -> & ' static str {
26+ match self {
27+ WitnessVersion :: SegWitV0 => "wpkh" ,
28+ WitnessVersion :: Taproot => "tr" ,
29+ }
30+ }
31+ }
32+
33+ impl fmt:: Display for WitnessVersion {
34+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
35+ write ! ( f, "{self:?}" )
36+ }
37+ }
38+
39+ impl FromStr for WitnessVersion {
40+ type Err = Error ;
41+
42+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
43+ Ok ( match s. to_lowercase ( ) . as_str ( ) {
44+ "segwitv0" => WitnessVersion :: SegWitV0 ,
45+ "taproot" => WitnessVersion :: Taproot ,
46+ _ => {
47+ return Err ( Error :: InvalidWitnessVersion {
48+ witness_version : s. to_string ( ) ,
49+ } ) ;
50+ }
51+ } )
52+ }
53+ }
54+
755/// A set of Bitcoin keys used by the wallet.
856#[ derive( Debug , Clone , Deserialize , Serialize ) ]
957#[ cfg_attr( feature = "camel_case" , serde( rename_all = "camelCase" ) ) ]
@@ -18,10 +66,13 @@ pub struct Keys {
1866 pub account_xpub_colored : String ,
1967 /// Fingerprint of the master xPub
2068 pub master_fingerprint : String ,
69+ /// Witness version these keys were derived with
70+ #[ serde( default ) ]
71+ pub witness_version : WitnessVersion ,
2172}
2273
23- /// Generate a set of [`Keys`] for the given Bitcoin network.
24- pub fn generate_keys ( bitcoin_network : BitcoinNetwork ) -> Keys {
74+ /// Generate a set of [`Keys`] for the given Bitcoin network and witness version .
75+ pub fn generate_keys ( bitcoin_network : BitcoinNetwork , witness_version : WitnessVersion ) -> Keys {
2576 let bdk_network = BdkNetwork :: from ( bitcoin_network) ;
2677 let mnemonic = Mnemonic :: generate ( ( WordCount :: Words12 , Language :: English ) )
2778 . expect ( "to be able to generate a new mnemonic" ) ;
@@ -32,22 +83,27 @@ pub fn generate_keys(bitcoin_network: BitcoinNetwork) -> Keys {
3283 let xpub = & xkey. into_xpub ( bdk_network, & Secp256k1 :: new ( ) ) ;
3384 let mnemonic_str = mnemonic. to_string ( ) ;
3485 let ( account_xpub_vanilla, account_xpub_colored) =
35- get_account_xpubs ( & bitcoin_network, & mnemonic_str) . unwrap ( ) ;
86+ get_account_xpubs ( & bitcoin_network, & mnemonic_str, witness_version ) . unwrap ( ) ;
3687 let master_fingerprint = xpub. fingerprint ( ) . to_string ( ) ;
3788 Keys {
3889 mnemonic : mnemonic_str,
3990 xpub : xpub. clone ( ) . to_string ( ) ,
4091 account_xpub_vanilla : account_xpub_vanilla. to_string ( ) ,
4192 account_xpub_colored : account_xpub_colored. to_string ( ) ,
4293 master_fingerprint,
94+ witness_version,
4395 }
4496}
4597
46- /// Recreate a set of [`Keys`] from the given mnemonic phrase.
47- pub fn restore_keys ( bitcoin_network : BitcoinNetwork , mnemonic : String ) -> Result < Keys , Error > {
98+ /// Recreate a set of [`Keys`] from the given mnemonic phrase for the given witness version.
99+ pub fn restore_keys (
100+ bitcoin_network : BitcoinNetwork ,
101+ mnemonic : String ,
102+ witness_version : WitnessVersion ,
103+ ) -> Result < Keys , Error > {
48104 let bdk_network = BdkNetwork :: from ( bitcoin_network) ;
49105 let ( account_xpub_vanilla, account_xpub_colored) =
50- get_account_xpubs ( & bitcoin_network, & mnemonic) ?;
106+ get_account_xpubs ( & bitcoin_network, & mnemonic, witness_version ) ?;
51107 let mnemonic_parsed = Mnemonic :: parse_in ( Language :: English , & mnemonic) ?;
52108 let xkey: ExtendedKey = mnemonic_parsed
53109 . clone ( )
@@ -61,13 +117,31 @@ pub fn restore_keys(bitcoin_network: BitcoinNetwork, mnemonic: String) -> Result
61117 account_xpub_vanilla : account_xpub_vanilla. to_string ( ) ,
62118 account_xpub_colored : account_xpub_colored. to_string ( ) ,
63119 master_fingerprint,
120+ witness_version,
64121 } )
65122}
66123
67124#[ cfg( test) ]
68125mod test {
69126 use super :: * ;
70127
128+ #[ test]
129+ fn witness_version_display_and_parse ( ) {
130+ for wv in [ WitnessVersion :: SegWitV0 , WitnessVersion :: Taproot ] {
131+ let s = wv. to_string ( ) ;
132+ assert_eq ! ( WitnessVersion :: from_str( & s) . unwrap( ) , wv) ;
133+ assert_eq ! ( WitnessVersion :: from_str( & s. to_lowercase( ) ) . unwrap( ) , wv) ;
134+ }
135+
136+ let err = WitnessVersion :: from_str ( "nonsense" ) . unwrap_err ( ) ;
137+ assert_eq ! (
138+ err,
139+ Error :: InvalidWitnessVersion {
140+ witness_version: "nonsense" . to_string( ) ,
141+ } ,
142+ ) ;
143+ }
144+
71145 #[ test]
72146 fn generate_success ( ) {
73147 let Keys {
@@ -76,7 +150,8 @@ mod test {
76150 account_xpub_vanilla,
77151 account_xpub_colored,
78152 master_fingerprint,
79- } = generate_keys ( BitcoinNetwork :: Regtest ) ;
153+ witness_version,
154+ } = generate_keys ( BitcoinNetwork :: Regtest , WitnessVersion :: Taproot ) ;
80155
81156 assert ! ( Mnemonic :: from_str( & mnemonic) . is_ok( ) ) ;
82157 let pubkey = Xpub :: from_str ( & xpub) ;
@@ -89,23 +164,40 @@ mod test {
89164 assert ! ( account_pubkey_rgb. is_ok( ) ) ;
90165 let account_pubkey_btc = Xpub :: from_str ( & account_xpub_vanilla) ;
91166 assert ! ( account_pubkey_btc. is_ok( ) ) ;
167+ assert_eq ! ( witness_version, WitnessVersion :: Taproot ) ;
92168 }
93169
94170 #[ test]
95171 fn restore_success ( ) {
96172 let network = BitcoinNetwork :: Regtest ;
97- let Keys {
98- mnemonic,
99- xpub,
100- account_xpub_vanilla,
101- account_xpub_colored,
102- master_fingerprint,
103- } = generate_keys ( network) ;
104173
105- let keys = restore_keys ( network, mnemonic) . unwrap ( ) ;
106- assert_eq ! ( keys. xpub, xpub) ;
107- assert_eq ! ( keys. master_fingerprint, master_fingerprint) ;
108- assert_eq ! ( keys. account_xpub_colored, account_xpub_colored) ;
109- assert_eq ! ( keys. account_xpub_vanilla, account_xpub_vanilla) ;
174+ // round-trip generate → restore for each supported witness version
175+ for wv in [ WitnessVersion :: Taproot , WitnessVersion :: SegWitV0 ] {
176+ let Keys {
177+ mnemonic,
178+ xpub,
179+ account_xpub_vanilla,
180+ account_xpub_colored,
181+ master_fingerprint,
182+ witness_version,
183+ } = generate_keys ( network, wv) ;
184+
185+ let keys = restore_keys ( network, mnemonic, witness_version) . unwrap ( ) ;
186+ assert_eq ! ( keys. xpub, xpub) ;
187+ assert_eq ! ( keys. master_fingerprint, master_fingerprint) ;
188+ assert_eq ! ( keys. account_xpub_colored, account_xpub_colored) ;
189+ assert_eq ! ( keys. account_xpub_vanilla, account_xpub_vanilla) ;
190+ assert_eq ! ( keys. witness_version, witness_version) ;
191+ assert_eq ! ( witness_version, wv) ;
192+ }
193+
194+ // same mnemonic + different witness versions ⇒ same master xpub
195+ // and fingerprint but different account xpubs (different BIP purpose)
196+ let tr = generate_keys ( network, WitnessVersion :: Taproot ) ;
197+ let wpkh = restore_keys ( network, tr. mnemonic . clone ( ) , WitnessVersion :: SegWitV0 ) . unwrap ( ) ;
198+ assert_eq ! ( tr. xpub, wpkh. xpub) ;
199+ assert_eq ! ( tr. master_fingerprint, wpkh. master_fingerprint) ;
200+ assert_ne ! ( tr. account_xpub_colored, wpkh. account_xpub_colored) ;
201+ assert_ne ! ( tr. account_xpub_vanilla, wpkh. account_xpub_vanilla) ;
110202 }
111203}
0 commit comments