Skip to content

Commit 0062f2d

Browse files
authored
Merge pull request #74 from dcorral/segwit-v0-upstream
add support for P2WPKH
2 parents 1d461af + 348afa2 commit 0062f2d

18 files changed

Lines changed: 392 additions & 78 deletions

File tree

bindings/c-ffi/example.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
int main() {
66
const char *bitcoin_network = "Regtest";
7-
CResultString keys_res = rgblib_generate_keys(bitcoin_network);
7+
const char *witness_version = "Taproot";
8+
CResultString keys_res =
9+
rgblib_generate_keys(bitcoin_network, witness_version);
810
if (keys_res.result == Err) {
911
printf("ERR: %s\n", keys_res.inner);
1012
return EXIT_FAILURE;

bindings/c-ffi/src/lib.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use std::{
1313

1414
use rgb_lib::{
1515
AssetSchema, Assignment, Error as RgbLibError,
16+
keys::WitnessVersion,
1617
utils::BitcoinNetwork,
1718
wallet::{
1819
Online, Recipient, RefreshFilter, RgbWalletOpsOffline, RgbWalletOpsOnline, SinglesigKeys,
@@ -171,8 +172,11 @@ pub extern "C" fn rgblib_finalize_psbt(
171172
}
172173

173174
#[unsafe(no_mangle)]
174-
pub extern "C" fn rgblib_generate_keys(bitcoin_network: *const c_char) -> CResultString {
175-
generate_keys(bitcoin_network).into()
175+
pub extern "C" fn rgblib_generate_keys(
176+
bitcoin_network: *const c_char,
177+
witness_version: *const c_char,
178+
) -> CResultString {
179+
generate_keys(bitcoin_network, witness_version).into()
176180
}
177181

178182
#[unsafe(no_mangle)]
@@ -379,8 +383,9 @@ pub extern "C" fn rgblib_restore_backup(
379383
pub extern "C" fn rgblib_restore_keys(
380384
bitcoin_network: *const c_char,
381385
mnemonic: *const c_char,
386+
witness_version: *const c_char,
382387
) -> CResultString {
383-
restore_keys(bitcoin_network, mnemonic).into()
388+
restore_keys(bitcoin_network, mnemonic, witness_version).into()
384389
}
385390

386391
#[unsafe(no_mangle)]

bindings/c-ffi/src/utils.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,9 +295,13 @@ pub(crate) fn finalize_psbt(
295295
Ok(wallet.finalize_psbt(signed_psbt, None)?)
296296
}
297297

298-
pub(crate) fn generate_keys(bitcoin_network: *const c_char) -> Result<String, Error> {
298+
pub(crate) fn generate_keys(
299+
bitcoin_network: *const c_char,
300+
witness_version: *const c_char,
301+
) -> Result<String, Error> {
299302
let bitcoin_network = BitcoinNetwork::from_str(&ptr_to_string(bitcoin_network))?;
300-
let res = rgb_lib::keys::generate_keys(bitcoin_network);
303+
let witness_version = WitnessVersion::from_str(&ptr_to_string(witness_version))?;
304+
let res = rgb_lib::keys::generate_keys(bitcoin_network, witness_version);
301305
Ok(serde_json::to_string(&res)?)
302306
}
303307

@@ -558,10 +562,12 @@ pub(crate) fn restore_backup(
558562
pub(crate) fn restore_keys(
559563
bitcoin_network: *const c_char,
560564
mnemonic: *const c_char,
565+
witness_version: *const c_char,
561566
) -> Result<String, Error> {
562567
let bitcoin_network = BitcoinNetwork::from_str(&ptr_to_string(bitcoin_network))?;
563568
let mnemonic = ptr_to_string(mnemonic);
564-
let res = rgb_lib::keys::restore_keys(bitcoin_network, mnemonic)?;
569+
let witness_version = WitnessVersion::from_str(&ptr_to_string(witness_version))?;
570+
let res = rgb_lib::keys::restore_keys(bitcoin_network, mnemonic, witness_version)?;
565571
Ok(serde_json::to_string(&res)?)
566572
}
567573

bindings/uniffi/src/lib.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::{
99
use rgb_lib::{
1010
AssetSchema, Assignment as RgbLibAssignment, CloseMethod, Error as RgbLibError, TransferStatus,
1111
TransportType,
12-
keys::Keys,
12+
keys::{Keys, WitnessVersion},
1313
utils::BitcoinNetwork,
1414
wallet::{
1515
Address as RgbLibAddress, AssetCFA, AssetIFA, AssetNIA, AssetUDA, Assets,
@@ -690,12 +690,16 @@ impl From<RespondToOperation> for RgbLibRespondToOperation {
690690
}
691691
}
692692

693-
fn generate_keys(bitcoin_network: BitcoinNetwork) -> Keys {
694-
rgb_lib::keys::generate_keys(bitcoin_network)
693+
fn generate_keys(bitcoin_network: BitcoinNetwork, witness_version: WitnessVersion) -> Keys {
694+
rgb_lib::keys::generate_keys(bitcoin_network, witness_version)
695695
}
696696

697-
fn restore_keys(bitcoin_network: BitcoinNetwork, mnemonic: String) -> Result<Keys, RgbLibError> {
698-
rgb_lib::keys::restore_keys(bitcoin_network, mnemonic)
697+
fn restore_keys(
698+
bitcoin_network: BitcoinNetwork,
699+
mnemonic: String,
700+
witness_version: WitnessVersion,
701+
) -> Result<Keys, RgbLibError> {
702+
rgb_lib::keys::restore_keys(bitcoin_network, mnemonic, witness_version)
699703
}
700704

701705
fn restore_backup(

bindings/uniffi/src/rgb-lib.udl

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
namespace rgb_lib {
2-
Keys generate_keys(BitcoinNetwork bitcoin_network);
2+
Keys generate_keys(BitcoinNetwork bitcoin_network, WitnessVersion witness_version);
33

44
[Throws=RgbLibError]
5-
Keys restore_keys(BitcoinNetwork bitcoin_network, string mnemonic);
5+
Keys restore_keys(BitcoinNetwork bitcoin_network, string mnemonic, WitnessVersion witness_version);
66

77
[Throws=RgbLibError]
88
void restore_backup(string backup_path, string password, string data_dir);
@@ -71,6 +71,7 @@ interface RgbLibError {
7171
InvalidTransportEndpoints(string details);
7272
InvalidTxid();
7373
InvalidVanillaKeychain();
74+
InvalidWitnessVersion(string witness_version);
7475
MaxFeeExceeded(string txid);
7576
MinFeeNotMet(string txid);
7677
MultisigHubService(string details);
@@ -271,6 +272,12 @@ enum DatabaseType {
271272
"Sqlite",
272273
};
273274

275+
[Remote]
276+
enum WitnessVersion {
277+
"SegWitV0",
278+
"Taproot",
279+
};
280+
274281
interface Address {
275282
[Throws=RgbLibError]
276283
constructor(string address_string, BitcoinNetwork bitcoin_network);
@@ -330,6 +337,7 @@ dictionary Keys {
330337
string account_xpub_vanilla;
331338
string account_xpub_colored;
332339
string master_fingerprint;
340+
WitnessVersion witness_version;
333341
};
334342

335343
[Remote]
@@ -525,6 +533,7 @@ dictionary SinglesigKeys {
525533
u8? vanilla_keychain;
526534
string master_fingerprint;
527535
string? mnemonic;
536+
WitnessVersion witness_version;
528537
};
529538

530539
[Remote]

src/error.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,13 @@ pub enum Error {
373373
#[error("Invalid vanilla keychain")]
374374
InvalidVanillaKeychain,
375375

376+
/// Invalid witness version
377+
#[error("Invalid witness version: {witness_version}")]
378+
InvalidWitnessVersion {
379+
/// The invalid witness version
380+
witness_version: String,
381+
},
382+
376383
/// The maximum fee has been exceeded
377384
#[error("Max fee exceeded for transfer with TXID: {txid}")]
378385
MaxFeeExceeded {

src/keys.rs

Lines changed: 111 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,54 @@
44
55
use 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)]
68125
mod 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
}

src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@
3939
//! ## Examples
4040
//! ### Create an RGB singlesig wallet
4141
//! ```
42-
//! use rgb_lib::keys::generate_keys;
42+
//! use rgb_lib::keys::{generate_keys, WitnessVersion};
4343
//! use rgb_lib::wallet::{DatabaseType, SinglesigKeys, Wallet, WalletData};
4444
//! use rgb_lib::{AssetSchema, BitcoinNetwork};
4545
//!
4646
//! fn main() -> Result<(), rgb_lib::Error> {
4747
//! let data_dir = tempfile::tempdir()?;
48-
//! let keys = generate_keys(BitcoinNetwork::Regtest);
48+
//! let keys = generate_keys(BitcoinNetwork::Regtest, WitnessVersion::Taproot);
4949
//! let single_sig_keys = SinglesigKeys::from_keys(&keys, None);
5050
//! let wallet_data = WalletData {
5151
//! data_dir: data_dir.path().to_str().unwrap().to_string(),
@@ -300,7 +300,7 @@ use crate::{
300300
enums::{ColoringType, RecipientTypeFull, WalletTransactionType},
301301
},
302302
error::InternalError,
303-
keys::Keys,
303+
keys::{Keys, WitnessVersion},
304304
utils::{
305305
ACCOUNT, DumbResolver, KEYCHAIN_BTC, KEYCHAIN_RGB, LOG_FILE, PURPOSE, RgbRuntime,
306306
adjust_canonicalization, beneficiary_from_script_buf, from_str_or_number_mandatory,

0 commit comments

Comments
 (0)