From 8103feed79aabb652ee4844c78765d284ee26164 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 20 Aug 2024 17:32:33 +0200 Subject: [PATCH] Add signing --- src/lib.rs | 69 ++++++++++++++++++++++++++++++++++++-- src/sign.rs | 93 ++++++++++++++++++++++++++++++++++++++++++--------- src/verify.rs | 21 ++++++------ 3 files changed, 153 insertions(+), 30 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d8075d6..699f36e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,7 +115,6 @@ mod tests { const WIF_PRIVATE_KEY: &str = "L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k"; const SEGWIT_ADDRESS: &str = "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l"; const TAPROOT_ADDRESS: &str = "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3"; - const SEGWIT_ADDRESS_LOCAL: &str = "bc1qvf8tafcx6yncee9f7jvza5nhnyalp32jjuhw6w"; #[test] fn message_hashes_are_correct() { @@ -266,7 +265,71 @@ mod tests { } #[test] - fn verify_valid_p2wpkh_signature() { - assert!(verify_simple_encoded(SEGWIT_ADDRESS_LOCAL, "Hello World gotcha", "AkcwRAIgJcWpci9gC4x9YMSGReCXx6IrylfHz8LzHrid8QXU4DECICMh74J1qn/XT8BGNCtxsFxa2rX7r02BYsLALYMNnDF0ASECcB+M459ms4JoioDlHLut7acdmMxFdMwsNCTlpoZWS2s=").is_ok()) + fn simple_verify_and_falsify_p2wpkh() { + assert!( + verify_simple_encoded( + SEGWIT_ADDRESS, + "Hello World", + "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=" + ).is_ok() + ); + + assert!( + verify_simple_encoded( + SEGWIT_ADDRESS, + "Hello World - this should fail", + "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=" + ).is_err() + ); + + assert!( + verify_simple_encoded( + SEGWIT_ADDRESS, + "Hello World", + "AkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy" + ).is_ok() + ); + + assert!( + verify_simple_encoded( + SEGWIT_ADDRESS, + "", + "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=" + ).is_ok() + ); + + assert!( + verify_simple_encoded( + SEGWIT_ADDRESS, + "fail", + "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=" + ).is_err() + ); + + assert!( + verify_simple_encoded( + SEGWIT_ADDRESS, + "", + "AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy" + ).is_ok() + ); + } + + #[test] + fn simple_sign_p2wpkh() { + assert_eq!( + sign_simple_encoded(SEGWIT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap(), + "AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy" + ); + } + + #[test] + fn roundtrip_p2wpkh_simple() { + assert!(verify_simple_encoded( + SEGWIT_ADDRESS, + "Hello World", + &sign_simple_encoded(SEGWIT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap() + ) + .is_ok()); } } diff --git a/src/sign.rs b/src/sign.rs index cd6129b..e28479c 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -1,3 +1,5 @@ +use bitcoin::EcdsaSighashType; + use super::*; /// Signs the BIP-322 simple from encoded values, i.e. address encoding, message string and @@ -54,40 +56,99 @@ 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 { + let to_spend = create_to_spend(address, message)?; + let mut to_sign = create_to_sign(&to_spend, None)?; + + let witness = + if let bitcoin::address::Payload::WitnessProgram(witness_program) = address.payload() { + let version = witness_program.version().to_num(); + let program_len = witness_program.program().len(); + + match version { + 0 => { + if program_len != 20 { + return Err(Error::NotKeyPathSpend); // TODO + } + create_message_signature_p2wpkh(&to_spend, &to_sign, private_key) + } + 1 => { + if program_len != 32 { + return Err(Error::NotKeyPathSpend); // TODO + } + create_message_signature_taproot(&to_spend, &to_sign, private_key) + } + _ => { + return Err(Error::UnsupportedAddress { + address: address.to_string(), + }) + } + } + } else { return Err(Error::UnsupportedAddress { + // TODO maybe rename to addres type? address: address.to_string(), }); - } + }; - if witness_program.program().len() != 32 { - return Err(Error::NotKeyPathSpend); - } - } else { - return Err(Error::UnsupportedAddress { - address: address.to_string(), - }); - }; + dbg!(&witness); - 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); to_sign.extract_tx().context(error::TransactionExtract) } -fn create_message_signature( +fn create_message_signature_p2wpkh( + to_spend_tx: &Transaction, + to_sign: &Psbt, + private_key: PrivateKey, +) -> Witness { + let secp = Secp256k1::new(); + let sighash_type = EcdsaSighashType::All; + let mut sighash_cache = SighashCache::new(to_sign.unsigned_tx.clone()); + + let sighash = sighash_cache + .p2wpkh_signature_hash( + 0, + &to_spend_tx.output[0].script_pubkey, + to_spend_tx.output[0].value, + sighash_type, + ) + .expect("signature hash should compute"); + + let sig = secp.sign_ecdsa( + &secp256k1::Message::from_digest_slice(sighash.as_ref()) + .expect("should be cryptographically secure hash"), + &private_key.inner, + ); + + let witness = sighash_cache + .witness_mut(0) + .expect("getting mutable witness reference should work"); + + witness.push( + bitcoin::ecdsa::Signature { + sig, + hash_ty: sighash_type, + } + .to_vec(), + ); + + witness.push(private_key.public_key(&secp).to_bytes()); + + witness.to_owned() +} + +fn create_message_signature_taproot( to_spend_tx: &Transaction, to_sign: &Psbt, private_key: PrivateKey, ) -> Witness { + println!("here"); let mut to_sign = to_sign.clone(); let secp = Secp256k1::new(); let key_pair = Keypair::from_secret_key(&secp, &private_key.inner); + let (x_only_public_key, _parity) = XOnlyPublicKey::from_keypair(&key_pair); to_sign.inputs[0].tap_internal_key = Some(x_only_public_key); diff --git a/src/verify.rs b/src/verify.rs index 56fec17..a981ca4 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -121,17 +121,16 @@ fn verify_full_p2wpkh( return Err(Error::PublicKeyMismatch); } - let (signature, sighash_type) = match encoded_signature.len() { - 71 | 72 => { - let len = encoded_signature.len(); - ( - bitcoin::secp256k1::ecdsa::Signature::from_der_lax( - &encoded_signature.as_slice()[..len - 1], - ) - .context(error::SignatureInvalid)?, - EcdsaSighashType::from_consensus(encoded_signature[len - 1] as u32), + let signature_length = encoded_signature.len(); + + let (signature, sighash_type) = match signature_length { + 71 | 72 => ( + bitcoin::secp256k1::ecdsa::Signature::from_der( + &encoded_signature.as_slice()[..signature_length - 1], ) - } + .context(error::SignatureInvalid)?, + EcdsaSighashType::from_consensus(encoded_signature[signature_length - 1] as u32), + ), _ => { return Err(Error::SignatureLength { length: encoded_signature.len(), @@ -161,7 +160,7 @@ fn verify_full_p2wpkh( Message::from_digest_slice(sighash.as_ref()).expect("should be cryptographically secure hash"); Secp256k1::verification_only() - .verify_ecdsa(&message, &(signature), &pub_key.inner) + .verify_ecdsa(&message, &signature, &pub_key.inner) .context(error::SignatureInvalid)?; Ok(())