diff --git a/src/lib.rs b/src/lib.rs index a03de5f4..addd8632 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,87 +24,13 @@ use { mod error; mod sign; +mod util; mod verify; -pub use { - sign::{sign_full, sign_full_encoded, sign_simple, sign_simple_encoded}, - verify::{verify_full, verify_full_encoded, verify_simple, verify_simple_encoded}, -}; - -const TAG: &str = "BIP0322-signed-message"; +pub use {sign::*, util::*, verify::*}; type Result = std::result::Result; -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); - - sha256::Hash::hash(tag_hash.as_slice()) - .to_byte_array() - .to_vec() -} - -pub(crate) fn create_to_spend(address: &Address, message: &[u8]) -> Result { - Ok(Transaction { - version: Version(0), - lock_time: LockTime::ZERO, - input: vec![TxIn { - previous_output: OutPoint { - txid: "0000000000000000000000000000000000000000000000000000000000000000" - .parse() - .unwrap(), - vout: 0xFFFFFFFF, - }, - script_sig: script::Builder::new() - .push_int(0) - .push_slice::<&PushBytes>(message_hash(message).as_slice().try_into().unwrap()) - .into_script(), - sequence: Sequence(0), - witness: Witness::new(), - }], - output: vec![TxOut { - value: Amount::from_sat(0), - script_pubkey: address.script_pubkey(), - }], - }) -} - -pub(crate) fn create_to_sign(to_spend: &Transaction, witness: Option) -> Result { - let inputs = vec![TxIn { - previous_output: OutPoint { - txid: to_spend.txid(), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: Sequence(0), - witness: Witness::new(), - }]; - - let to_sign = Transaction { - version: Version(0), - lock_time: LockTime::ZERO, - input: inputs, - output: vec![TxOut { - value: Amount::from_sat(0), - script_pubkey: script::Builder::new() - .push_opcode(opcodes::all::OP_RETURN) - .into_script(), - }], - }; - - let mut psbt = Psbt::from_unsigned_tx(to_sign).context(error::PsbtExtract)?; - - 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; - - Ok(psbt) -} - #[cfg(test)] mod tests { use {super::*, pretty_assertions::assert_eq}; @@ -187,7 +113,7 @@ mod tests { #[test] fn simple_verify_and_falsify_taproot() { assert!( - verify_simple_encoded( + verify::verify_simple_encoded( TAPROOT_ADDRESS, "Hello World", "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==" @@ -195,7 +121,7 @@ mod tests { ); assert_eq!( - verify_simple_encoded( + verify::verify_simple_encoded( TAPROOT_ADDRESS, "Hello World -- This should fail", "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==" @@ -207,34 +133,34 @@ mod tests { #[test] fn simple_sign_taproot() { assert_eq!( - sign_simple_encoded(TAPROOT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap(), + sign::sign_simple_encoded(TAPROOT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap(), "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==" ); } #[test] fn roundtrip_taproot_simple() { - assert!(verify_simple_encoded( + assert!(verify::verify_simple_encoded( TAPROOT_ADDRESS, "Hello World", - &sign_simple_encoded(TAPROOT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap() + &sign::sign_simple_encoded(TAPROOT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap() ) .is_ok()); } #[test] fn roundtrip_taproot_full() { - assert!(verify_full_encoded( + assert!(verify::verify_full_encoded( TAPROOT_ADDRESS, "Hello World", - &sign_full_encoded(TAPROOT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap() + &sign::sign_full_encoded(TAPROOT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap() ) .is_ok()); } #[test] fn invalid_address() { - assert_eq!(verify_simple_encoded( + assert_eq!(verify::verify_simple_encoded( "3B5fQsEXEaV8v6U3ejYc8XaKXAkyQj2MjV", "", "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=").unwrap_err().to_string(), @@ -245,7 +171,7 @@ mod tests { #[test] fn signature_decode_error() { assert_eq!( - verify_simple_encoded( + verify::verify_simple_encoded( TAPROOT_ADDRESS, "Hello World", "invalid signature not in base64 encoding" @@ -256,7 +182,7 @@ mod tests { ); assert_eq!( - verify_simple_encoded( + verify::verify_simple_encoded( TAPROOT_ADDRESS, "Hello World", "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViH" @@ -268,7 +194,7 @@ mod tests { #[test] fn simple_verify_and_falsify_p2wpkh() { assert!( - verify_simple_encoded( + verify::verify_simple_encoded( SEGWIT_ADDRESS, "Hello World", "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=" @@ -276,7 +202,7 @@ mod tests { ); assert!( - verify_simple_encoded( + verify::verify_simple_encoded( SEGWIT_ADDRESS, "Hello World - this should fail", "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=" @@ -284,7 +210,7 @@ mod tests { ); assert!( - verify_simple_encoded( + verify::verify_simple_encoded( SEGWIT_ADDRESS, "Hello World", "AkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy" @@ -292,7 +218,7 @@ mod tests { ); assert!( - verify_simple_encoded( + verify::verify_simple_encoded( SEGWIT_ADDRESS, "", "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=" @@ -300,7 +226,7 @@ mod tests { ); assert!( - verify_simple_encoded( + verify::verify_simple_encoded( SEGWIT_ADDRESS, "fail", "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=" @@ -308,7 +234,7 @@ mod tests { ); assert!( - verify_simple_encoded( + verify::verify_simple_encoded( SEGWIT_ADDRESS, "", "AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy" @@ -319,32 +245,32 @@ mod tests { #[test] fn simple_sign_p2wpkh() { assert_eq!( - sign_simple_encoded(SEGWIT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap(), + sign::sign_simple_encoded(SEGWIT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap(), "AkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy" ); assert_eq!( - sign_simple_encoded(SEGWIT_ADDRESS, "", WIF_PRIVATE_KEY).unwrap(), + sign::sign_simple_encoded(SEGWIT_ADDRESS, "", WIF_PRIVATE_KEY).unwrap(), "AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy" ); } #[test] fn roundtrip_p2wpkh_simple() { - assert!(verify_simple_encoded( + assert!(verify::verify_simple_encoded( SEGWIT_ADDRESS, "Hello World", - &sign_simple_encoded(SEGWIT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap() + &sign::sign_simple_encoded(SEGWIT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap() ) .is_ok()); } #[test] fn roundtrip_p2wpkh_full() { - assert!(verify_full_encoded( + assert!(verify::verify_full_encoded( SEGWIT_ADDRESS, "Hello World", - &sign_full_encoded(SEGWIT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap() + &sign::sign_full_encoded(SEGWIT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap() ) .is_ok()); } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 00000000..ee383569 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,76 @@ +use super::*; + +const TAG: &str = "BIP0322-signed-message"; + +/// Create the tagged message hash. +pub 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); + + sha256::Hash::hash(tag_hash.as_slice()) + .to_byte_array() + .to_vec() +} + +/// Create the `to_spend` transaction. +pub fn create_to_spend(address: &Address, message: &[u8]) -> Result { + Ok(Transaction { + version: Version(0), + lock_time: LockTime::ZERO, + input: vec![TxIn { + previous_output: OutPoint { + txid: "0000000000000000000000000000000000000000000000000000000000000000" + .parse() + .unwrap(), + vout: 0xFFFFFFFF, + }, + script_sig: script::Builder::new() + .push_int(0) + .push_slice::<&PushBytes>(message_hash(message).as_slice().try_into().unwrap()) + .into_script(), + sequence: Sequence(0), + witness: Witness::new(), + }], + output: vec![TxOut { + value: Amount::from_sat(0), + script_pubkey: address.script_pubkey(), + }], + }) +} + +/// Create the `to_sign` transaction. +pub fn create_to_sign(to_spend: &Transaction, witness: Option) -> Result { + let inputs = vec![TxIn { + previous_output: OutPoint { + txid: to_spend.txid(), + vout: 0, + }, + script_sig: ScriptBuf::new(), + sequence: Sequence(0), + witness: Witness::new(), + }]; + + let to_sign = Transaction { + version: Version(0), + lock_time: LockTime::ZERO, + input: inputs, + output: vec![TxOut { + value: Amount::from_sat(0), + script_pubkey: script::Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .into_script(), + }], + }; + + let mut psbt = Psbt::from_unsigned_tx(to_sign).context(error::PsbtExtract)?; + + 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; + + Ok(psbt) +}