Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion bindings/c-ffi/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

int main() {
const char *bitcoin_network = "Regtest";
CResultString keys_res = rgblib_generate_keys(bitcoin_network);
const char *witness_version = "Taproot";
CResultString keys_res =
rgblib_generate_keys(bitcoin_network, witness_version);
if (keys_res.result == Err) {
printf("ERR: %s\n", keys_res.inner);
return EXIT_FAILURE;
Expand Down
11 changes: 8 additions & 3 deletions bindings/c-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::{

use rgb_lib::{
AssetSchema, Assignment, Error as RgbLibError,
keys::WitnessVersion,
utils::BitcoinNetwork,
wallet::{
Online, Recipient, RefreshFilter, RgbWalletOpsOffline, RgbWalletOpsOnline, SinglesigKeys,
Expand Down Expand Up @@ -171,8 +172,11 @@ pub extern "C" fn rgblib_finalize_psbt(
}

#[unsafe(no_mangle)]
pub extern "C" fn rgblib_generate_keys(bitcoin_network: *const c_char) -> CResultString {
generate_keys(bitcoin_network).into()
pub extern "C" fn rgblib_generate_keys(
bitcoin_network: *const c_char,
witness_version: *const c_char,
) -> CResultString {
generate_keys(bitcoin_network, witness_version).into()
}

#[unsafe(no_mangle)]
Expand Down Expand Up @@ -379,8 +383,9 @@ pub extern "C" fn rgblib_restore_backup(
pub extern "C" fn rgblib_restore_keys(
bitcoin_network: *const c_char,
mnemonic: *const c_char,
witness_version: *const c_char,
) -> CResultString {
restore_keys(bitcoin_network, mnemonic).into()
restore_keys(bitcoin_network, mnemonic, witness_version).into()
}

#[unsafe(no_mangle)]
Expand Down
12 changes: 9 additions & 3 deletions bindings/c-ffi/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,13 @@ pub(crate) fn finalize_psbt(
Ok(wallet.finalize_psbt(signed_psbt, None)?)
}

pub(crate) fn generate_keys(bitcoin_network: *const c_char) -> Result<String, Error> {
pub(crate) fn generate_keys(
bitcoin_network: *const c_char,
witness_version: *const c_char,
) -> Result<String, Error> {
let bitcoin_network = BitcoinNetwork::from_str(&ptr_to_string(bitcoin_network))?;
let res = rgb_lib::keys::generate_keys(bitcoin_network);
let witness_version = WitnessVersion::from_str(&ptr_to_string(witness_version))?;
let res = rgb_lib::keys::generate_keys(bitcoin_network, witness_version);
Ok(serde_json::to_string(&res)?)
}

Expand Down Expand Up @@ -558,10 +562,12 @@ pub(crate) fn restore_backup(
pub(crate) fn restore_keys(
bitcoin_network: *const c_char,
mnemonic: *const c_char,
witness_version: *const c_char,
) -> Result<String, Error> {
let bitcoin_network = BitcoinNetwork::from_str(&ptr_to_string(bitcoin_network))?;
let mnemonic = ptr_to_string(mnemonic);
let res = rgb_lib::keys::restore_keys(bitcoin_network, mnemonic)?;
let witness_version = WitnessVersion::from_str(&ptr_to_string(witness_version))?;
let res = rgb_lib::keys::restore_keys(bitcoin_network, mnemonic, witness_version)?;
Ok(serde_json::to_string(&res)?)
}

Expand Down
14 changes: 9 additions & 5 deletions bindings/uniffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::{
use rgb_lib::{
AssetSchema, Assignment as RgbLibAssignment, CloseMethod, Error as RgbLibError, TransferStatus,
TransportType,
keys::Keys,
keys::{Keys, WitnessVersion},
utils::BitcoinNetwork,
wallet::{
Address as RgbLibAddress, AssetCFA, AssetIFA, AssetNIA, AssetUDA, Assets,
Expand Down Expand Up @@ -690,12 +690,16 @@ impl From<RespondToOperation> for RgbLibRespondToOperation {
}
}

fn generate_keys(bitcoin_network: BitcoinNetwork) -> Keys {
rgb_lib::keys::generate_keys(bitcoin_network)
fn generate_keys(bitcoin_network: BitcoinNetwork, witness_version: WitnessVersion) -> Keys {
rgb_lib::keys::generate_keys(bitcoin_network, witness_version)
}

fn restore_keys(bitcoin_network: BitcoinNetwork, mnemonic: String) -> Result<Keys, RgbLibError> {
rgb_lib::keys::restore_keys(bitcoin_network, mnemonic)
fn restore_keys(
bitcoin_network: BitcoinNetwork,
mnemonic: String,
witness_version: WitnessVersion,
) -> Result<Keys, RgbLibError> {
rgb_lib::keys::restore_keys(bitcoin_network, mnemonic, witness_version)
}

fn restore_backup(
Expand Down
13 changes: 11 additions & 2 deletions bindings/uniffi/src/rgb-lib.udl
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
namespace rgb_lib {
Keys generate_keys(BitcoinNetwork bitcoin_network);
Keys generate_keys(BitcoinNetwork bitcoin_network, WitnessVersion witness_version);

[Throws=RgbLibError]
Keys restore_keys(BitcoinNetwork bitcoin_network, string mnemonic);
Keys restore_keys(BitcoinNetwork bitcoin_network, string mnemonic, WitnessVersion witness_version);

[Throws=RgbLibError]
void restore_backup(string backup_path, string password, string data_dir);
Expand Down Expand Up @@ -71,6 +71,7 @@ interface RgbLibError {
InvalidTransportEndpoints(string details);
InvalidTxid();
InvalidVanillaKeychain();
InvalidWitnessVersion(string witness_version);
MaxFeeExceeded(string txid);
MinFeeNotMet(string txid);
MultisigHubService(string details);
Expand Down Expand Up @@ -271,6 +272,12 @@ enum DatabaseType {
"Sqlite",
};

[Remote]
enum WitnessVersion {
"SegWitV0",
"Taproot",
};

interface Address {
[Throws=RgbLibError]
constructor(string address_string, BitcoinNetwork bitcoin_network);
Expand Down Expand Up @@ -330,6 +337,7 @@ dictionary Keys {
string account_xpub_vanilla;
string account_xpub_colored;
string master_fingerprint;
WitnessVersion witness_version;
};

[Remote]
Expand Down Expand Up @@ -525,6 +533,7 @@ dictionary SinglesigKeys {
u8? vanilla_keychain;
string master_fingerprint;
string? mnemonic;
WitnessVersion witness_version;
};

[Remote]
Expand Down
7 changes: 7 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,13 @@ pub enum Error {
#[error("Invalid vanilla keychain")]
InvalidVanillaKeychain,

/// Invalid witness version
#[error("Invalid witness version: {witness_version}")]
InvalidWitnessVersion {
/// The invalid witness version
witness_version: String,
},

/// The maximum fee has been exceeded
#[error("Max fee exceeded for transfer with TXID: {txid}")]
MaxFeeExceeded {
Expand Down
130 changes: 111 additions & 19 deletions src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,54 @@

use super::*;

/// Supported bitcoin witness versions.
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum WitnessVersion {
/// Segregated Witness version 0
SegWitV0,
/// Segregated Witness version 1
#[default]
Taproot,
}
Comment thread
dcorral marked this conversation as resolved.

impl WitnessVersion {
pub(crate) fn purpose(&self) -> u32 {
match self {
WitnessVersion::SegWitV0 => 84,
WitnessVersion::Taproot => 86,
}
}

pub(crate) fn descriptor_fn(&self) -> &'static str {
match self {
WitnessVersion::SegWitV0 => "wpkh",
WitnessVersion::Taproot => "tr",
}
}
}

impl fmt::Display for WitnessVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
}
}

impl FromStr for WitnessVersion {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s.to_lowercase().as_str() {
"segwitv0" => WitnessVersion::SegWitV0,
"taproot" => WitnessVersion::Taproot,
_ => {
return Err(Error::InvalidWitnessVersion {
witness_version: s.to_string(),
});
}
})
}
}

/// A set of Bitcoin keys used by the wallet.
#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "camel_case", serde(rename_all = "camelCase"))]
Expand All @@ -18,10 +66,13 @@ pub struct Keys {
pub account_xpub_colored: String,
/// Fingerprint of the master xPub
pub master_fingerprint: String,
/// Witness version these keys were derived with
#[serde(default)]
pub witness_version: WitnessVersion,
}

/// Generate a set of [`Keys`] for the given Bitcoin network.
pub fn generate_keys(bitcoin_network: BitcoinNetwork) -> Keys {
/// Generate a set of [`Keys`] for the given Bitcoin network and witness version.
pub fn generate_keys(bitcoin_network: BitcoinNetwork, witness_version: WitnessVersion) -> Keys {
let bdk_network = BdkNetwork::from(bitcoin_network);
let mnemonic = Mnemonic::generate((WordCount::Words12, Language::English))
.expect("to be able to generate a new mnemonic");
Expand All @@ -32,22 +83,27 @@ pub fn generate_keys(bitcoin_network: BitcoinNetwork) -> Keys {
let xpub = &xkey.into_xpub(bdk_network, &Secp256k1::new());
let mnemonic_str = mnemonic.to_string();
let (account_xpub_vanilla, account_xpub_colored) =
get_account_xpubs(&bitcoin_network, &mnemonic_str).unwrap();
get_account_xpubs(&bitcoin_network, &mnemonic_str, witness_version).unwrap();
let master_fingerprint = xpub.fingerprint().to_string();
Keys {
mnemonic: mnemonic_str,
xpub: xpub.clone().to_string(),
account_xpub_vanilla: account_xpub_vanilla.to_string(),
account_xpub_colored: account_xpub_colored.to_string(),
master_fingerprint,
witness_version,
}
}

/// Recreate a set of [`Keys`] from the given mnemonic phrase.
pub fn restore_keys(bitcoin_network: BitcoinNetwork, mnemonic: String) -> Result<Keys, Error> {
/// Recreate a set of [`Keys`] from the given mnemonic phrase for the given witness version.
pub fn restore_keys(
bitcoin_network: BitcoinNetwork,
mnemonic: String,
witness_version: WitnessVersion,
) -> Result<Keys, Error> {
let bdk_network = BdkNetwork::from(bitcoin_network);
let (account_xpub_vanilla, account_xpub_colored) =
get_account_xpubs(&bitcoin_network, &mnemonic)?;
get_account_xpubs(&bitcoin_network, &mnemonic, witness_version)?;
let mnemonic_parsed = Mnemonic::parse_in(Language::English, &mnemonic)?;
let xkey: ExtendedKey = mnemonic_parsed
.clone()
Expand All @@ -61,13 +117,31 @@ pub fn restore_keys(bitcoin_network: BitcoinNetwork, mnemonic: String) -> Result
account_xpub_vanilla: account_xpub_vanilla.to_string(),
account_xpub_colored: account_xpub_colored.to_string(),
master_fingerprint,
witness_version,
})
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn witness_version_display_and_parse() {
for wv in [WitnessVersion::SegWitV0, WitnessVersion::Taproot] {
let s = wv.to_string();
assert_eq!(WitnessVersion::from_str(&s).unwrap(), wv);
assert_eq!(WitnessVersion::from_str(&s.to_lowercase()).unwrap(), wv);
}

let err = WitnessVersion::from_str("nonsense").unwrap_err();
assert_eq!(
err,
Error::InvalidWitnessVersion {
witness_version: "nonsense".to_string(),
},
);
}

#[test]
fn generate_success() {
let Keys {
Expand All @@ -76,7 +150,8 @@ mod test {
account_xpub_vanilla,
account_xpub_colored,
master_fingerprint,
} = generate_keys(BitcoinNetwork::Regtest);
witness_version,
} = generate_keys(BitcoinNetwork::Regtest, WitnessVersion::Taproot);

assert!(Mnemonic::from_str(&mnemonic).is_ok());
let pubkey = Xpub::from_str(&xpub);
Expand All @@ -89,23 +164,40 @@ mod test {
assert!(account_pubkey_rgb.is_ok());
let account_pubkey_btc = Xpub::from_str(&account_xpub_vanilla);
assert!(account_pubkey_btc.is_ok());
assert_eq!(witness_version, WitnessVersion::Taproot);
}

#[test]
fn restore_success() {
let network = BitcoinNetwork::Regtest;
let Keys {
mnemonic,
xpub,
account_xpub_vanilla,
account_xpub_colored,
master_fingerprint,
} = generate_keys(network);

let keys = restore_keys(network, mnemonic).unwrap();
assert_eq!(keys.xpub, xpub);
assert_eq!(keys.master_fingerprint, master_fingerprint);
assert_eq!(keys.account_xpub_colored, account_xpub_colored);
assert_eq!(keys.account_xpub_vanilla, account_xpub_vanilla);
// round-trip generate → restore for each supported witness version
for wv in [WitnessVersion::Taproot, WitnessVersion::SegWitV0] {
let Keys {
mnemonic,
xpub,
account_xpub_vanilla,
account_xpub_colored,
master_fingerprint,
witness_version,
} = generate_keys(network, wv);

let keys = restore_keys(network, mnemonic, witness_version).unwrap();
assert_eq!(keys.xpub, xpub);
assert_eq!(keys.master_fingerprint, master_fingerprint);
assert_eq!(keys.account_xpub_colored, account_xpub_colored);
assert_eq!(keys.account_xpub_vanilla, account_xpub_vanilla);
assert_eq!(keys.witness_version, witness_version);
assert_eq!(witness_version, wv);
}

// same mnemonic + different witness versions ⇒ same master xpub
// and fingerprint but different account xpubs (different BIP purpose)
let tr = generate_keys(network, WitnessVersion::Taproot);
let wpkh = restore_keys(network, tr.mnemonic.clone(), WitnessVersion::SegWitV0).unwrap();
assert_eq!(tr.xpub, wpkh.xpub);
assert_eq!(tr.master_fingerprint, wpkh.master_fingerprint);
assert_ne!(tr.account_xpub_colored, wpkh.account_xpub_colored);
assert_ne!(tr.account_xpub_vanilla, wpkh.account_xpub_vanilla);
}
}
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@
//! ## Examples
//! ### Create an RGB singlesig wallet
//! ```
//! use rgb_lib::keys::generate_keys;
//! use rgb_lib::keys::{generate_keys, WitnessVersion};
//! use rgb_lib::wallet::{DatabaseType, SinglesigKeys, Wallet, WalletData};
//! use rgb_lib::{AssetSchema, BitcoinNetwork};
//!
//! fn main() -> Result<(), rgb_lib::Error> {
//! let data_dir = tempfile::tempdir()?;
//! let keys = generate_keys(BitcoinNetwork::Regtest);
//! let keys = generate_keys(BitcoinNetwork::Regtest, WitnessVersion::Taproot);
//! let single_sig_keys = SinglesigKeys::from_keys(&keys, None);
//! let wallet_data = WalletData {
//! data_dir: data_dir.path().to_str().unwrap().to_string(),
Expand Down Expand Up @@ -300,7 +300,7 @@ use crate::{
enums::{ColoringType, RecipientTypeFull, WalletTransactionType},
},
error::InternalError,
keys::Keys,
keys::{Keys, WitnessVersion},
utils::{
ACCOUNT, DumbResolver, KEYCHAIN_BTC, KEYCHAIN_RGB, LOG_FILE, PURPOSE, RgbRuntime,
adjust_canonicalization, beneficiary_from_script_buf, from_str_or_number_mandatory,
Expand Down
Loading
Loading