Skip to content

Commit

Permalink
Add util module (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Aug 22, 2024
1 parent b060937 commit b0441fe
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 98 deletions.
122 changes: 24 additions & 98 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T = (), E = Error> = std::result::Result<T, E>;

pub(crate) fn message_hash(message: &[u8]) -> Vec<u8> {
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<Transaction> {
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<Witness>) -> Result<Psbt> {
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};
Expand Down Expand Up @@ -187,15 +113,15 @@ 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=="
).is_ok()
);

assert_eq!(
verify_simple_encoded(
verify::verify_simple_encoded(
TAPROOT_ADDRESS,
"Hello World -- This should fail",
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
Expand All @@ -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(),
Expand All @@ -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"
Expand All @@ -256,7 +182,7 @@ mod tests {
);

assert_eq!(
verify_simple_encoded(
verify::verify_simple_encoded(
TAPROOT_ADDRESS,
"Hello World",
"AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViH"
Expand All @@ -268,47 +194,47 @@ 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="
).is_ok()
);

assert!(
verify_simple_encoded(
verify::verify_simple_encoded(
SEGWIT_ADDRESS,
"Hello World - this should fail",
"AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="
).is_err()
);

assert!(
verify_simple_encoded(
verify::verify_simple_encoded(
SEGWIT_ADDRESS,
"Hello World",
"AkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy"
).is_ok()
);

assert!(
verify_simple_encoded(
verify::verify_simple_encoded(
SEGWIT_ADDRESS,
"",
"AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="
).is_ok()
);

assert!(
verify_simple_encoded(
verify::verify_simple_encoded(
SEGWIT_ADDRESS,
"fail",
"AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="
).is_err()
);

assert!(
verify_simple_encoded(
verify::verify_simple_encoded(
SEGWIT_ADDRESS,
"",
"AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy"
Expand All @@ -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());
}
Expand Down
76 changes: 76 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use super::*;

const TAG: &str = "BIP0322-signed-message";

/// Create the tagged message hash.
pub fn message_hash(message: &[u8]) -> Vec<u8> {
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<Transaction> {
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<Witness>) -> Result<Psbt> {
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)
}

0 comments on commit b0441fe

Please sign in to comment.