fix(core): avoid bdk bip39 helper; use local bip39 -> xpub descriptors; normalize PSBT serialization; remove stale imports

This commit is contained in:
MoCipher 2026-02-20 09:32:41 +01:00
parent 3ed29dc179
commit 1c15e8f150
2 changed files with 27 additions and 20 deletions

View file

@ -1,5 +1,5 @@
use aes_gcm::{Aes256Gcm, Key, Nonce}; // Or `AesGcm` use aes_gcm::{Aes256Gcm, Key, Nonce}; // Or `AesGcm`
use aes_gcm::aead::{Aead, NewAead}; use aes_gcm::aead::Aead;
use argon2::{Argon2, password_hash::{SaltString, PasswordHasher, PasswordVerifier}, PasswordHash, PasswordHasher as _}; use argon2::{Argon2, password_hash::{SaltString, PasswordHasher, PasswordVerifier}, PasswordHash, PasswordHasher as _};
use rand::rngs::OsRng; use rand::rngs::OsRng;
use rand::RngCore; use rand::RngCore;

View file

@ -2,11 +2,13 @@ use bdk::Wallet;
use bdk::database::{MemoryDatabase, SqliteDatabase}; use bdk::database::{MemoryDatabase, SqliteDatabase};
use bdk::wallet::AddressIndex; use bdk::wallet::AddressIndex;
use bdk::blockchain::{noop_progress::NoopProgress, ElectrumBlockchain, ElectrumBlockchainConfig}; use bdk::blockchain::{noop_progress::NoopProgress, ElectrumBlockchain, ElectrumBlockchainConfig};
use bdk::keys::bip39::{Mnemonic as BdkMnemonic}; use crate::bip39::Mnemonic as LocalMnemonic;
use bdk::descriptor::DescriptorTemplateOut; use bdk::descriptor::DescriptorTemplateOut;
use bitcoin::util::bip32::ExtendedPubKey;
use bitcoin::Network; use bitcoin::Network;
use anyhow::Result; use anyhow::Result;
use std::path::Path; use std::path::Path;
use bitcoin::consensus::encode as consensus_encode;
/// Minimal BDK wallet scaffold with Electrum support (in-memory) /// Minimal BDK wallet scaffold with Electrum support (in-memory)
pub struct BdkWallet { pub struct BdkWallet {
@ -16,14 +18,14 @@ pub struct BdkWallet {
impl BdkWallet { impl BdkWallet {
/// Create a new BDK wallet from a BIP39 mnemonic (and passphrase) for the specified network /// Create a new BDK wallet from a BIP39 mnemonic (and passphrase) for the specified network
pub fn new_from_mnemonic(phrase: &str, passphrase: &str, network: Network) -> Result<Self> { pub fn new_from_mnemonic(phrase: &str, passphrase: &str, network: Network) -> Result<Self> {
// Use BDK's bip39 helper to create descriptor templates // Derive master xprv using our local bip39 helper and build simple `wpkh(<xpub>/0/*)` descriptors.
let mnemonic = BdkMnemonic::from_str(phrase).map_err(|e| anyhow::anyhow!(e.to_string()))?; let local_m = LocalMnemonic::from_phrase(phrase).map_err(|e| anyhow::anyhow!(e.to_string()))?;
let xkey: DescriptorTemplateOut = bdk::keys::bip39::translate_mnemonic(&mnemonic, passphrase, network) let xprv = local_m.to_master_xprv(passphrase, network);
.map_err(|e| anyhow::anyhow!(e.to_string()))?; // neuter to xpub for descriptor
let secp = secp256k1::Secp256k1::new();
// For demo, use the first "external" descriptor for a single-key wallet let xpub = ExtendedPubKey::from_private(&secp, &xprv);
let descriptor = xkey.external.clone(); let descriptor = format!("wpkh({}/0/*)", xpub.to_string());
let change_descriptor = xkey.internal.clone(); let change_descriptor = format!("wpkh({}/1/*)", xpub.to_string());
let wallet = Wallet::new_offline(&descriptor, Some(&change_descriptor), network, MemoryDatabase::default())?; let wallet = Wallet::new_offline(&descriptor, Some(&change_descriptor), network, MemoryDatabase::default())?;
Ok(Self { wallet }) Ok(Self { wallet })
@ -49,7 +51,8 @@ impl BdkWallet {
let mut builder = bdk::TxBuilder::new(); let mut builder = bdk::TxBuilder::new();
builder = builder.add_recipient(addr.script_pubkey(), satoshis); builder = builder.add_recipient(addr.script_pubkey(), satoshis);
let (psbt, _details) = self.wallet.build_tx(builder)?; let (psbt, _details) = self.wallet.build_tx(builder)?;
let bs = base64::encode(&psbt.serialize()); let serialized = consensus_encode::serialize(&psbt);
let bs = base64::encode(&serialized);
Ok(bs) Ok(bs)
} }
@ -58,7 +61,8 @@ impl BdkWallet {
let raw = base64::decode(psbt_b64)?; let raw = base64::decode(psbt_b64)?;
let mut psbt: bitcoin::util::psbt::PartiallySignedTransaction = bitcoin::consensus::deserialize(&raw)?; let mut psbt: bitcoin::util::psbt::PartiallySignedTransaction = bitcoin::consensus::deserialize(&raw)?;
self.wallet.sign(&mut psbt, bdk::SignOptions::default())?; self.wallet.sign(&mut psbt, bdk::SignOptions::default())?;
let out = base64::encode(&psbt.serialize()); let serialized = consensus_encode::serialize(&psbt);
let out = base64::encode(&serialized);
Ok(out) Ok(out)
} }
@ -78,12 +82,13 @@ impl PersistentBdkWallet {
/// Create or open a persistent wallet using a SQLite DB file at `db_path`. /// Create or open a persistent wallet using a SQLite DB file at `db_path`.
/// `db_path` should be a filesystem path like `/path/to/wallet.db`. /// `db_path` should be a filesystem path like `/path/to/wallet.db`.
pub fn new_from_mnemonic_sqlite(phrase: &str, passphrase: &str, network: Network, db_path: &str) -> Result<Self> { pub fn new_from_mnemonic_sqlite(phrase: &str, passphrase: &str, network: Network, db_path: &str) -> Result<Self> {
let mnemonic = BdkMnemonic::from_str(phrase).map_err(|e| anyhow::anyhow!(e.to_string()))?; // derive master xprv & descriptors just like in BdkWallet
let xkey: DescriptorTemplateOut = bdk::keys::bip39::translate_mnemonic(&mnemonic, passphrase, network) let local_m = LocalMnemonic::from_phrase(phrase).map_err(|e| anyhow::anyhow!(e.to_string()))?;
.map_err(|e| anyhow::anyhow!(e.to_string()))?; let xprv = local_m.to_master_xprv(passphrase, network);
let secp = secp256k1::Secp256k1::new();
let descriptor = xkey.external.clone(); let xpub = ExtendedPubKey::from_private(&secp, &xprv);
let change_descriptor = xkey.internal.clone(); let descriptor = format!("wpkh({}/0/*)", xpub.to_string());
let change_descriptor = format!("wpkh({}/1/*)", xpub.to_string());
// Ensure directory exists // Ensure directory exists
let p = Path::new(db_path); let p = Path::new(db_path);
@ -116,7 +121,8 @@ impl PersistentBdkWallet {
let mut builder = bdk::TxBuilder::new(); let mut builder = bdk::TxBuilder::new();
builder = builder.add_recipient(addr.script_pubkey(), satoshis); builder = builder.add_recipient(addr.script_pubkey(), satoshis);
let (psbt, _details) = self.wallet.build_tx(builder)?; let (psbt, _details) = self.wallet.build_tx(builder)?;
let bs = base64::encode(&psbt.serialize()); let serialized = consensus_encode::serialize(&psbt);
let bs = base64::encode(&serialized);
Ok(bs) Ok(bs)
} }
@ -124,7 +130,8 @@ impl PersistentBdkWallet {
let raw = base64::decode(psbt_b64)?; let raw = base64::decode(psbt_b64)?;
let mut psbt: bitcoin::util::psbt::PartiallySignedTransaction = bitcoin::consensus::deserialize(&raw)?; let mut psbt: bitcoin::util::psbt::PartiallySignedTransaction = bitcoin::consensus::deserialize(&raw)?;
self.wallet.sign(&mut psbt, bdk::SignOptions::default())?; self.wallet.sign(&mut psbt, bdk::SignOptions::default())?;
let out = base64::encode(&psbt.serialize()); let serialized = consensus_encode::serialize(&psbt);
let out = base64::encode(&serialized);
Ok(out) Ok(out)
} }