Skip to content
Merged
1 change: 0 additions & 1 deletion mm2src/derives/enum_derives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ pub fn derive(input: TokenStream) -> TokenStream {
/// Polygon,
/// }
///
///#[test]
///fn test_enum_variant_list() {
/// let all_chains = Chain::variant_list();
/// assert_eq!(all_chains, vec![
Expand Down
245 changes: 240 additions & 5 deletions mm2src/mm2_bitcoin/script/src/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use hash::{H256, H512};
use keys::KeyPair;
use ser::Stream;
use serde::Deserialize;
use std::collections::BTreeSet;
use std::convert::TryInto;
use {Builder, Script};

Expand All @@ -29,6 +30,8 @@ pub enum SignatureVersion {
WitnessV0,
#[serde(rename = "fork_id")]
ForkId,
#[serde(rename = "fork_id_rxd")]
ForkIdRxd,
}

#[derive(Debug, PartialEq, Clone, Copy)]
Expand Down Expand Up @@ -79,7 +82,7 @@ impl Sighash {
pub fn is_defined(version: SignatureVersion, u: u32) -> bool {
// reset anyone_can_pay && fork_id (if applicable) bits
let u = match version {
SignatureVersion::ForkId => u & !(0x40 | 0x80),
SignatureVersion::ForkId | SignatureVersion::ForkIdRxd => u & !(0x40 | 0x80),
_ => u & !(0x80),
};

Expand All @@ -90,7 +93,7 @@ impl Sighash {
/// Creates Sighash from any u, even if is_defined() == false
pub fn from_u32(version: SignatureVersion, u: u32) -> Self {
let anyone_can_pay = (u & 0x80) == 0x80;
let fork_id = version == SignatureVersion::ForkId && (u & 0x40) == 0x40;
let fork_id = matches!(version, SignatureVersion::ForkId | SignatureVersion::ForkIdRxd) && (u & 0x40) == 0x40;
let base = match u & 0x1f {
2 => SighashBase::None,
3 => SighashBase::Single,
Expand Down Expand Up @@ -246,7 +249,10 @@ impl TransactionInputSigner {
SignatureVersion::ForkId if sighash.fork_id => {
self.signature_hash_fork_id(input_index, input_amount, script_pubkey, sighashtype, sighash)
},
SignatureVersion::Base | SignatureVersion::ForkId => {
SignatureVersion::ForkIdRxd if sighash.fork_id => {
self.signature_hash_fork_id_rxd(input_index, input_amount, script_pubkey, sighashtype, sighash)
},
SignatureVersion::Base | SignatureVersion::ForkId | SignatureVersion::ForkIdRxd => {
self.signature_hash_original(input_index, script_pubkey, sighashtype, sighash)
},
SignatureVersion::WitnessV0 => {
Expand Down Expand Up @@ -435,6 +441,43 @@ impl TransactionInputSigner {
self.signature_hash_witness0(input_index, input_amount, script_pubkey, sighashtype, sighash)
}

fn signature_hash_fork_id_rxd(
&self,
input_index: usize,
input_amount: u64,
script_pubkey: &Script,
sighashtype: u32,
sighash: Sighash,
) -> H256 {
if input_index >= self.inputs.len() {
return 1u8.into();
}

if sighash.base == SighashBase::Single && input_index >= self.outputs.len() {
return 1u8.into();
}

let hash_prevouts = compute_hash_prevouts(sighash, &self.inputs);
let hash_sequence = compute_hash_sequence(sighash, &self.inputs);
let hash_output_hashes = compute_hash_output_hashes_rxd(sighash, input_index, &self.outputs);
let hash_outputs = compute_hash_outputs(sighash, input_index, &self.outputs);

let mut stream = Stream::default();
stream.append(&self.version);
stream.append(&hash_prevouts);
stream.append(&hash_sequence);
stream.append(&self.inputs[input_index].previous_output);
stream.append_list(script_pubkey);
stream.append(&input_amount);
stream.append(&self.inputs[input_index].sequence);
stream.append(&hash_output_hashes);
stream.append(&hash_outputs);
stream.append(&self.lock_time);
stream.append(&sighashtype);
let out = stream.out();
dhash256(&out)
}

/// https://github.com/zcash/zips/blob/master/zip-0243.rst#notes
/// This method doesn't cover all possible Sighash combinations so it doesn't fully match the
/// specification, however I don't need other cases yet as BarterDEX marketmaker always uses
Expand Down Expand Up @@ -615,6 +658,126 @@ fn compute_hash_outputs(sighash: Sighash, input_index: usize, outputs: &[Transac
}
}

fn compute_hash_output_hashes_rxd(sighash: Sighash, input_index: usize, outputs: &[TransactionOutput]) -> H256 {
match sighash.base {
SighashBase::All => {
let mut stream = Stream::default();
for output in outputs {
append_output_data_summary_rxd(&mut stream, output);
}
dhash256(&stream.out())
},
SighashBase::Single if input_index < outputs.len() => {
let mut stream = Stream::default();
append_output_data_summary_rxd(&mut stream, &outputs[input_index]);
dhash256(&stream.out())
},
SighashBase::Single | SighashBase::None => 0u8.into(),
}
}

fn append_output_data_summary_rxd(stream: &mut Stream, output: &TransactionOutput) {
let script_pubkey_hash = dhash256(output.script_pubkey.as_ref());
let sorted_push_refs = sorted_push_refs_rxd(output.script_pubkey.as_ref());
let total_refs: u32 = sorted_push_refs.len() as u32;
let refs_hash = if sorted_push_refs.is_empty() {
0u8.into()
} else {
let refs_concat: Vec<u8> = sorted_push_refs.into_iter().flatten().collect();
dhash256(&refs_concat)
};

stream.append(&output.value);
stream.append(&script_pubkey_hash);
stream.append(&total_refs);
stream.append(&refs_hash);
}

fn sorted_push_refs_rxd(script_pubkey: &[u8]) -> Vec<[u8; 36]> {
const OP_PUSHDATA1: u8 = 0x4c;
const OP_PUSHDATA2: u8 = 0x4d;
const OP_PUSHDATA4: u8 = 0x4e;
const OP_PUSHINPUTREF: u8 = 0xd0;
const OP_REQUIREINPUTREF: u8 = 0xd1;
const OP_DISALLOWPUSHINPUTREF: u8 = 0xd2;
const OP_DISALLOWPUSHINPUTREFSIBLING: u8 = 0xd3;
const OP_PUSHINPUTREFSINGLETON: u8 = 0xd8;

let mut refs = BTreeSet::new();
let mut pos = 0usize;

while pos < script_pubkey.len() {
let opcode = script_pubkey[pos];
pos += 1;

match opcode {
0x01..=0x4b => {
let push_len = opcode as usize;
match pos.checked_add(push_len) {
Some(end) if end <= script_pubkey.len() => pos = end,
_ => break,
}
},
OP_PUSHDATA1 => {
if pos >= script_pubkey.len() {
break;
}
let push_len = script_pubkey[pos] as usize;
pos += 1;
match pos.checked_add(push_len) {
Some(end) if end <= script_pubkey.len() => pos = end,
_ => break,
}
},
OP_PUSHDATA2 => {
if pos + 2 > script_pubkey.len() {
break;
}
let push_len = u16::from_le_bytes([script_pubkey[pos], script_pubkey[pos + 1]]) as usize;
pos += 2;
match pos.checked_add(push_len) {
Some(end) if end <= script_pubkey.len() => pos = end,
_ => break,
}
},
OP_PUSHDATA4 => {
if pos + 4 > script_pubkey.len() {
break;
}
let push_len = u32::from_le_bytes([
script_pubkey[pos],
script_pubkey[pos + 1],
script_pubkey[pos + 2],
script_pubkey[pos + 3],
]) as usize;
pos += 4;
match pos.checked_add(push_len) {
Some(end) if end <= script_pubkey.len() => pos = end,
_ => break,
}
},
OP_PUSHINPUTREF
| OP_PUSHINPUTREFSINGLETON
| OP_REQUIREINPUTREF
| OP_DISALLOWPUSHINPUTREF
| OP_DISALLOWPUSHINPUTREFSIBLING => {
if pos + 36 > script_pubkey.len() {
break;
}
if matches!(opcode, OP_PUSHINPUTREF | OP_PUSHINPUTREFSINGLETON) {
let mut reference = [0u8; 36];
reference.copy_from_slice(&script_pubkey[pos..pos + 36]);
refs.insert(reference);
}
pos += 36;
},
_ => (),
}
}

refs.into_iter().collect()
}

fn blake_2b_256_personal(input: &[u8], personal: &[u8]) -> Result<H256, String> {
let bytes: [u8; 32] = Blake2b::new()
.hash_length(32)
Expand All @@ -631,14 +794,15 @@ fn blake_2b_256_personal(input: &[u8], personal: &[u8]) -> Result<H256, String>
#[cfg(test)]
mod tests {
use super::{
blake_2b_256_personal, Sighash, SighashBase, SignatureVersion, TransactionInputSigner, UnsignedTransactionInput,
blake_2b_256_personal, sorted_push_refs_rxd, Sighash, SighashBase, SignatureVersion, TransactionInputSigner,
UnsignedTransactionInput,
};
use bytes::Bytes;
use chain::{OutPoint, Transaction, TransactionOutput};
use hash::{H160, H256};
use keys::{
prefixes::{BTC_PREFIXES, T_BTC_PREFIXES},
Address, AddressHashEnum, Private,
Address, AddressHashEnum, Private, Public, Signature,
};
use script::Script;
use ser::deserialize;
Expand Down Expand Up @@ -792,6 +956,77 @@ mod tests {
assert!(Sighash::is_defined(SignatureVersion::ForkId, 0x00000081));
assert!(Sighash::is_defined(SignatureVersion::ForkId, 0x000000C2));
assert!(Sighash::is_defined(SignatureVersion::ForkId, 0x00000043));

assert!(Sighash::is_defined(SignatureVersion::ForkIdRxd, 0x00000081));
assert!(Sighash::is_defined(SignatureVersion::ForkIdRxd, 0x000000C2));
assert!(Sighash::is_defined(SignatureVersion::ForkIdRxd, 0x00000043));
}

#[test]
fn test_rxd_forkid_sighash_vector() {
const SPEND_TX_HEX: &str = "0100000002502d0525588143dee5d3857b6b901805fb4ec53cca16b374f25bf8c3e12644ee010000006a47304402204715bf639b322d08cfabb2b1b7af1ba8b6b3d571a3629a2ae5faa39b8483942f0220424747e9e1cda6e034501171292fac690da050d5221cbfd318d880eb9701405541210275c802fa50d9a1f2b89c7e43b74c77e8826209b8aa79ed144c6768b9a6f262a1ffffffff457a0a555e6fbdaf5e5f2c241847026e1cf38ee3f523ebcd0dc27683fdcd4c55000000006a47304402200be359814746c94556d80fae90f02cc9bcff50a60a1a87dadce7579eb986d783022028f7d7a39561fbacde025a91b41b6279d4ffda991d9ef69804bde990f663c1df41210275c802fa50d9a1f2b89c7e43b74c77e8826209b8aa79ed144c6768b9a6f262a1ffffffff0300ca9a3b0000000017a914cf53278b47afd12ffd864a00090b6df471d22a16870000000000000000166a14bddadc147d635060f518c7d59e481090dc066f8bdb1ae329010000001976a914d4466bdfcc471b6207a108289b847d562044539288acd8c89169";
const INPUT_INDEX: usize = 0;
const INPUT_AMOUNT_SATS: u64 = 998_119_699;
const PREVOUT_SCRIPT_CODE_HEX: &str = "76a914d4466bdfcc471b6207a108289b847d562044539288ac";
const SIGHASH_U32: u32 = 0x41;
const DER_SIGNATURE_HEX: &str =
"304402204715bf639b322d08cfabb2b1b7af1ba8b6b3d571a3629a2ae5faa39b8483942f0220424747e9e1cda6e034501171292fac690da050d5221cbfd318d880eb97014055";
const PUBKEY_HEX: &str = "0275c802fa50d9a1f2b89c7e43b74c77e8826209b8aa79ed144c6768b9a6f262a1";
const EXPECTED_SIGHASH_HEX: &str = "201a2654d0ed04643080bbdda4acdf40a3d31a6a2e0c486270476fd3c6409187";

let tx: Transaction = SPEND_TX_HEX.into();
let mut signer: TransactionInputSigner = tx.into();
signer.inputs[INPUT_INDEX].amount = INPUT_AMOUNT_SATS;

let script_code: Script = PREVOUT_SCRIPT_CODE_HEX.into();
let sighash = signer.signature_hash(
INPUT_INDEX,
INPUT_AMOUNT_SATS,
&script_code,
SignatureVersion::ForkIdRxd,
SIGHASH_U32,
);

let signature: Signature = DER_SIGNATURE_HEX.into();
let pubkey_bytes: Bytes = PUBKEY_HEX.into();
let pubkey = Public::from_slice(&pubkey_bytes).expect("valid compressed pubkey");

assert!(pubkey
.verify(&sighash, &signature)
.expect("signature verification should not error"));

let expected_sighash: H256 = EXPECTED_SIGHASH_HEX.into();
assert_eq!(
sighash, expected_sighash,
"RXD sighash mismatch: computed {}, expected {}",
sighash, expected_sighash
);
}

#[test]
fn test_sorted_push_refs_rxd_extracts_and_sorts_push_refs() {
const OP_PUSHINPUTREF: u8 = 0xd0;
const OP_REQUIREINPUTREF: u8 = 0xd1;
const OP_PUSHINPUTREFSINGLETON: u8 = 0xd8;

let ref_a = [0x11u8; 36];
let ref_b = [0x22u8; 36];
let ref_ignored = [0x33u8; 36];

let mut script_pubkey = Vec::new();
script_pubkey.extend_from_slice(&[0x51]);
script_pubkey.push(OP_PUSHINPUTREF);
script_pubkey.extend_from_slice(&ref_b);
script_pubkey.push(OP_REQUIREINPUTREF);
script_pubkey.extend_from_slice(&ref_ignored);
script_pubkey.push(OP_PUSHINPUTREFSINGLETON);
script_pubkey.extend_from_slice(&ref_a);
script_pubkey.push(OP_PUSHINPUTREF);
script_pubkey.extend_from_slice(&ref_a);

let refs = sorted_push_refs_rxd(&script_pubkey);

assert_eq!(refs, vec![ref_a, ref_b]);
}

#[test]
Expand Down
Loading