From 6d0b8343d9316fc34792cb7850fa13c40ea7d765 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 02:14:02 +0200 Subject: [PATCH 01/14] Create inner and outer functions for Rust library consumers --- Cargo.lock | 63 ++++++++++++++++ Cargo.toml | 1 + src/error.rs | 32 ++++++-- src/lib.rs | 88 ++++++++++++++-------- src/sign.rs | 21 ++---- src/verify.rs | 193 ++++++++++++++++++++++++++----------------------- www/src/lib.rs | 14 ++-- 7 files changed, 257 insertions(+), 155 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e9a079..d29affd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,7 @@ dependencies = [ "hex", "miniscript", "pretty_assertions", + "snafu", ] [[package]] @@ -90,6 +91,12 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex" version = "0.4.3" @@ -138,6 +145,24 @@ dependencies = [ "yansi", ] +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + [[package]] name = "secp256k1" version = "0.28.2" @@ -157,6 +182,44 @@ dependencies = [ "cc", ] +[[package]] +name = "snafu" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b835cb902660db3415a672d862905e791e54d306c6e8189168c7f3d9ae1c79d" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d1e02fca405f6280643174a50c942219f0bbf4dbf7d480f1dd864d6f211ae5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index e10a8f7..78dd613 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ bitcoin = { version = "0.31.2" } bitcoin_hashes = "0.14.0" hex = "0.4.3" miniscript = "11.0.0" +snafu = "0.8.4" [dev-dependencies] pretty_assertions = "1.4.0" diff --git a/src/error.rs b/src/error.rs index 23956fd..275e69c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,8 +1,26 @@ -#[derive(Debug, PartialEq, Eq)] -pub enum Bip322Error { - InvalidAddress, // for legacy addresses 1... (p2pkh) not supported, also any non taproot - Invalid, // Address no key; pubkey not recovered, invalid signature - MalformedSignature, // wrong length, etc. - InvalidSigHash, // only sighash All and Default supported - NotKeyPathSpend, // only single key path spend supported +use super::*; + +#[derive(Debug, Snafu, PartialEq)] +#[snafu(context(suffix(false)), visibility(pub))] +pub enum Error { + #[snafu(display("Failed to parse address `{address}`"))] + AddressParse { + source: bitcoin::address::ParseError, + address: String, + }, + #[snafu(display("Invalid address"))] + InvalidAddress, + #[snafu(display("Invalid"))] + Invalid, + #[snafu(display("Malformed signature `{signature}`"))] + SignatureDecode { + source: base64::DecodeError, + signature: String, + }, + #[snafu(display("Malformed signature"))] + MalformedSignature, + #[snafu(display("Invalid sighash"))] + InvalidSigHash, + #[snafu(display("Not key path spend"))] + NotKeyPathSpend, } diff --git a/src/lib.rs b/src/lib.rs index 2a519d6..4f3da64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,25 @@ use { + base64::{engine::general_purpose, Engine}, bitcoin::{ - absolute::LockTime, blockdata::script, opcodes, psbt::Psbt, script::PushBytes, - secp256k1::Secp256k1, transaction::Version, Address, Amount, Network, OutPoint, PrivateKey, - PublicKey, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness, + absolute::LockTime, + address::AddressType, + blockdata::script, + consensus::Decodable, + consensus::Encodable, + key::{Keypair, TapTweak}, + opcodes, + psbt::Psbt, + script::PushBytes, + secp256k1::{self, schnorr::Signature, Message, Secp256k1, XOnlyPublicKey}, + sighash::{self, SighashCache, TapSighashType}, + transaction::Version, + Address, Amount, Network, OutPoint, PrivateKey, PublicKey, ScriptBuf, Sequence, Transaction, + TxIn, TxOut, Witness, }, bitcoin_hashes::{sha256, Hash}, + error::Error, + snafu::{ResultExt, Snafu}, + std::{io::Cursor, str::FromStr}, }; mod error; @@ -16,6 +31,10 @@ pub use { verify::{full_verify, simple_verify}, }; +const TAG: &str = "BIP0322-signed-message"; + +type Result = std::result::Result; + pub struct Wallet { pub btc_address: Address, pub descriptor: miniscript::Descriptor, @@ -42,22 +61,18 @@ impl Wallet { } } -const TAG: &str = "BIP0322-signed-message"; - -type Result = std::result::Result; - // message_hash = sha256(sha256(tag) || sha256(tag) || message); see BIP340 -fn message_hash(message: &str) -> Vec { +pub(crate) fn message_hash(message: &[u8]) -> Vec { let mut tag_hash = sha256::Hash::hash(TAG.as_bytes()).to_byte_array().to_vec(); tag_hash.extend(tag_hash.clone()); - tag_hash.extend(message.as_bytes()); + tag_hash.extend(message); sha256::Hash::hash(tag_hash.as_slice()) .to_byte_array() .to_vec() } -fn create_to_spend(address: &Address, message: &str) -> Transaction { +pub(crate) fn create_to_spend(address: &Address, message: &[u8]) -> Transaction { Transaction { version: Version(0), lock_time: LockTime::ZERO, @@ -82,7 +97,7 @@ fn create_to_spend(address: &Address, message: &str) -> Transaction { } } -fn create_to_sign(to_spend: &Transaction) -> Psbt { +pub(crate) fn create_to_sign(to_spend: &Transaction, witness: Option) -> Psbt { let inputs = vec![TxIn { previous_output: OutPoint { txid: to_spend.txid(), @@ -106,17 +121,20 @@ fn create_to_sign(to_spend: &Transaction) -> Psbt { }; let mut psbt = Psbt::from_unsigned_tx(to_sign).unwrap(); // TODO + psbt.inputs[0].witness_utxo = Some(TxOut { value: Amount::from_sat(0), script_pubkey: to_spend.output[0].script_pubkey.clone(), }); + psbt.inputs[0].final_script_witness = witness; + psbt } #[cfg(test)] mod tests { - use {super::*, error::Bip322Error, pretty_assertions::assert_eq, std::str::FromStr}; + use {super::*, error::Error, pretty_assertions::assert_eq}; /// From https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#test-vectors /// and https://github.com/ACken2/bip322-js/blob/main/test/Verifier.test.ts @@ -129,12 +147,12 @@ mod tests { #[test] fn message_hashes_are_correct() { assert_eq!( - hex::encode(message_hash("")), + hex::encode(message_hash("".as_bytes())), "c90c269c4f8fcbe6880f72a721ddfbf1914268a794cbb21cfafee13770ae19f1" ); assert_eq!( - hex::encode(message_hash("Hello World")), + hex::encode(message_hash("Hello World".as_bytes())), "f0eb03b1a75ac6d9847f55c624a99169b5dccba2a31f5b23bea77ba270de0a7a" ); } @@ -144,7 +162,7 @@ mod tests { assert_eq!( create_to_spend( &Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(), - "" + "".as_bytes() ) .txid() .to_string(), @@ -154,7 +172,7 @@ mod tests { assert_eq!( create_to_spend( &Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(), - "Hello World" + "Hello World".as_bytes() ) .txid() .to_string(), @@ -166,9 +184,9 @@ mod tests { fn to_sign_txids_correct() { let to_spend = create_to_spend( &Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(), - "", + "".as_bytes(), ); - let to_sign = create_to_sign(&to_spend); + let to_sign = create_to_sign(&to_spend, None); assert_eq!( to_sign.unsigned_tx.txid().to_string(), "1e9654e951a5ba44c8604c4de6c67fd78a27e81dcadcfe1edf638ba3aaebaed6" @@ -176,9 +194,9 @@ mod tests { let to_spend = create_to_spend( &Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(), - "Hello World", + "Hello World".as_bytes(), ); - let to_sign = create_to_sign(&to_spend); + let to_sign = create_to_sign(&to_spend, None); assert_eq!( to_sign.unsigned_tx.txid().to_string(), "88737ae86f2077145f93cc4b153ae9a1cb8d56afa511988c149c5c8c9d93bddf" @@ -189,7 +207,7 @@ mod tests { fn simple_verify_and_falsify_taproot() { assert!( simple_verify( - &Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(), + TAPROOT_ADDRESS, "Hello World", "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==" ).is_ok() @@ -197,11 +215,11 @@ mod tests { assert_eq!( simple_verify( - &Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(), + TAPROOT_ADDRESS, "Hello World -- This should fail", "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==" ), - Err(Bip322Error::Invalid) + Err(Error::Invalid) ); } @@ -226,7 +244,7 @@ mod tests { let wallet = Wallet::new(WIF_PRIVATE_KEY); assert!(simple_verify( - &Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(), + TAPROOT_ADDRESS, "Hello World", &simple_sign( &Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(), @@ -242,7 +260,7 @@ mod tests { let wallet = Wallet::new(WIF_PRIVATE_KEY); assert!(full_verify( - &Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(), + TAPROOT_ADDRESS, "Hello World", &full_sign( &Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(), @@ -256,31 +274,37 @@ mod tests { #[test] fn invalid_address() { assert_eq!(simple_verify( - &Address::from_str("3B5fQsEXEaV8v6U3ejYc8XaKXAkyQj2MjV").unwrap().assume_checked(), + "3B5fQsEXEaV8v6U3ejYc8XaKXAkyQj2MjV", "", "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="), - Err(Bip322Error::InvalidAddress) + Err(Error::InvalidAddress) ) } #[test] - fn malformed_signature() { + fn signature_decode_error() { assert_eq!( simple_verify( - &Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(), + TAPROOT_ADDRESS, "Hello World", "invalid signature not in base64 encoding" ), - Err(Bip322Error::MalformedSignature) + Err(Error::SignatureDecode { + source: base64::DecodeError::InvalidByte(7, 32,), + signature: "invalid signature not in base64 encoding".to_string(), + }) ); assert_eq!( simple_verify( - &Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(), + TAPROOT_ADDRESS, "Hello World", "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViH" ), - Err(Bip322Error::MalformedSignature) + Err(Error::SignatureDecode { + source: base64::DecodeError::InvalidPadding, + signature: "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViH".to_string(), + }) ) } } diff --git a/src/sign.rs b/src/sign.rs index 719b350..26ebecb 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -1,15 +1,4 @@ -use { - crate::{create_to_sign, create_to_spend, Wallet}, - base64::{engine::general_purpose, Engine}, - bitcoin::{ - consensus::Encodable, - key::{Keypair, TapTweak}, - psbt::Psbt, - secp256k1::{self, Secp256k1, XOnlyPublicKey}, - sighash::{self, SighashCache, TapSighashType}, - Address, Amount, PrivateKey, Transaction, TxOut, Witness, - }, -}; +use super::*; fn create_message_signature( to_spend_tx: &Transaction, @@ -64,8 +53,8 @@ fn create_message_signature( } pub fn simple_sign(address: &Address, message: &str, wallet: &Wallet) -> String { - let to_spend = create_to_spend(address, message); - let to_sign = create_to_sign(&to_spend); + let to_spend = create_to_spend(address, message.as_bytes()); + let to_sign = create_to_sign(&to_spend, None); let witness = create_message_signature(&to_spend, &to_sign, wallet.private_key); @@ -76,8 +65,8 @@ pub fn simple_sign(address: &Address, message: &str, wallet: &Wallet) -> String } pub fn full_sign(address: &Address, message: &str, wallet: &Wallet) -> String { - let to_spend = create_to_spend(address, message); - let mut to_sign = create_to_sign(&to_spend); + let to_spend = create_to_spend(address, message.as_bytes()); + let mut to_sign = create_to_sign(&to_spend, None); let witness = create_message_signature(&to_spend, &to_sign, wallet.private_key); to_sign.inputs[0].final_script_witness = Some(witness); diff --git a/src/verify.rs b/src/verify.rs index d32c35d..b7eca72 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -1,69 +1,126 @@ -use { - super::*, - crate::{create_to_sign, create_to_spend, error::Bip322Error}, - base64::{engine::general_purpose, Engine}, - bitcoin::{ - address::AddressType, - consensus::Decodable, - secp256k1::{schnorr::Signature, Message, Secp256k1, XOnlyPublicKey}, - sighash::{self, SighashCache, TapSighashType}, - Address, Amount, OutPoint, Transaction, TxOut, Witness, - }, - std::io::Cursor, -}; - -fn extract_pub_key(address: &Address) -> Result { +use super::*; + +/// This completely outward facing function is meant to be consumed by very naive users like when +/// compiling this library to WASM, where Javascript has no type safety. If you'd like to use the +/// more type safe / Rust variant use `fn simple_verify_inner`. +pub fn simple_verify(address: &str, message: &str, signature: &str) -> Result<()> { + let address = Address::from_str(address) + .context(error::AddressParse { address })? + .assume_checked(); + + let mut cursor = Cursor::new( + general_purpose::STANDARD + .decode(signature) + .context(error::SignatureDecode { signature })?, + ); + + let witness = match Witness::consensus_decode_from_finite_reader(&mut cursor) { + Ok(witness) => witness, + Err(_) => return Err(Error::MalformedSignature), + }; + + simple_verify_inner(&address, message.as_bytes(), witness) +} + +/// This completely outward facing function is meant to be consumed by very naive users like when +/// compiling this library to WASM, where Javascript has no type safety. If you'd like to use the +/// more type safe / Rust variant use `fn full_verify_inner`. +pub fn full_verify(address: &str, message: &str, to_sign: &str) -> Result<()> { + let address = Address::from_str(address) + .context(error::AddressParse { address })? + .assume_checked(); + + let mut cursor = Cursor::new( + general_purpose::STANDARD + .decode(to_sign) + .map_err(|_| Error::MalformedSignature)?, + ); + + let to_sign = Transaction::consensus_decode_from_finite_reader(&mut cursor) + .map_err(|_| Error::MalformedSignature)?; + + full_verify_inner(&address, message.as_bytes(), to_sign) +} + +pub fn simple_verify_inner(address: &Address, message: &[u8], signature: Witness) -> Result<()> { + full_verify_inner( + address, + message, + create_to_sign(&create_to_spend(address, message), Some(signature)) + .extract_tx() + .unwrap(), + ) +} + +pub fn full_verify_inner(address: &Address, message: &[u8], to_sign: Transaction) -> Result<()> { if address .address_type() .is_some_and(|addr| addr != AddressType::P2tr) { - return Err(Bip322Error::InvalidAddress); + return Err(Error::InvalidAddress); } - if let bitcoin::address::Payload::WitnessProgram(witness_program) = address.payload() { - if witness_program.version().to_num() != 1 { - return Err(Bip322Error::InvalidAddress); - } + let pub_key = + if let bitcoin::address::Payload::WitnessProgram(witness_program) = address.payload() { + if witness_program.version().to_num() != 1 { + return Err(Error::InvalidAddress); + } - if witness_program.program().len() != 32 { - return Err(Bip322Error::NotKeyPathSpend); - } + if witness_program.program().len() != 32 { + return Err(Error::NotKeyPathSpend); + } - Ok( XOnlyPublicKey::from_slice(witness_program.program().as_bytes()) - .expect("should extract an xonly public key"), - ) - } else { - Err(Bip322Error::InvalidAddress) + .expect("should extract an xonly public key") + } else { + return Err(Error::InvalidAddress); + }; + + let to_spend = create_to_spend(address, message); + + let witness = to_sign.input[0].witness.clone(); + + let to_spend_outpoint = OutPoint { + txid: to_spend.txid(), + vout: 0, + }; + + if to_spend_outpoint != to_sign.input[0].previous_output { + return Err(Error::Invalid); } -} -fn decode_and_verify( - encoded_signature: &Vec, - pub_key: &XOnlyPublicKey, - to_spend: Transaction, - to_sign: Transaction, -) -> Result<()> { + let mut to_sign = create_to_sign(&to_spend, None); + to_sign.inputs[0].final_script_witness = Some(witness); + + if to_spend_outpoint != to_sign.unsigned_tx.input[0].previous_output { + return Err(Error::Invalid); + } + + let Some(witness) = to_sign.inputs[0].final_script_witness.clone() else { + return Err(Error::Invalid); + }; + + let encoded_signature = witness.to_vec()[0].clone(); + let (signature, sighash_type) = match encoded_signature.len() { 65 => ( Signature::from_slice(&encoded_signature.as_slice()[..64]) - .map_err(|_| Bip322Error::MalformedSignature)?, + .map_err(|_| Error::MalformedSignature)?, TapSighashType::from_consensus_u8(encoded_signature[64]) - .map_err(|_| Bip322Error::InvalidSigHash)?, + .map_err(|_| Error::InvalidSigHash)?, ), 64 => ( - Signature::from_slice(encoded_signature.as_slice()) - .map_err(|_| Bip322Error::MalformedSignature)?, + Signature::from_slice(encoded_signature.as_slice()).map_err(|_| Error::MalformedSignature)?, TapSighashType::Default, ), - _ => return Err(Bip322Error::MalformedSignature), + _ => return Err(Error::MalformedSignature), }; if !(sighash_type == TapSighashType::All || sighash_type == TapSighashType::Default) { - return Err(Bip322Error::InvalidSigHash); + return Err(Error::InvalidSigHash); } - let mut sighash_cache = SighashCache::new(to_sign); + let mut sighash_cache = SighashCache::new(to_sign.unsigned_tx); let sighash = sighash_cache .taproot_key_spend_signature_hash( @@ -80,54 +137,6 @@ fn decode_and_verify( Message::from_digest_slice(sighash.as_ref()).expect("should be cryptographically secure hash"); Secp256k1::verification_only() - .verify_schnorr(&signature, &message, pub_key) - .map_err(|_| Bip322Error::Invalid) -} - -pub fn simple_verify(address: &Address, message: &str, signature: &str) -> Result<()> { - let pub_key = extract_pub_key(address)?; - let to_spend = create_to_spend(address, message); - let to_sign = create_to_sign(&to_spend); - - let mut cursor = Cursor::new( - general_purpose::STANDARD - .decode(signature) - .map_err(|_| Bip322Error::MalformedSignature)?, - ); - - let witness = match Witness::consensus_decode_from_finite_reader(&mut cursor) { - Ok(witness) => witness, - Err(_) => return Err(Bip322Error::MalformedSignature), - }; - - let encoded_signature = &witness.to_vec()[0]; - - decode_and_verify(encoded_signature, &pub_key, to_spend, to_sign.unsigned_tx) -} - -pub fn full_verify(address: &Address, message: &str, to_sign_base64: &str) -> Result<()> { - let pub_key = extract_pub_key(address)?; - let to_spend = create_to_spend(address, message); - - let mut cursor = Cursor::new( - general_purpose::STANDARD - .decode(to_sign_base64) - .map_err(|_| Bip322Error::MalformedSignature)?, - ); - - let to_sign = Transaction::consensus_decode_from_finite_reader(&mut cursor) - .map_err(|_| Bip322Error::MalformedSignature)?; - - let to_spend_out_point = OutPoint { - txid: to_spend.txid(), - vout: 0, - }; - - if to_spend_out_point != to_sign.input[0].previous_output { - return Err(Bip322Error::Invalid); - } - - let encoded_signature = &to_sign.input[0].witness.to_vec()[0]; - - decode_and_verify(encoded_signature, &pub_key, to_spend, to_sign) + .verify_schnorr(&signature, &message, &pub_key) + .map_err(|_| Error::Invalid) } diff --git a/www/src/lib.rs b/www/src/lib.rs index 53f08d1..bdc9644 100644 --- a/www/src/lib.rs +++ b/www/src/lib.rs @@ -5,12 +5,10 @@ use { #[wasm_bindgen] pub fn verify(address: &str, message: &str, signature: &str) -> bool { - bip322::simple_verify( - &address - .parse::>() - .unwrap() - .assume_checked(), - message, - signature, - ) + let address = address + .parse::>() + .unwrap() + .assume_checked(); + + bip322::simple_verify(&address, message, signature) } From 4dc6d201fea1ad054f5a6066c6685041a751c88f Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 02:18:11 +0200 Subject: [PATCH 02/14] Amend --- www/Cargo.lock | 32 +++++++++++++++++++++++++++++--- www/Cargo.toml | 5 ++++- www/src/lib.rs | 12 ++---------- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/www/Cargo.lock b/www/Cargo.lock index a480482..deaf5e2 100644 --- a/www/Cargo.lock +++ b/www/Cargo.lock @@ -22,15 +22,14 @@ checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" [[package]] name = "bip322" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048ae0b251cd57e77fc4ec31b0fe916b5fbdb6d0b9d20c8fd1ccb6fcc7e15f7" +version = "0.0.4" dependencies = [ "base64", "bitcoin", "bitcoin_hashes 0.14.0", "hex", "miniscript", + "snafu", ] [[package]] @@ -97,6 +96,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex" version = "0.4.3" @@ -184,6 +189,27 @@ dependencies = [ "cc", ] +[[package]] +name = "snafu" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b835cb902660db3415a672d862905e791e54d306c6e8189168c7f3d9ae1c79d" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d1e02fca405f6280643174a50c942219f0bbf4dbf7d480f1dd864d6f211ae5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "2.0.72" diff --git a/www/Cargo.toml b/www/Cargo.toml index 58d906f..ea57c2b 100644 --- a/www/Cargo.toml +++ b/www/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -bip322 = "0.0.3" +bip322 = "0.0.4" bitcoin = "0.31.2" wasm-bindgen = "0.2.92" + +[patch.crates-io] +bip322 = { path = '../' } diff --git a/www/src/lib.rs b/www/src/lib.rs index bdc9644..5635ab7 100644 --- a/www/src/lib.rs +++ b/www/src/lib.rs @@ -1,14 +1,6 @@ -use { - bitcoin::{address::NetworkUnchecked, Address}, - wasm_bindgen::prelude::*, -}; +use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn verify(address: &str, message: &str, signature: &str) -> bool { - let address = address - .parse::>() - .unwrap() - .assume_checked(); - - bip322::simple_verify(&address, message, signature) + bip322::simple_verify(&address, message, signature).is_ok() } From 21704ccfe2a2d94a1d180ab3bab102fbf2f1e7d9 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 02:23:44 +0200 Subject: [PATCH 03/14] Amend --- src/verify.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/verify.rs b/src/verify.rs index b7eca72..6785eab 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -22,6 +22,17 @@ pub fn simple_verify(address: &str, message: &str, signature: &str) -> Result<() simple_verify_inner(&address, message.as_bytes(), witness) } +/// Meant to be consumed by Rust applications +pub fn simple_verify_inner(address: &Address, message: &[u8], signature: Witness) -> Result<()> { + full_verify_inner( + address, + message, + create_to_sign(&create_to_spend(address, message), Some(signature)) + .extract_tx() + .unwrap(), + ) +} + /// This completely outward facing function is meant to be consumed by very naive users like when /// compiling this library to WASM, where Javascript has no type safety. If you'd like to use the /// more type safe / Rust variant use `fn full_verify_inner`. @@ -42,16 +53,7 @@ pub fn full_verify(address: &str, message: &str, to_sign: &str) -> Result<()> { full_verify_inner(&address, message.as_bytes(), to_sign) } -pub fn simple_verify_inner(address: &Address, message: &[u8], signature: Witness) -> Result<()> { - full_verify_inner( - address, - message, - create_to_sign(&create_to_spend(address, message), Some(signature)) - .extract_tx() - .unwrap(), - ) -} - +/// Meant to be consumed by Rust applications pub fn full_verify_inner(address: &Address, message: &[u8], to_sign: Transaction) -> Result<()> { if address .address_type() @@ -78,8 +80,6 @@ pub fn full_verify_inner(address: &Address, message: &[u8], to_sign: Transaction let to_spend = create_to_spend(address, message); - let witness = to_sign.input[0].witness.clone(); - let to_spend_outpoint = OutPoint { txid: to_spend.txid(), vout: 0, @@ -89,8 +89,7 @@ pub fn full_verify_inner(address: &Address, message: &[u8], to_sign: Transaction return Err(Error::Invalid); } - let mut to_sign = create_to_sign(&to_spend, None); - to_sign.inputs[0].final_script_witness = Some(witness); + let to_sign = create_to_sign(&to_spend, Some(to_sign.input[0].witness.clone())); if to_spend_outpoint != to_sign.unsigned_tx.input[0].previous_output { return Err(Error::Invalid); From a675b45208bdde408c69c4d43af57030f92cf3f7 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 02:28:24 +0200 Subject: [PATCH 04/14] Amend --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 275e69c..6d67881 100644 --- a/src/error.rs +++ b/src/error.rs @@ -12,7 +12,7 @@ pub enum Error { InvalidAddress, #[snafu(display("Invalid"))] Invalid, - #[snafu(display("Malformed signature `{signature}`"))] + #[snafu(display("Decode error for signature `{signature}`"))] SignatureDecode { source: base64::DecodeError, signature: String, From efc7ed1e8749f1613d3febf10b2b04eb4a550c3c Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 03:02:11 +0200 Subject: [PATCH 05/14] Amend --- src/error.rs | 37 +++++++++++++++++++++++++++++------ src/lib.rs | 26 +++++++++++-------------- src/verify.rs | 53 ++++++++++++++++++++++++++++++++------------------- 3 files changed, 75 insertions(+), 41 deletions(-) diff --git a/src/error.rs b/src/error.rs index 6d67881..964a8ef 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ use super::*; -#[derive(Debug, Snafu, PartialEq)] +#[derive(Debug, Snafu)] #[snafu(context(suffix(false)), visibility(pub))] pub enum Error { #[snafu(display("Failed to parse address `{address}`"))] @@ -8,8 +8,8 @@ pub enum Error { source: bitcoin::address::ParseError, address: String, }, - #[snafu(display("Invalid address"))] - InvalidAddress, + #[snafu(display("Unsuported address `{address}`, only P2TR allowed"))] + UnsupportedAddress { address: String }, #[snafu(display("Invalid"))] Invalid, #[snafu(display("Decode error for signature `{signature}`"))] @@ -17,10 +17,35 @@ pub enum Error { source: base64::DecodeError, signature: String, }, - #[snafu(display("Malformed signature"))] - MalformedSignature, + #[snafu(display("Base64 decode error for transaction `{transaction}`"))] + TransactionDecode { + source: base64::DecodeError, + transaction: String, + }, + #[snafu(display("Consensu decode error for transaction `{transaction}`"))] + TransactionConsensusDecode { + source: bitcoin::consensus::encode::Error, + transaction: String, + }, + #[snafu(display("Witness signature"))] + MalformedWitness { + source: bitcoin::consensus::encode::Error, + }, + #[snafu(display("Signature of wrong length `{length}`"))] + SignatureLength { + length: usize, + encoded_signature: Vec, + }, + #[snafu(display("Invalid signature because: `{}`", source.to_string()))] + InvalidSignature { + source: bitcoin::secp256k1::Error, + }, #[snafu(display("Invalid sighash"))] - InvalidSigHash, + InvalidSigHash { + source: bitcoin::sighash::InvalidSighashTypeError, + }, + #[snafu(display("Unsupported sighash type `{sighash_type}`"))] + UnsupportedSigHash { sighash_type: String }, #[snafu(display("Not key path spend"))] NotKeyPathSpend, } diff --git a/src/lib.rs b/src/lib.rs index 4f3da64..bcb56a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,7 +134,7 @@ pub(crate) fn create_to_sign(to_spend: &Transaction, witness: Option) - #[cfg(test)] mod tests { - use {super::*, error::Error, pretty_assertions::assert_eq}; + use {super::*, pretty_assertions::assert_eq}; /// From https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#test-vectors /// and https://github.com/ACken2/bip322-js/blob/main/test/Verifier.test.ts @@ -218,8 +218,8 @@ mod tests { TAPROOT_ADDRESS, "Hello World -- This should fail", "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==" - ), - Err(Error::Invalid) + ).unwrap_err().to_string(), + "Invalid" ); } @@ -276,8 +276,8 @@ mod tests { assert_eq!(simple_verify( "3B5fQsEXEaV8v6U3ejYc8XaKXAkyQj2MjV", "", - "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="), - Err(Error::InvalidAddress) + "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=").unwrap_err().to_string(), + "Unsuported address `3B5fQsEXEaV8v6U3ejYc8XaKXAkyQj2MjV`, only P2TR allowed" ) } @@ -288,11 +288,10 @@ mod tests { TAPROOT_ADDRESS, "Hello World", "invalid signature not in base64 encoding" - ), - Err(Error::SignatureDecode { - source: base64::DecodeError::InvalidByte(7, 32,), - signature: "invalid signature not in base64 encoding".to_string(), - }) + ) + .unwrap_err() + .to_string(), + "Decode error for signature `invalid signature not in base64 encoding`" ); assert_eq!( @@ -300,11 +299,8 @@ mod tests { TAPROOT_ADDRESS, "Hello World", "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViH" - ), - Err(Error::SignatureDecode { - source: base64::DecodeError::InvalidPadding, - signature: "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViH".to_string(), - }) + ).unwrap_err().to_string(), + "Decode error for signature `AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViH`" ) } } diff --git a/src/verify.rs b/src/verify.rs index 6785eab..1f226ab 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -14,10 +14,8 @@ pub fn simple_verify(address: &str, message: &str, signature: &str) -> Result<() .context(error::SignatureDecode { signature })?, ); - let witness = match Witness::consensus_decode_from_finite_reader(&mut cursor) { - Ok(witness) => witness, - Err(_) => return Err(Error::MalformedSignature), - }; + let witness = + Witness::consensus_decode_from_finite_reader(&mut cursor).context(error::MalformedWitness)?; simple_verify_inner(&address, message.as_bytes(), witness) } @@ -41,14 +39,17 @@ pub fn full_verify(address: &str, message: &str, to_sign: &str) -> Result<()> { .context(error::AddressParse { address })? .assume_checked(); - let mut cursor = Cursor::new( - general_purpose::STANDARD - .decode(to_sign) - .map_err(|_| Error::MalformedSignature)?, - ); + let mut cursor = Cursor::new(general_purpose::STANDARD.decode(to_sign).context( + error::TransactionDecode { + transaction: to_sign, + }, + )?); - let to_sign = Transaction::consensus_decode_from_finite_reader(&mut cursor) - .map_err(|_| Error::MalformedSignature)?; + let to_sign = Transaction::consensus_decode_from_finite_reader(&mut cursor).context( + error::TransactionConsensusDecode { + transaction: to_sign, + }, + )?; full_verify_inner(&address, message.as_bytes(), to_sign) } @@ -59,13 +60,17 @@ pub fn full_verify_inner(address: &Address, message: &[u8], to_sign: Transaction .address_type() .is_some_and(|addr| addr != AddressType::P2tr) { - return Err(Error::InvalidAddress); + return Err(Error::UnsupportedAddress { + address: address.to_string(), + }); } let pub_key = if let bitcoin::address::Payload::WitnessProgram(witness_program) = address.payload() { if witness_program.version().to_num() != 1 { - return Err(Error::InvalidAddress); + return Err(Error::UnsupportedAddress { + address: address.to_string(), + }); } if witness_program.program().len() != 32 { @@ -75,7 +80,9 @@ pub fn full_verify_inner(address: &Address, message: &[u8], to_sign: Transaction XOnlyPublicKey::from_slice(witness_program.program().as_bytes()) .expect("should extract an xonly public key") } else { - return Err(Error::InvalidAddress); + return Err(Error::UnsupportedAddress { + address: address.to_string(), + }); }; let to_spend = create_to_spend(address, message); @@ -104,19 +111,25 @@ pub fn full_verify_inner(address: &Address, message: &[u8], to_sign: Transaction let (signature, sighash_type) = match encoded_signature.len() { 65 => ( Signature::from_slice(&encoded_signature.as_slice()[..64]) - .map_err(|_| Error::MalformedSignature)?, - TapSighashType::from_consensus_u8(encoded_signature[64]) - .map_err(|_| Error::InvalidSigHash)?, + .context(error::InvalidSignature)?, + TapSighashType::from_consensus_u8(encoded_signature[64]).context(error::InvalidSigHash)?, ), 64 => ( - Signature::from_slice(encoded_signature.as_slice()).map_err(|_| Error::MalformedSignature)?, + Signature::from_slice(encoded_signature.as_slice()).context(error::InvalidSignature)?, TapSighashType::Default, ), - _ => return Err(Error::MalformedSignature), + _ => { + return Err(Error::SignatureLength { + length: encoded_signature.len(), + encoded_signature, + }) + } }; if !(sighash_type == TapSighashType::All || sighash_type == TapSighashType::Default) { - return Err(Error::InvalidSigHash); + return Err(Error::UnsupportedSigHash { + sighash_type: sighash_type.to_string(), + }); } let mut sighash_cache = SighashCache::new(to_sign.unsigned_tx); From 6cdb58645610804f98efb17a7d9a4fcf295a5201 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 03:02:23 +0200 Subject: [PATCH 06/14] Amend --- src/error.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index 964a8ef..d69b9a2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -37,9 +37,7 @@ pub enum Error { encoded_signature: Vec, }, #[snafu(display("Invalid signature because: `{}`", source.to_string()))] - InvalidSignature { - source: bitcoin::secp256k1::Error, - }, + InvalidSignature { source: bitcoin::secp256k1::Error }, #[snafu(display("Invalid sighash"))] InvalidSigHash { source: bitcoin::sighash::InvalidSighashTypeError, From 93bf9c0bbefe397b0ee51d659a8c349a8083000a Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 03:17:16 +0200 Subject: [PATCH 07/14] Amend --- src/error.rs | 2 +- src/verify.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/error.rs b/src/error.rs index d69b9a2..9fe20be 100644 --- a/src/error.rs +++ b/src/error.rs @@ -43,7 +43,7 @@ pub enum Error { source: bitcoin::sighash::InvalidSighashTypeError, }, #[snafu(display("Unsupported sighash type `{sighash_type}`"))] - UnsupportedSigHash { sighash_type: String }, + UnsupportedSigHashType { sighash_type: String }, #[snafu(display("Not key path spend"))] NotKeyPathSpend, } diff --git a/src/verify.rs b/src/verify.rs index 1f226ab..6901b98 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -127,7 +127,7 @@ pub fn full_verify_inner(address: &Address, message: &[u8], to_sign: Transaction }; if !(sighash_type == TapSighashType::All || sighash_type == TapSighashType::Default) { - return Err(Error::UnsupportedSigHash { + return Err(Error::UnsupportedSigHashType { sighash_type: sighash_type.to_string(), }); } From d6a1dcb8800c67ab368e0ec2160f44ee39ae7b24 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 22:13:32 +0200 Subject: [PATCH 08/14] Refactor sign --- src/lib.rs | 73 ++++++++---------------------------------- src/sign.rs | 87 ++++++++++++++++++++++++++++++++++----------------- src/verify.rs | 44 +++++++++++++------------- 3 files changed, 92 insertions(+), 112 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bcb56a5..b0f802f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,8 +13,7 @@ use { secp256k1::{self, schnorr::Signature, Message, Secp256k1, XOnlyPublicKey}, sighash::{self, SighashCache, TapSighashType}, transaction::Version, - Address, Amount, Network, OutPoint, PrivateKey, PublicKey, ScriptBuf, Sequence, Transaction, - TxIn, TxOut, Witness, + Address, Amount, OutPoint, PrivateKey, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness, }, bitcoin_hashes::{sha256, Hash}, error::Error, @@ -27,40 +26,14 @@ mod sign; mod verify; pub use { - sign::{full_sign, simple_sign}, - verify::{full_verify, simple_verify}, + sign::{sign_full_encoded, sign_simple_encoded}, + verify::{verify_full, verify_full_encoded, verify_simple, verify_simple_encoded}, }; const TAG: &str = "BIP0322-signed-message"; type Result = std::result::Result; -pub struct Wallet { - pub btc_address: Address, - pub descriptor: miniscript::Descriptor, - pub ordinal_address: Address, - pub private_key: PrivateKey, -} - -impl Wallet { - pub fn new(wif_private_key: &str) -> Self { - let secp = Secp256k1::new(); - let private_key = PrivateKey::from_wif(wif_private_key).unwrap(); - let public_key = private_key.public_key(&secp); - let descriptor = miniscript::Descriptor::new_tr(public_key, None).unwrap(); - - Self { - btc_address: miniscript::Descriptor::new_sh_wpkh(public_key) - .unwrap() - .address(Network::Regtest) - .unwrap(), - descriptor: descriptor.clone(), - ordinal_address: descriptor.address(Network::Regtest).unwrap(), - private_key, - } - } -} - // message_hash = sha256(sha256(tag) || sha256(tag) || message); see BIP340 pub(crate) fn message_hash(message: &[u8]) -> Vec { let mut tag_hash = sha256::Hash::hash(TAG.as_bytes()).to_byte_array().to_vec(); @@ -206,7 +179,7 @@ mod tests { #[test] fn simple_verify_and_falsify_taproot() { assert!( - simple_verify( + verify_simple_encoded( TAPROOT_ADDRESS, "Hello World", "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==" @@ -214,7 +187,7 @@ mod tests { ); assert_eq!( - simple_verify( + verify_simple_encoded( TAPROOT_ADDRESS, "Hello World -- This should fail", "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==" @@ -225,55 +198,35 @@ mod tests { #[test] fn simple_sign_taproot() { - let wallet = Wallet::new(WIF_PRIVATE_KEY); - - let signature = simple_sign( - &Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(), - "Hello World", - &wallet, - ); - assert_eq!( - signature, + sign_simple_encoded(TAPROOT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap(), "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==" ); } #[test] fn roundtrip_taproot_simple() { - let wallet = Wallet::new(WIF_PRIVATE_KEY); - - assert!(simple_verify( + assert!(verify_simple_encoded( TAPROOT_ADDRESS, "Hello World", - &simple_sign( - &Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(), - "Hello World", - &wallet - ) + &sign_simple_encoded(TAPROOT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap() ) .is_ok()); } #[test] fn roundtrip_taproot_full() { - let wallet = Wallet::new(WIF_PRIVATE_KEY); - - assert!(full_verify( + assert!(verify_full_encoded( TAPROOT_ADDRESS, "Hello World", - &full_sign( - &Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(), - "Hello World", - &wallet - ) + &sign_full_encoded(TAPROOT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap() ) .is_ok()); } #[test] fn invalid_address() { - assert_eq!(simple_verify( + assert_eq!(verify_simple_encoded( "3B5fQsEXEaV8v6U3ejYc8XaKXAkyQj2MjV", "", "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=").unwrap_err().to_string(), @@ -284,7 +237,7 @@ mod tests { #[test] fn signature_decode_error() { assert_eq!( - simple_verify( + verify_simple_encoded( TAPROOT_ADDRESS, "Hello World", "invalid signature not in base64 encoding" @@ -295,7 +248,7 @@ mod tests { ); assert_eq!( - simple_verify( + verify_simple_encoded( TAPROOT_ADDRESS, "Hello World", "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViH" diff --git a/src/sign.rs b/src/sign.rs index 26ebecb..1abe1f8 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -1,5 +1,63 @@ use super::*; +/// Signs the BIP-322 simple from encoded values, i.e. address encoding, message string and +/// WIF private key string. Returns a base64 encoded witness stack. +pub fn sign_simple_encoded(address: &str, message: &str, wif_private_key: &str) -> Result { + let address = Address::from_str(address) + .context(error::AddressParse { address })? + .assume_checked(); + + let private_key = PrivateKey::from_wif(wif_private_key).unwrap(); + + let witness = sign_simple(&address, message.as_bytes(), private_key)?; + + let mut buffer = Vec::new(); + witness.consensus_encode(&mut buffer).unwrap(); + + Ok(general_purpose::STANDARD.encode(buffer)) +} + +/// Signs the BIP-322 full from encoded values, i.e. address encoding, message string and +/// WIF private key string. Returns a base64 encoded transaction. +pub fn sign_full_encoded(address: &str, message: &str, wif_private_key: &str) -> Result { + let address = Address::from_str(address) + .context(error::AddressParse { address })? + .assume_checked(); + + let private_key = PrivateKey::from_wif(wif_private_key).unwrap(); + + let tx = sign_full(&address, message.as_bytes(), private_key)?; + + let mut buffer = Vec::new(); + tx.consensus_encode(&mut buffer).unwrap(); + + Ok(general_purpose::STANDARD.encode(buffer)) +} + +/// Signs in the BIP-322 simple format from proper Rust types and returns the witness. +pub fn sign_simple(address: &Address, message: &[u8], private_key: PrivateKey) -> Result { + Ok( + sign_full(&address, message, private_key)?.input[0] + .witness + .clone(), + ) +} + +/// Signs in the BIP-322 full format from proper Rust types and returns the full transaction. +pub fn sign_full( + address: &Address, + message: &[u8], + private_key: PrivateKey, +) -> Result { + let to_spend = create_to_spend(&address, message); + let mut to_sign = create_to_sign(&to_spend, None); + + let witness = create_message_signature(&to_spend, &to_sign, private_key); + to_sign.inputs[0].final_script_witness = Some(witness); + + Ok(to_sign.extract_tx().unwrap()) +} + fn create_message_signature( to_spend_tx: &Transaction, to_sign: &Psbt, @@ -51,32 +109,3 @@ fn create_message_signature( witness.to_owned() } - -pub fn simple_sign(address: &Address, message: &str, wallet: &Wallet) -> String { - let to_spend = create_to_spend(address, message.as_bytes()); - let to_sign = create_to_sign(&to_spend, None); - - let witness = create_message_signature(&to_spend, &to_sign, wallet.private_key); - - let mut buffer = Vec::new(); - witness.consensus_encode(&mut buffer).unwrap(); - - general_purpose::STANDARD.encode(buffer) -} - -pub fn full_sign(address: &Address, message: &str, wallet: &Wallet) -> String { - let to_spend = create_to_spend(address, message.as_bytes()); - let mut to_sign = create_to_sign(&to_spend, None); - - let witness = create_message_signature(&to_spend, &to_sign, wallet.private_key); - to_sign.inputs[0].final_script_witness = Some(witness); - - let mut buffer = Vec::new(); - to_sign - .extract_tx() - .unwrap() - .consensus_encode(&mut buffer) - .unwrap(); - - general_purpose::STANDARD.encode(buffer) -} diff --git a/src/verify.rs b/src/verify.rs index 6901b98..968a810 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -1,9 +1,8 @@ use super::*; -/// This completely outward facing function is meant to be consumed by very naive users like when -/// compiling this library to WASM, where Javascript has no type safety. If you'd like to use the -/// more type safe / Rust variant use `fn simple_verify_inner`. -pub fn simple_verify(address: &str, message: &str, signature: &str) -> Result<()> { +/// Verifies the BIP-322 simple from encoded values, i.e. address encoding, message string and +/// signature base64 string +pub fn verify_simple_encoded(address: &str, message: &str, signature: &str) -> Result<()> { let address = Address::from_str(address) .context(error::AddressParse { address })? .assume_checked(); @@ -17,24 +16,12 @@ pub fn simple_verify(address: &str, message: &str, signature: &str) -> Result<() let witness = Witness::consensus_decode_from_finite_reader(&mut cursor).context(error::MalformedWitness)?; - simple_verify_inner(&address, message.as_bytes(), witness) + verify_simple(&address, message.as_bytes(), witness) } -/// Meant to be consumed by Rust applications -pub fn simple_verify_inner(address: &Address, message: &[u8], signature: Witness) -> Result<()> { - full_verify_inner( - address, - message, - create_to_sign(&create_to_spend(address, message), Some(signature)) - .extract_tx() - .unwrap(), - ) -} - -/// This completely outward facing function is meant to be consumed by very naive users like when -/// compiling this library to WASM, where Javascript has no type safety. If you'd like to use the -/// more type safe / Rust variant use `fn full_verify_inner`. -pub fn full_verify(address: &str, message: &str, to_sign: &str) -> Result<()> { +/// Verifies the BIP-322 full from encoded values, i.e. address encoding, message string and +/// transaction base64 string +pub fn verify_full_encoded(address: &str, message: &str, to_sign: &str) -> Result<()> { let address = Address::from_str(address) .context(error::AddressParse { address })? .assume_checked(); @@ -51,11 +38,22 @@ pub fn full_verify(address: &str, message: &str, to_sign: &str) -> Result<()> { }, )?; - full_verify_inner(&address, message.as_bytes(), to_sign) + verify_full(&address, message.as_bytes(), to_sign) +} + +/// Verifies the BIP-322 simple from proper Rust types +pub fn verify_simple(address: &Address, message: &[u8], signature: Witness) -> Result<()> { + verify_full( + address, + message, + create_to_sign(&create_to_spend(address, message), Some(signature)) + .extract_tx() + .unwrap(), + ) } -/// Meant to be consumed by Rust applications -pub fn full_verify_inner(address: &Address, message: &[u8], to_sign: Transaction) -> Result<()> { +/// Verifies the BIP-322 full from proper Rust types +pub fn verify_full(address: &Address, message: &[u8], to_sign: Transaction) -> Result<()> { if address .address_type() .is_some_and(|addr| addr != AddressType::P2tr) From 41a7a934901457037e298b89bb22b60ea6d9928c Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 22:41:27 +0200 Subject: [PATCH 09/14] Amend --- src/error.rs | 20 ++++++++++++++++---- src/lib.rs | 31 +++++++++++++++++++------------ src/sign.rs | 19 ++++++++++++------- src/verify.rs | 27 ++++++++++++++------------- 4 files changed, 61 insertions(+), 36 deletions(-) diff --git a/src/error.rs b/src/error.rs index 9fe20be..48b53ea 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,6 +8,8 @@ pub enum Error { source: bitcoin::address::ParseError, address: String, }, + #[snafu(display("Failed to parse private key"))] + PrivateKeyParse { source: bitcoin::key::Error }, #[snafu(display("Unsuported address `{address}`, only P2TR allowed"))] UnsupportedAddress { address: String }, #[snafu(display("Invalid"))] @@ -22,28 +24,38 @@ pub enum Error { source: base64::DecodeError, transaction: String, }, + #[snafu(display("Transaction encode error"))] + TransactionEncode { source: std::io::Error }, + #[snafu(display("Transaction extract error"))] + TransactionExtract { + source: bitcoin::psbt::ExtractTxError, + }, + #[snafu(display("PSBT extract error"))] + PsbtExtract { source: bitcoin::psbt::Error }, #[snafu(display("Consensu decode error for transaction `{transaction}`"))] TransactionConsensusDecode { source: bitcoin::consensus::encode::Error, transaction: String, }, #[snafu(display("Witness signature"))] - MalformedWitness { + WitnessMalformed { source: bitcoin::consensus::encode::Error, }, + #[snafu(display("Encode witness error"))] + WitnessEncoding { source: std::io::Error }, #[snafu(display("Signature of wrong length `{length}`"))] SignatureLength { length: usize, encoded_signature: Vec, }, #[snafu(display("Invalid signature because: `{}`", source.to_string()))] - InvalidSignature { source: bitcoin::secp256k1::Error }, + SignatureInvalid { source: bitcoin::secp256k1::Error }, #[snafu(display("Invalid sighash"))] - InvalidSigHash { + SigHashTypeInvalid { source: bitcoin::sighash::InvalidSighashTypeError, }, #[snafu(display("Unsupported sighash type `{sighash_type}`"))] - UnsupportedSigHashType { sighash_type: String }, + SigHashTypeUnsupported { sighash_type: String }, #[snafu(display("Not key path spend"))] NotKeyPathSpend, } diff --git a/src/lib.rs b/src/lib.rs index b0f802f..93ef36f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ mod sign; mod verify; pub use { - sign::{sign_full_encoded, sign_simple_encoded}, + sign::{sign_full, sign_full_encoded, sign_simple, sign_simple_encoded}, verify::{verify_full, verify_full_encoded, verify_simple, verify_simple_encoded}, }; @@ -34,7 +34,6 @@ const TAG: &str = "BIP0322-signed-message"; type Result = std::result::Result; -// message_hash = sha256(sha256(tag) || sha256(tag) || message); see BIP340 pub(crate) fn message_hash(message: &[u8]) -> Vec { let mut tag_hash = sha256::Hash::hash(TAG.as_bytes()).to_byte_array().to_vec(); tag_hash.extend(tag_hash.clone()); @@ -45,8 +44,8 @@ pub(crate) fn message_hash(message: &[u8]) -> Vec { .to_vec() } -pub(crate) fn create_to_spend(address: &Address, message: &[u8]) -> Transaction { - Transaction { +pub(crate) fn create_to_spend(address: &Address, message: &[u8]) -> Result { + Ok(Transaction { version: Version(0), lock_time: LockTime::ZERO, input: vec![TxIn { @@ -67,10 +66,10 @@ pub(crate) fn create_to_spend(address: &Address, message: &[u8]) -> Transaction value: Amount::from_sat(0), script_pubkey: address.script_pubkey(), }], - } + }) } -pub(crate) fn create_to_sign(to_spend: &Transaction, witness: Option) -> Psbt { +pub(crate) fn create_to_sign(to_spend: &Transaction, witness: Option) -> Result { let inputs = vec![TxIn { previous_output: OutPoint { txid: to_spend.txid(), @@ -93,7 +92,7 @@ pub(crate) fn create_to_sign(to_spend: &Transaction, witness: Option) - }], }; - let mut psbt = Psbt::from_unsigned_tx(to_sign).unwrap(); // TODO + let mut psbt = Psbt::from_unsigned_tx(to_sign).context(error::PsbtExtract)?; psbt.inputs[0].witness_utxo = Some(TxOut { value: Amount::from_sat(0), @@ -102,7 +101,7 @@ pub(crate) fn create_to_sign(to_spend: &Transaction, witness: Option) - psbt.inputs[0].final_script_witness = witness; - psbt + Ok(psbt) } #[cfg(test)] @@ -137,6 +136,7 @@ mod tests { &Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(), "".as_bytes() ) + .unwrap() .txid() .to_string(), "c5680aa69bb8d860bf82d4e9cd3504b55dde018de765a91bb566283c545a99a7" @@ -147,6 +147,7 @@ mod tests { &Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(), "Hello World".as_bytes() ) + .unwrap() .txid() .to_string(), "b79d196740ad5217771c1098fc4a4b51e0535c32236c71f1ea4d61a2d603352b" @@ -158,8 +159,11 @@ mod tests { let to_spend = create_to_spend( &Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(), "".as_bytes(), - ); - let to_sign = create_to_sign(&to_spend, None); + ) + .unwrap(); + + let to_sign = create_to_sign(&to_spend, None).unwrap(); + assert_eq!( to_sign.unsigned_tx.txid().to_string(), "1e9654e951a5ba44c8604c4de6c67fd78a27e81dcadcfe1edf638ba3aaebaed6" @@ -168,8 +172,11 @@ mod tests { let to_spend = create_to_spend( &Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(), "Hello World".as_bytes(), - ); - let to_sign = create_to_sign(&to_spend, None); + ) + .unwrap(); + + let to_sign = create_to_sign(&to_spend, None).unwrap(); + assert_eq!( to_sign.unsigned_tx.txid().to_string(), "88737ae86f2077145f93cc4b153ae9a1cb8d56afa511988c149c5c8c9d93bddf" diff --git a/src/sign.rs b/src/sign.rs index 1abe1f8..0371223 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -7,12 +7,15 @@ pub fn sign_simple_encoded(address: &str, message: &str, wif_private_key: &str) .context(error::AddressParse { address })? .assume_checked(); - let private_key = PrivateKey::from_wif(wif_private_key).unwrap(); + let private_key = PrivateKey::from_wif(wif_private_key).context(error::PrivateKeyParse)?; let witness = sign_simple(&address, message.as_bytes(), private_key)?; let mut buffer = Vec::new(); - witness.consensus_encode(&mut buffer).unwrap(); + + witness + .consensus_encode(&mut buffer) + .context(error::WitnessEncoding)?; Ok(general_purpose::STANDARD.encode(buffer)) } @@ -24,12 +27,14 @@ pub fn sign_full_encoded(address: &str, message: &str, wif_private_key: &str) -> .context(error::AddressParse { address })? .assume_checked(); - let private_key = PrivateKey::from_wif(wif_private_key).unwrap(); + let private_key = PrivateKey::from_wif(wif_private_key).context(error::PrivateKeyParse)?; let tx = sign_full(&address, message.as_bytes(), private_key)?; let mut buffer = Vec::new(); - tx.consensus_encode(&mut buffer).unwrap(); + + tx.consensus_encode(&mut buffer) + .context(error::TransactionEncode)?; Ok(general_purpose::STANDARD.encode(buffer)) } @@ -49,13 +54,13 @@ pub fn sign_full( message: &[u8], private_key: PrivateKey, ) -> Result { - let to_spend = create_to_spend(&address, message); - let mut to_sign = create_to_sign(&to_spend, None); + let to_spend = create_to_spend(&address, message)?; + let mut to_sign = create_to_sign(&to_spend, None)?; let witness = create_message_signature(&to_spend, &to_sign, private_key); to_sign.inputs[0].final_script_witness = Some(witness); - Ok(to_sign.extract_tx().unwrap()) + Ok(to_sign.extract_tx().context(error::TransactionExtract)?) } fn create_message_signature( diff --git a/src/verify.rs b/src/verify.rs index 968a810..e931b23 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -1,7 +1,7 @@ use super::*; /// Verifies the BIP-322 simple from encoded values, i.e. address encoding, message string and -/// signature base64 string +/// signature base64 string. pub fn verify_simple_encoded(address: &str, message: &str, signature: &str) -> Result<()> { let address = Address::from_str(address) .context(error::AddressParse { address })? @@ -14,13 +14,13 @@ pub fn verify_simple_encoded(address: &str, message: &str, signature: &str) -> R ); let witness = - Witness::consensus_decode_from_finite_reader(&mut cursor).context(error::MalformedWitness)?; + Witness::consensus_decode_from_finite_reader(&mut cursor).context(error::WitnessMalformed)?; verify_simple(&address, message.as_bytes(), witness) } /// Verifies the BIP-322 full from encoded values, i.e. address encoding, message string and -/// transaction base64 string +/// transaction base64 string. pub fn verify_full_encoded(address: &str, message: &str, to_sign: &str) -> Result<()> { let address = Address::from_str(address) .context(error::AddressParse { address })? @@ -41,18 +41,18 @@ pub fn verify_full_encoded(address: &str, message: &str, to_sign: &str) -> Resul verify_full(&address, message.as_bytes(), to_sign) } -/// Verifies the BIP-322 simple from proper Rust types +/// Verifies the BIP-322 simple from proper Rust types. pub fn verify_simple(address: &Address, message: &[u8], signature: Witness) -> Result<()> { verify_full( address, message, - create_to_sign(&create_to_spend(address, message), Some(signature)) + create_to_sign(&create_to_spend(address, message)?, Some(signature))? .extract_tx() - .unwrap(), + .context(error::TransactionExtract)?, ) } -/// Verifies the BIP-322 full from proper Rust types +/// Verifies the BIP-322 full from proper Rust types. pub fn verify_full(address: &Address, message: &[u8], to_sign: Transaction) -> Result<()> { if address .address_type() @@ -83,7 +83,7 @@ pub fn verify_full(address: &Address, message: &[u8], to_sign: Transaction) -> R }); }; - let to_spend = create_to_spend(address, message); + let to_spend = create_to_spend(address, message)?; let to_spend_outpoint = OutPoint { txid: to_spend.txid(), @@ -94,7 +94,7 @@ pub fn verify_full(address: &Address, message: &[u8], to_sign: Transaction) -> R return Err(Error::Invalid); } - let to_sign = create_to_sign(&to_spend, Some(to_sign.input[0].witness.clone())); + let to_sign = create_to_sign(&to_spend, Some(to_sign.input[0].witness.clone()))?; if to_spend_outpoint != to_sign.unsigned_tx.input[0].previous_output { return Err(Error::Invalid); @@ -109,11 +109,12 @@ pub fn verify_full(address: &Address, message: &[u8], to_sign: Transaction) -> R let (signature, sighash_type) = match encoded_signature.len() { 65 => ( Signature::from_slice(&encoded_signature.as_slice()[..64]) - .context(error::InvalidSignature)?, - TapSighashType::from_consensus_u8(encoded_signature[64]).context(error::InvalidSigHash)?, + .context(error::SignatureInvalid)?, + TapSighashType::from_consensus_u8(encoded_signature[64]) + .context(error::SigHashTypeInvalid)?, ), 64 => ( - Signature::from_slice(encoded_signature.as_slice()).context(error::InvalidSignature)?, + Signature::from_slice(encoded_signature.as_slice()).context(error::SignatureInvalid)?, TapSighashType::Default, ), _ => { @@ -125,7 +126,7 @@ pub fn verify_full(address: &Address, message: &[u8], to_sign: Transaction) -> R }; if !(sighash_type == TapSighashType::All || sighash_type == TapSighashType::Default) { - return Err(Error::UnsupportedSigHashType { + return Err(Error::SigHashTypeUnsupported { sighash_type: sighash_type.to_string(), }); } From aef45f9eee35d8f55515ead994c4532924065a9d Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 22:42:03 +0200 Subject: [PATCH 10/14] Amend --- src/sign.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sign.rs b/src/sign.rs index 0371223..e9cf832 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -42,7 +42,7 @@ pub fn sign_full_encoded(address: &str, message: &str, wif_private_key: &str) -> /// Signs in the BIP-322 simple format from proper Rust types and returns the witness. pub fn sign_simple(address: &Address, message: &[u8], private_key: PrivateKey) -> Result { Ok( - sign_full(&address, message, private_key)?.input[0] + sign_full(address, message, private_key)?.input[0] .witness .clone(), ) @@ -54,13 +54,13 @@ pub fn sign_full( message: &[u8], private_key: PrivateKey, ) -> Result { - let to_spend = create_to_spend(&address, message)?; + let to_spend = create_to_spend(address, message)?; let mut to_sign = create_to_sign(&to_spend, None)?; let witness = create_message_signature(&to_spend, &to_sign, private_key); to_sign.inputs[0].final_script_witness = Some(witness); - Ok(to_sign.extract_tx().context(error::TransactionExtract)?) + to_sign.extract_tx().context(error::TransactionExtract) } fn create_message_signature( From 7c90ef550c6b94c23d627f2f8f2a1a7d88ff01c0 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 22:57:07 +0200 Subject: [PATCH 11/14] Amend --- src/error.rs | 8 +++++--- src/lib.rs | 2 +- src/sign.rs | 16 ++++++++++++++++ src/verify.rs | 13 ++++--------- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/error.rs b/src/error.rs index 48b53ea..28d342c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -12,8 +12,6 @@ pub enum Error { PrivateKeyParse { source: bitcoin::key::Error }, #[snafu(display("Unsuported address `{address}`, only P2TR allowed"))] UnsupportedAddress { address: String }, - #[snafu(display("Invalid"))] - Invalid, #[snafu(display("Decode error for signature `{signature}`"))] SignatureDecode { source: base64::DecodeError, @@ -30,6 +28,8 @@ pub enum Error { TransactionExtract { source: bitcoin::psbt::ExtractTxError, }, + #[snafu(display("To sign transaction invalid"))] + ToSignInvalid, #[snafu(display("PSBT extract error"))] PsbtExtract { source: bitcoin::psbt::Error }, #[snafu(display("Consensu decode error for transaction `{transaction}`"))] @@ -37,10 +37,12 @@ pub enum Error { source: bitcoin::consensus::encode::Error, transaction: String, }, - #[snafu(display("Witness signature"))] + #[snafu(display("Witness malformed"))] WitnessMalformed { source: bitcoin::consensus::encode::Error, }, + #[snafu(display("Witness empty"))] + WitnessEmpty, #[snafu(display("Encode witness error"))] WitnessEncoding { source: std::io::Error }, #[snafu(display("Signature of wrong length `{length}`"))] diff --git a/src/lib.rs b/src/lib.rs index 93ef36f..bc5f675 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,7 +199,7 @@ mod tests { "Hello World -- This should fail", "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==" ).unwrap_err().to_string(), - "Invalid" + "Invalid signature because: `malformed signature`" ); } diff --git a/src/sign.rs b/src/sign.rs index e9cf832..cd6129b 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -54,6 +54,22 @@ pub fn sign_full( message: &[u8], private_key: PrivateKey, ) -> Result { + if let bitcoin::address::Payload::WitnessProgram(witness_program) = address.payload() { + if witness_program.version().to_num() != 1 { + return Err(Error::UnsupportedAddress { + address: address.to_string(), + }); + } + + if witness_program.program().len() != 32 { + return Err(Error::NotKeyPathSpend); + } + } else { + return Err(Error::UnsupportedAddress { + address: address.to_string(), + }); + }; + let to_spend = create_to_spend(address, message)?; let mut to_sign = create_to_sign(&to_spend, None)?; diff --git a/src/verify.rs b/src/verify.rs index e931b23..a50608f 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -84,24 +84,19 @@ pub fn verify_full(address: &Address, message: &[u8], to_sign: Transaction) -> R }; let to_spend = create_to_spend(address, message)?; + let to_sign = create_to_sign(&to_spend, Some(to_sign.input[0].witness.clone()))?; let to_spend_outpoint = OutPoint { txid: to_spend.txid(), vout: 0, }; - if to_spend_outpoint != to_sign.input[0].previous_output { - return Err(Error::Invalid); - } - - let to_sign = create_to_sign(&to_spend, Some(to_sign.input[0].witness.clone()))?; - if to_spend_outpoint != to_sign.unsigned_tx.input[0].previous_output { - return Err(Error::Invalid); + return Err(Error::ToSignInvalid); } let Some(witness) = to_sign.inputs[0].final_script_witness.clone() else { - return Err(Error::Invalid); + return Err(Error::WitnessEmpty); }; let encoded_signature = witness.to_vec()[0].clone(); @@ -149,5 +144,5 @@ pub fn verify_full(address: &Address, message: &[u8], to_sign: Transaction) -> R Secp256k1::verification_only() .verify_schnorr(&signature, &message, &pub_key) - .map_err(|_| Error::Invalid) + .context(error::SignatureInvalid) } From a9d1f3c2fa9106c51c1b8a23adea62534f715177 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 22:58:46 +0200 Subject: [PATCH 12/14] Amend --- src/error.rs | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/error.rs b/src/error.rs index 28d342c..eaf1bbd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -50,7 +50,7 @@ pub enum Error { length: usize, encoded_signature: Vec, }, - #[snafu(display("Invalid signature because: `{}`", source.to_string()))] + #[snafu(display("Invalid signature"))] SignatureInvalid { source: bitcoin::secp256k1::Error }, #[snafu(display("Invalid sighash"))] SigHashTypeInvalid { diff --git a/src/lib.rs b/src/lib.rs index bc5f675..46a9135 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,7 +199,7 @@ mod tests { "Hello World -- This should fail", "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==" ).unwrap_err().to_string(), - "Invalid signature because: `malformed signature`" + "Invalid signature" ); } From 19196e930d0f4990a217884ebf294b8559a7d273 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 23:01:09 +0200 Subject: [PATCH 13/14] Amend --- src/error.rs | 12 ++++++------ src/verify.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/error.rs b/src/error.rs index eaf1bbd..b3cb59d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,11 +17,6 @@ pub enum Error { source: base64::DecodeError, signature: String, }, - #[snafu(display("Base64 decode error for transaction `{transaction}`"))] - TransactionDecode { - source: base64::DecodeError, - transaction: String, - }, #[snafu(display("Transaction encode error"))] TransactionEncode { source: std::io::Error }, #[snafu(display("Transaction extract error"))] @@ -32,7 +27,12 @@ pub enum Error { ToSignInvalid, #[snafu(display("PSBT extract error"))] PsbtExtract { source: bitcoin::psbt::Error }, - #[snafu(display("Consensu decode error for transaction `{transaction}`"))] + #[snafu(display("Base64 decode error for transaction `{transaction}`"))] + TransactionBase64Decode { + source: base64::DecodeError, + transaction: String, + }, + #[snafu(display("Consensus decode error for transaction `{transaction}`"))] TransactionConsensusDecode { source: bitcoin::consensus::encode::Error, transaction: String, diff --git a/src/verify.rs b/src/verify.rs index a50608f..10f4a3b 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -27,7 +27,7 @@ pub fn verify_full_encoded(address: &str, message: &str, to_sign: &str) -> Resul .assume_checked(); let mut cursor = Cursor::new(general_purpose::STANDARD.decode(to_sign).context( - error::TransactionDecode { + error::TransactionBase64Decode { transaction: to_sign, }, )?); From cb7a1c178ed3e22a7fa6ad711af59665cc327b97 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sat, 10 Aug 2024 23:11:25 +0200 Subject: [PATCH 14/14] Amend --- www/index.js | 13 ++----------- www/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/www/index.js b/www/index.js index e4048bf..2706698 100644 --- a/www/index.js +++ b/www/index.js @@ -1,18 +1,7 @@ import init, { verify } from './bip322.js'; -let wasmInitialized = false; - -async function initializeWasm() { - if (!wasmInitialized) { - await init(); - console.log('WASM module loaded'); - wasmInitialized = true; - } -} - async function runVerification(event) { event.preventDefault(); - await initializeWasm(); const address = document.getElementById('address').value; const message = document.getElementById('message').value; @@ -52,6 +41,8 @@ function handleKeyPress(event) { } } +await init(); + document.getElementById('bip').addEventListener('click', showForm); document.getElementById('verify-form').addEventListener('submit', runVerification); diff --git a/www/src/lib.rs b/www/src/lib.rs index 5635ab7..3f5fabc 100644 --- a/www/src/lib.rs +++ b/www/src/lib.rs @@ -2,5 +2,5 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn verify(address: &str, message: &str, signature: &str) -> bool { - bip322::simple_verify(&address, message, signature).is_ok() + bip322::verify_simple_encoded(&address, message, signature).is_ok() }