Skip to content

Commit 94b84cd

Browse files
Merge pull request #177 from diba-io/HT/descriptor-migration
v4 descriptor migration
2 parents b836d93 + 5ceee89 commit 94b84cd

6 files changed

Lines changed: 129 additions & 7 deletions

File tree

src/bitcoin.rs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::str::FromStr;
22

33
pub use ::bitcoin::util::address::Address;
4-
use anyhow::Result;
4+
use anyhow::{anyhow, Result};
55
pub use bdk::{wallet::AddressIndex, FeeRate, LocalUtxo, TransactionDetails};
66
pub use bitcoin_hashes::{sha256, Hash};
77
use serde_encrypt::{
@@ -16,6 +16,7 @@ mod payment;
1616
mod psbt;
1717
mod wallet;
1818

19+
use crate::structs::EncryptedWalletDataV04;
1920
pub use crate::{
2021
bitcoin::{
2122
assets::dust_tx,
@@ -33,7 +34,11 @@ pub use crate::{
3334
};
3435

3536
impl SerdeEncryptSharedKey for EncryptedWalletData {
36-
type S = BincodeSerializer<Self>; // you can specify serializer implementation (or implement it by yourself).
37+
type S = BincodeSerializer<Self>;
38+
}
39+
40+
impl SerdeEncryptSharedKey for EncryptedWalletDataV04 {
41+
type S = BincodeSerializer<Self>;
3742
}
3843

3944
/// Bitcoin Wallet Operations
@@ -47,12 +52,45 @@ pub fn get_encrypted_wallet(
4752
let shared_key: [u8; 32] = hash.into_inner();
4853
let encrypted_descriptors: Vec<u8> = hex::decode(encrypted_descriptors)?;
4954
let encrypted_message = EncryptedMessage::deserialize(encrypted_descriptors)?;
55+
5056
Ok(EncryptedWalletData::decrypt_owned(
5157
&encrypted_message,
5258
&SharedKey::from_array(shared_key),
5359
)?)
5460
}
5561

62+
pub async fn upgrade_wallet(
63+
password: &str,
64+
encrypted_descriptors: &str,
65+
seed_password: &str,
66+
) -> Result<String> {
67+
// read hash digest and consume hasher
68+
let hash = sha256::Hash::hash(password.as_bytes());
69+
let shared_key: [u8; 32] = hash.into_inner();
70+
let encrypted_descriptors: Vec<u8> = hex::decode(encrypted_descriptors)?;
71+
let encrypted_message = EncryptedMessage::deserialize(encrypted_descriptors)?;
72+
73+
match EncryptedWalletData::decrypt_owned(&encrypted_message, &SharedKey::from_array(shared_key))
74+
{
75+
Ok(_data) => Err(anyhow!("Descriptor does not need to be upgraded")),
76+
Err(_err) => {
77+
// If there's a deserialization error, attempt to recover just the mnemnonic.
78+
let recovered_wallet_data = EncryptedWalletDataV04::decrypt_owned(
79+
&encrypted_message,
80+
&SharedKey::from_array(shared_key),
81+
)?;
82+
83+
// println!("Recovered wallet data: {recovered_wallet_data:?}"); // Keep commented out for security
84+
85+
let upgraded_descriptor =
86+
save_mnemonic_seed(&recovered_wallet_data.mnemonic, password, seed_password)
87+
.await?;
88+
89+
Ok(upgraded_descriptor.serialized_encrypted_message)
90+
}
91+
}
92+
}
93+
5694
pub async fn new_mnemonic_seed(
5795
encryption_password: &str,
5896
seed_password: &str,
@@ -64,7 +102,7 @@ pub async fn new_mnemonic_seed(
64102
let encrypted_message = encrypted_wallet_data.encrypt(&SharedKey::from_array(shared_key))?;
65103
let serialized_encrypted_message = hex::encode(encrypted_message.serialize());
66104
let mnemonic_seed_data = MnemonicSeedData {
67-
mnemonic: encrypted_wallet_data.private.mnemonic,
105+
mnemonic: encrypted_wallet_data.mnemonic,
68106
serialized_encrypted_message,
69107
};
70108

@@ -83,7 +121,7 @@ pub async fn save_mnemonic_seed(
83121
let encrypted_message = vault_data.encrypt(&SharedKey::from_array(shared_key))?;
84122
let serialized_encrypted_message = hex::encode(encrypted_message.serialize());
85123
let mnemonic_seed_data = MnemonicSeedData {
86-
mnemonic: vault_data.private.mnemonic,
124+
mnemonic: vault_data.mnemonic,
87125
serialized_encrypted_message,
88126
};
89127

src/bitcoin/keys.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ pub async fn get_mnemonic(
122122
rgb_udas_descriptor_xprv,
123123
nostr_prv,
124124
nostr_nsec,
125-
mnemonic: mnemonic_phrase.to_string(),
126125
};
127126

128127
let public = PublicWalletData {
@@ -137,5 +136,9 @@ pub async fn get_mnemonic(
137136
xpub: xpub.to_string(),
138137
};
139138

140-
Ok(EncryptedWalletData { private, public })
139+
Ok(EncryptedWalletData {
140+
mnemonic: mnemonic_phrase.to_string(),
141+
private,
142+
public,
143+
})
141144
}

src/structs.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ pub struct PrivateWalletData {
3232
pub rgb_udas_descriptor_xprv: String,
3333
pub nostr_prv: String,
3434
pub nostr_nsec: String,
35-
pub mnemonic: String,
3635
}
3736

3837
#[derive(Serialize, Deserialize, Clone, Debug)]
@@ -52,10 +51,27 @@ pub struct PublicWalletData {
5251
#[derive(Serialize, Deserialize, Clone, Debug)]
5352
#[serde(rename_all = "camelCase")]
5453
pub struct EncryptedWalletData {
54+
pub mnemonic: String,
5555
pub private: PrivateWalletData,
5656
pub public: PublicWalletData,
5757
}
5858

59+
#[derive(Serialize, Deserialize, Clone, Debug)]
60+
#[serde(rename_all = "camelCase")]
61+
pub struct EncryptedWalletDataV04 {
62+
pub btc_descriptor_xprv: String,
63+
pub btc_descriptor_xpub: String,
64+
pub btc_change_descriptor_xprv: String,
65+
pub btc_change_descriptor_xpub: String,
66+
pub rgb_assets_descriptor_xprv: String,
67+
pub rgb_assets_descriptor_xpub: String,
68+
pub rgb_udas_descriptor_xprv: String,
69+
pub rgb_udas_descriptor_xpub: String,
70+
pub xprvkh: String,
71+
pub xpubkh: String,
72+
pub mnemonic: String,
73+
}
74+
5975
#[derive(Serialize, Deserialize, Clone, Debug)]
6076
#[serde(rename_all = "camelCase")]
6177
pub struct MnemonicSeedData {

src/web.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,26 @@ pub mod bitcoin {
110110
})
111111
}
112112

113+
#[wasm_bindgen]
114+
pub fn upgrade_wallet(
115+
password: String,
116+
encrypted_descriptors: String,
117+
seed_password: String,
118+
) -> Promise {
119+
set_panic_hook();
120+
121+
future_to_promise(async move {
122+
match crate::bitcoin::upgrade_wallet(&password, &encrypted_descriptors, &seed_password)
123+
.await
124+
{
125+
Ok(result) => Ok(JsValue::from_string(
126+
serde_json::to_string(&result).unwrap(),
127+
)),
128+
Err(err) => Err(JsValue::from_string(err.to_string())),
129+
}
130+
})
131+
}
132+
113133
#[wasm_bindgen]
114134
pub fn get_mnemonic_seed(encryption_password: String, seed_password: String) -> Promise {
115135
set_panic_hook();

tests/migration.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#![cfg(not(target_arch = "wasm32"))]
2+
3+
use anyhow::Result;
4+
use bitmask_core::{
5+
bitcoin::{get_encrypted_wallet, upgrade_wallet},
6+
constants::switch_network,
7+
util::init_logging,
8+
};
9+
use log::{debug, info};
10+
11+
const ENCRYPTION_PASSWORD: &str = "asdfasdf";
12+
const ENCRYPTED_DESCRIPTOR_04: &str = "d80b2c6af514802c5e7b7a91e8c84f93edbe705f0849cf57abf5bfd465a0c4b2792e8fb16a1b76d7f65b8d68a65c3c001565318ddcd9905536391ca0abb68789da28bb3ccbc923760b36474876070fff4c67e2c23c79f9d5272513fa17dfa4ea47101c7fb0487a678eb40b37ccda73805ab821b63feb9abcc7c60bfa7aac2ff076d906fd542400fb81d8a4bf8905932f3db10a252a1cd9661515f996724545c7e732db17899e6210e80af14be0610b0db90143513586bf8670deaa14e05f66b556936c7cc6f82a3363ee7e77f8081205cb6a1a5ad6d627d4dae6c174cd1f2158384daa276a37d8ef6b51b8ae7c8351fa07b606beb2f083e8a2f64f8148e834f326c256cc274adee7e5d05a946e23e9a76c165fbc25ff5618e2936b5222f394066fa071f954ff6ee3446d67375ec5d1caa5d01d7722576ecdf6f67aff833dccb7ade77a1988959cb250a8723a1dff2d3b6b39877f0291c5a34fb2ccc01ba0b6f9bdf6b1d30a3309870ee5d4f828e5d45edc7c5c08ff94ca8fd572e1038ff6975031a5a48733b968cf28eaf90a5895b393ef1230b1632c800f96b95cd0e77e8c971d20311a38da90aa49cf9570be8ef9e97534f4364d1b5840a5214fc534fbe63e76e3d8aeb159cd446fbaabf2502c0efce9a899650344df9fdd499862c92749fd26ea5070a51d3768515d0716f12312e29ca450c231ccf8714e43aa548f7a9a8b883e62b33b290f723bbf1469094ed589a7d0fdb12fc76e6267825924fe616eebb24202c387d0326a9d3dab7245cca9abe28003c7f574e9d3e63854732e5fdc6edda4180384a3424ad6ab1109c6a49acc8eb99af347f0ce72637723a9b3377124324dc9e4ec5a3f3c4673eebb1e7e4d5b7fd7d2568987edb71853fdb1bbd922eff16cf5cac008e43a90ff281ceec9f4213a0d6c2a3df4d579aa1ab20003a4792421cbc7a7822faec1430018861c39380878993f75b6051642af06857f57ba9ad067b9537f19dc35f69fa72935fd4690935168a812e20874f586d63af04b5a4955e1734d2a5d3e7d69b8f9f136a5bff94de0f5a932a89fe00d535145e971510ac16d3ddcd3053d7727a0164d5f560c372d98f13d98e67a108753b5df4abe6bbc5536ee551bbdf28cbf311afa41f2826d338cc8a3f87411e3fa1d178ec21da27b9382b9480cb974aa588f7c7ba09e08fd5428019ac017164e10a2a585ca063c518db1514fe3081f4f393fc06fb6d0d1719d33e85dec3a17a506fdf860ec07dde0bcf3d77d345bd893f0f79cda14d55577a1f6b768ef0bf1e2c69d8c201348f6fa2748cc63bc397aad7afe629122188b0b806546237e60be07063d6f1372d36da554d95e741f6bca9c7ffdc48cdf367e9d5b893fd710e74f24c3f0d8194aa389d0ff9f6f9a5f93cf07c1ce5b67589d99a77aa5fb122c1b88d38f5d6e0d18ed66c9637a73fb9085fc4c43e73e7e0124e383d91407c9ddfa285450ef09889b03e6b1550da033e1edbf0cfcf346c9d1929c07d6853999b7f5eea341dee10547d543463434dacde09926d2db1a5788d70711437e530d93c3bfaf58d5500e4bff89a680951976089832b3e1382cb943cbe7be40c5e0770363d30887d5634681cd903927003334bdd10364c39c0b9457fbc9b7a3f23808971d091e20363934555c449840a763eaa39d6407db0550693b2649517ee0696f10244b3814c37e0473f80d1acf26fbe2a129d35809b22cc6d047612ca9344c1e1c6cbeeab3907fb331b1ad90232b3f6984a90d8e1b3e47397f43ca0c9ab5b1273cdf0c368bc537e31a63b278aa76dc282cdf2550549e694afc45f32be88b436392d2b4637ea81b74447fc6892c9608722845cd8d0518459804fc1eefa624bd12a24b8bef12a5bb857906a213e2d4bcb31b6983b004946798c38ece5e2d7d82c04034deedee709f94210cabc0a8916c9b465fdd49fe708b7c2862474bb43368ae42fd4f8e0e45e28b1d1619069d25d3368e53244943952bfbc36004bf88b8e19e3d";
13+
const SEED_PASSWORD: &str = "";
14+
15+
#[tokio::test]
16+
async fn migration() -> Result<()> {
17+
init_logging("migration=debug");
18+
19+
switch_network("testnet").await?;
20+
21+
info!("Import bitmask-core 0.4 encrypted descriptor");
22+
let wallet = get_encrypted_wallet(ENCRYPTION_PASSWORD, ENCRYPTED_DESCRIPTOR_04);
23+
24+
assert!(wallet.is_err(), "Importing an old descriptor should error");
25+
26+
let upgraded_descriptor =
27+
upgrade_wallet(ENCRYPTION_PASSWORD, ENCRYPTED_DESCRIPTOR_04, SEED_PASSWORD).await?;
28+
29+
debug!("Upgraded descriptor: {upgraded_descriptor}");
30+
31+
let wallet = get_encrypted_wallet(ENCRYPTION_PASSWORD, &upgraded_descriptor)?;
32+
33+
assert_eq!(
34+
wallet.public.xpub, "tpubD6NzVbkrYhZ4Xxrh54Ew5kjkagEfUhS3aCNqRJmUuNfnTXhK4LGXyUzZ5kxgn8f2txjnFtypnoYfRQ9Y8P2nhSNXffxVKutJgxNPxgmwpUR",
35+
"Upgraded wallet should upgrade the descriptor"
36+
);
37+
38+
Ok(())
39+
}

tests/rgb/integration/utils.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ use bitmask_core::{
77
};
88
use tokio::process::Command;
99

10+
#[allow(dead_code)]
1011
pub const REGTEST_MNEMONIC: &str =
1112
"ordinary crucial edit settle pencil lion appear unlock left fly century license";
1213

14+
#[allow(dead_code)]
1315
pub async fn start_node() {
1416
let path = env::current_dir().expect("");
1517
let path = path.to_str().expect("");
@@ -25,6 +27,7 @@ pub async fn start_node() {
2527
.expect("");
2628
}
2729

30+
#[allow(dead_code)]
2831
pub async fn stop_node() {
2932
let path = env::current_dir().expect("");
3033
let path = path.to_str().expect("");
@@ -40,6 +43,7 @@ pub async fn stop_node() {
4043
.expect("");
4144
}
4245

46+
#[allow(dead_code)]
4347
pub async fn send_some_coins(address: &str, amount: &str) {
4448
let path = env::current_dir().expect("");
4549
let path = path.to_str().expect("");
@@ -56,6 +60,7 @@ pub async fn send_some_coins(address: &str, amount: &str) {
5660
.expect("");
5761
}
5862

63+
#[allow(dead_code)]
5964
pub async fn setup_integration() -> anyhow::Result<EncryptedWalletData> {
6065
if env::var("RESET_DOCKER_ENV").is_ok() {
6166
// Start Node
@@ -75,6 +80,7 @@ pub async fn setup_integration() -> anyhow::Result<EncryptedWalletData> {
7580
Ok(vault_data)
7681
}
7782

83+
#[allow(dead_code)]
7884
pub async fn shutdown_integration() -> anyhow::Result<()> {
7985
if env::var("RESET_DOCKER_ENV").is_ok() {
8086
// Start Node

0 commit comments

Comments
 (0)