Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sign / verify for arbitrary messages #139

Closed
wants to merge 3 commits into from
Closed
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
21 changes: 21 additions & 0 deletions src/bdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -153,6 +173,7 @@ interface TxBuilder {
dictionary ExtendedKeyInfo {
string mnemonic;
string xprv;
string xpub;
string fingerprint;
};

Expand Down
123 changes: 119 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -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");

Expand Down Expand Up @@ -240,6 +243,7 @@ impl Wallet {
pub struct ExtendedKeyInfo {
mnemonic: String,
xprv: String,
xpub: String,
fingerprint: String,
}

Expand All @@ -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(),
})
}
Expand All @@ -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(),
})
}
Expand Down Expand Up @@ -362,4 +372,109 @@ impl TxBuilder {
}
}

pub trait MessageSigner {
fn sign(&self, msg: String) -> String;
}

pub struct EcdsaMessageSigner {
secp: Secp256k1<All>,
secret_key: SecretKey
}

impl EcdsaMessageSigner {

pub fn from_wif(wif: String) -> Result<Self, Error> {
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<Self, Error> {
let prv = SecretKey::from_str(str.as_str()).unwrap();
EcdsaMessageSigner::from_secret_key(prv)
}

pub fn from_xprv(str: String) -> Result<Self, Error> {
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<Self, Error> {
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<All>,
address: Address,
}

impl EcdsaMessageSignatureVerifier {

pub fn from_address(address: String) -> Result<Self, Error> {
let address = Address::from_str(address.as_str()).unwrap();
EcdsaMessageSignatureVerifier::from_addr_struct(address)
}

pub fn from_pub(str: String) -> Result<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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);