diff --git a/src/bdk.udl b/src/bdk.udl index e185deb53..caea977ec 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -134,6 +134,26 @@ interface Wallet { string broadcast([ByRef] PartiallySignedBitcoinTransaction psbt); }; +interface EcdsaMessageSigner { + [Throws=BdkError, Name=from_wif] + constructor(string wif); + [Throws=BdkError, Name=from_prv] + constructor(string prv); + [Throws=BdkError, Name=from_xprv] + constructor(string xprv); + string sign(string msg); +}; + +interface EcdsaMessageSignatureVerifier { + [Throws=BdkError, Name=from_pub] + constructor(string pubkey); + [Throws=BdkError, Name=from_xpub] + constructor(string xpub); + [Throws=BdkError, Name=from_address] + constructor(string address); + boolean verify(string sig, string msg); +}; + interface PartiallySignedBitcoinTransaction { [Throws=BdkError] constructor(string psbt_base64); @@ -153,6 +173,7 @@ interface TxBuilder { dictionary ExtendedKeyInfo { string mnemonic; string xprv; + string xpub; string fingerprint; }; diff --git a/src/lib.rs b/src/lib.rs index bdfec3c17..6a2cf493d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ use bdk::bitcoin::hashes::hex::ToHex; -use bdk::bitcoin::secp256k1::Secp256k1; +use bdk::bitcoin::secp256k1::{All, Message, Secp256k1, SecretKey}; use bdk::bitcoin::util::psbt::PartiallySignedTransaction; -use bdk::bitcoin::{Address, Network, Script}; +use bdk::bitcoin::{Address, Network, PrivateKey, PublicKey, Script}; use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; use bdk::blockchain::Progress; use bdk::blockchain::{ @@ -17,6 +17,9 @@ use bdk::{BlockTime, Error, FeeRate, SignOptions, Wallet as BdkWallet}; use std::convert::TryFrom; use std::str::FromStr; use std::sync::{Arc, Mutex, MutexGuard}; +use bdk::bitcoin::hashes::Hash; +use bdk::bitcoin::util::bip32::{ExtendedPrivKey, ExtendedPubKey}; +use bdk::bitcoin::util::misc::{MessageSignature, signed_msg_hash}; uniffi_macros::include_scaffolding!("bdk"); @@ -240,6 +243,7 @@ impl Wallet { pub struct ExtendedKeyInfo { mnemonic: String, xprv: String, + xpub: String, fingerprint: String, } @@ -253,10 +257,13 @@ fn generate_extended_key( let mnemonic = mnemonic.into_key(); let xkey: ExtendedKey = (mnemonic.clone(), password).into_extended_key()?; let xprv = xkey.into_xprv(network).unwrap(); - let fingerprint = xprv.fingerprint(&Secp256k1::new()); + let secp = Secp256k1::new(); + let xpub = ExtendedPubKey::from_private(&secp, &xprv); + let fingerprint = xprv.fingerprint(&secp); Ok(ExtendedKeyInfo { mnemonic: mnemonic.to_string(), xprv: xprv.to_string(), + xpub: xpub.to_string(), fingerprint: fingerprint.to_string(), }) } @@ -269,10 +276,13 @@ fn restore_extended_key( let mnemonic = Mnemonic::parse_in(Language::English, mnemonic).unwrap(); let xkey: ExtendedKey = (mnemonic.clone(), password).into_extended_key()?; let xprv = xkey.into_xprv(network).unwrap(); - let fingerprint = xprv.fingerprint(&Secp256k1::new()); + let secp = Secp256k1::new(); + let xpub = ExtendedPubKey::from_private(&secp, &xprv); + let fingerprint = xprv.fingerprint(&secp); Ok(ExtendedKeyInfo { mnemonic: mnemonic.to_string(), xprv: xprv.to_string(), + xpub: xpub.to_string(), fingerprint: fingerprint.to_string(), }) } @@ -362,4 +372,109 @@ impl TxBuilder { } } +pub trait MessageSigner { + fn sign(&self, msg: String) -> String; +} + +pub struct EcdsaMessageSigner { + secp: Secp256k1, + secret_key: SecretKey +} + +impl EcdsaMessageSigner { + + pub fn from_wif(wif: String) -> Result { + let prv = match PrivateKey::from_wif(wif.as_str()) { + Ok(prv ) => prv, + Err(e) => return Err(Error::Generic(e.to_string())) + }; + + EcdsaMessageSigner::from_secret_key(prv.key) + } + + pub fn from_prv(str: String) -> Result { + let prv = SecretKey::from_str(str.as_str()).unwrap(); + EcdsaMessageSigner::from_secret_key(prv) + } + + pub fn from_xprv(str: String) -> Result { + let xprv = ExtendedPrivKey::from_str(str.as_str()).unwrap(); + EcdsaMessageSigner::from_secret_key(xprv.private_key.key) + } + + fn from_secret_key(secret_key: SecretKey) -> Result { + Ok(EcdsaMessageSigner { + secret_key, + secp: Secp256k1::new(), + }) + } +} + +impl MessageSigner for EcdsaMessageSigner { + + fn sign(&self, msg: String) -> String { + let msg_hash = signed_msg_hash(msg.as_str()); + let sig = self.secp.sign_recoverable( + &Message::from_slice(&msg_hash.into_inner()[..]).unwrap(), + &self.secret_key, + ); + MessageSignature::new(sig, false).to_base64() + } +} + +pub trait MessageSignatureVerifier { + fn verify(&self, sig: String, msg: String) -> bool; +} + +pub struct EcdsaMessageSignatureVerifier { + secp: Secp256k1, + address: Address, +} + +impl EcdsaMessageSignatureVerifier { + + pub fn from_address(address: String) -> Result { + let address = Address::from_str(address.as_str()).unwrap(); + EcdsaMessageSignatureVerifier::from_addr_struct(address) + } + + pub fn from_pub(str: String) -> Result { + let public_key = PublicKey::from_str(str.as_str()).unwrap(); + let address = Address::p2pkh(&public_key, Network::Bitcoin); + EcdsaMessageSignatureVerifier::from_addr_struct(address) + } + + pub fn from_xpub(str: String) -> Result { + let public_key = ExtendedPubKey::from_str(str.as_str()).unwrap(); + EcdsaMessageSignatureVerifier::from_pub(public_key.public_key.to_string()) + } + + fn from_addr_struct(address: Address) -> Result { + Ok(EcdsaMessageSignatureVerifier { + address, + secp: Secp256k1::new(), + }) + } +} + +impl MessageSignatureVerifier for EcdsaMessageSignatureVerifier { + + fn verify(&self, sig: String, msg: String) -> bool { + let msg_sig = MessageSignature::from_base64(sig.as_str()); + let pub_key = match msg_sig { + Ok(sig) => sig.recover_pubkey(&self.secp, signed_msg_hash(msg.as_str())), + Err(_) => return false + }; + let compressed_pub_key = match pub_key { + Ok(pub_key) => PublicKey::from_str(&pub_key.key.to_string()), + Err(_) => return false + }; + let address = match compressed_pub_key { + Ok(pub_key) => Address::p2pkh(&pub_key, self.address.network), + Err(_) => return false + }; + address == self.address + } +} + uniffi::deps::static_assertions::assert_impl_all!(Wallet: Sync, Send);