uahf: forkid replay protection

This commit is contained in:
Svyatoslav Nikolsky 2017-08-08 17:49:26 +03:00
parent d6b9445344
commit 5326a90243
6 changed files with 138 additions and 71 deletions

View File

@ -65,6 +65,9 @@ pub struct VerificationFlags {
/// Making v1-v16 witness program non-standard
pub verify_discourage_upgradable_witness_program: bool,
/// Only verify transactions with SIGHASH_FORKID signatures.
pub enforce_sighash_fork_id: bool,
}
impl VerificationFlags {
@ -87,5 +90,10 @@ impl VerificationFlags {
self.verify_dersig = value;
self
}
pub fn enforce_sighash_fork_id(mut self, value: bool) -> Self {
self.enforce_sighash_fork_id = value;
self
}
}

View File

@ -230,7 +230,8 @@ pub fn verify_script(
script_sig: &Script,
script_pubkey: &Script,
flags: &VerificationFlags,
checker: &SignatureChecker
checker: &SignatureChecker,
version: SignatureVersion,
) -> Result<(), Error> {
if flags.verify_sigpushonly && !script_sig.is_push_only() {
return Err(Error::SignaturePushOnly);
@ -239,13 +240,13 @@ pub fn verify_script(
let mut stack = Stack::new();
let mut stack_copy = Stack::new();
try!(eval_script(&mut stack, script_sig, flags, checker, SignatureVersion::Base));
try!(eval_script(&mut stack, script_sig, flags, checker, version));
if flags.verify_p2sh {
stack_copy = stack.clone();
}
let res = try!(eval_script(&mut stack, script_pubkey, flags, checker, SignatureVersion::Base));
let res = try!(eval_script(&mut stack, script_pubkey, flags, checker, version));
if !res {
return Err(Error::EvalFalse);
}
@ -265,7 +266,7 @@ pub fn verify_script(
let pubkey2: Script = try!(stack.pop()).into();
let res = try!(eval_script(&mut stack, &pubkey2, flags, checker, SignatureVersion::Base));
let res = try!(eval_script(&mut stack, &pubkey2, flags, checker, version));
if !res {
return Err(Error::EvalFalse);
}
@ -1842,12 +1843,13 @@ mod tests {
let checker = TransactionSignatureChecker {
signer: signer,
input_index: 0,
input_amount: 0,
};
let input: Script = "47304402202cb265bf10707bf49346c3515dd3d16fc454618c58ec0a0ff448a676c54ff71302206c6624d762a1fcef4618284ead8f08678ac05b13c84235f1654e6ad168233e8201410414e301b2328f17442c0b8310d787bf3d8a404cfbd0704f135b6ad4b2d3ee751310f981926e53a6e8c39bd7d3fefd576c543cce493cbac06388f2651d1aacbfcd".into();
let output: Script = "76a914df3bd30160e6c6145baaf2c88a8844c13a00d1d588ac".into();
let flags = VerificationFlags::default()
.verify_p2sh(true);
assert_eq!(verify_script(&input, &output, &flags, &checker), Ok(()));
assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(()));
}
// https://blockchain.info/rawtx/02b082113e35d5386285094c2829e7e2963fa0b5369fb7f4b79c4c90877dcd3d
@ -1858,12 +1860,13 @@ mod tests {
let checker = TransactionSignatureChecker {
signer: signer,
input_index: 0,
input_amount: 0,
};
let input: Script = "00483045022100deeb1f13b5927b5e32d877f3c42a4b028e2e0ce5010fdb4e7f7b5e2921c1dcd2022068631cb285e8c1be9f061d2968a18c3163b780656f30a049effee640e80d9bff01483045022100ee80e164622c64507d243bd949217d666d8b16486e153ac6a1f8e04c351b71a502203691bef46236ca2b4f5e60a82a853a33d6712d6a1e7bf9a65e575aeb7328db8c014cc9524104a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd41046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187410411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e8353ae".into();
let output: Script = "a9141a8b0026343166625c7475f01e48b5ede8c0252e87".into();
let flags = VerificationFlags::default()
.verify_p2sh(true);
assert_eq!(verify_script(&input, &output, &flags, &checker), Ok(()));
assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(()));
}
// https://blockchain.info/en/tx/12b5633bad1f9c167d523ad1aa1947b2732a865bf5414eab2f9e5ae5d5c191ba?show_adv=true
@ -1874,12 +1877,13 @@ mod tests {
let checker = TransactionSignatureChecker {
signer: signer,
input_index: 0,
input_amount: 0,
};
let input: Script = "483045022052ffc1929a2d8bd365c6a2a4e3421711b4b1e1b8781698ca9075807b4227abcb0221009984107ddb9e3813782b095d0d84361ed4c76e5edaf6561d252ae162c2341cfb01".into();
let output: Script = "410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac".into();
let flags = VerificationFlags::default()
.verify_p2sh(true);
assert_eq!(verify_script(&input, &output, &flags, &checker), Ok(()));
assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(()));
}
// https://blockchain.info/rawtx/fb0a1d8d34fa5537e461ac384bac761125e1bfa7fec286fa72511240fa66864d
@ -1890,12 +1894,13 @@ mod tests {
let checker = TransactionSignatureChecker {
signer: signer,
input_index: 0,
input_amount: 0,
};
let input: Script = "4b3048022200002b83d59c1d23c08efd82ee0662fec23309c3adbcbd1f0b8695378db4b14e736602220000334a96676e58b1bb01784cb7c556dd8ce1c220171904da22e18fe1e7d1510db5014104d0fe07ff74c9ef5b00fed1104fad43ecf72dbab9e60733e4f56eacf24b20cf3b8cd945bcabcc73ba0158bf9ce769d43e94bd58c5c7e331a188922b3fe9ca1f5a".into();
let output: Script = "76a9147a2a3b481ca80c4ba7939c54d9278e50189d94f988ac".into();
let flags = VerificationFlags::default()
.verify_p2sh(true);
assert_eq!(verify_script(&input, &output, &flags, &checker), Ok(()));
assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(()));
}
// https://blockchain.info/rawtx/eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb
@ -1906,18 +1911,19 @@ mod tests {
let checker = TransactionSignatureChecker {
signer: signer,
input_index: 1,
input_amount: 0,
};
let input: Script = "004730440220276d6dad3defa37b5f81add3992d510d2f44a317fd85e04f93a1e2daea64660202200f862a0da684249322ceb8ed842fb8c859c0cb94c81e1c5308b4868157a428ee01ab51210232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a51ae".into();
let output: Script = "142a9bc5447d664c1d0141392a842d23dba45c4f13b175".into();
let flags = VerificationFlags::default()
.verify_p2sh(true);
assert_eq!(verify_script(&input, &output, &flags, &checker), Ok(()));
assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(()));
let flags = VerificationFlags::default()
.verify_p2sh(true)
.verify_locktime(true);
assert_eq!(verify_script(&input, &output, &flags, &checker), Err(Error::NumberOverflow));
assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Err(Error::NumberOverflow));
}
// https://blockchain.info/rawtx/54fabd73f1d20c980a0686bf0035078e07f69c58437e4d586fb29aa0bee9814f
@ -1928,11 +1934,12 @@ mod tests {
let checker = TransactionSignatureChecker {
signer: signer,
input_index: 0,
input_amount: 0,
};
let input: Script = "483045022100d92e4b61452d91a473a43cde4b469a472467c0ba0cbd5ebba0834e4f4762810402204802b76b7783db57ac1f61d2992799810e173e91055938750815b6d8a675902e014f".into();
let output: Script = "76009f69905160a56b210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71ad6c".into();
let flags = VerificationFlags::default();
assert_eq!(verify_script(&input, &output, &flags, &checker), Ok(()));
assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(()));
}
#[test]
@ -1978,13 +1985,14 @@ mod tests {
let checker = TransactionSignatureChecker {
signer: signer,
input_index: 1,
input_amount: 0,
};
let input: Script = "00483045022015BD0139BCCCF990A6AF6EC5C1C52ED8222E03A0D51C334DF139968525D2FCD20221009F9EFE325476EB64C3958E4713E9EEFE49BF1D820ED58D2112721B134E2A1A53034930460221008431BDFA72BC67F9D41FE72E94C88FB8F359FFA30B33C72C121C5A877D922E1002210089EF5FC22DD8BFC6BF9FFDB01A9862D27687D424D1FEFBAB9E9C7176844A187A014C9052483045022015BD0139BCCCF990A6AF6EC5C1C52ED8222E03A0D51C334DF139968525D2FCD20221009F9EFE325476EB64C3958E4713E9EEFE49BF1D820ED58D2112721B134E2A1A5303210378D430274F8C5EC1321338151E9F27F4C676A008BDF8638D07C0B6BE9AB35C71210378D430274F8C5EC1321338151E9F27F4C676A008BDF8638D07C0B6BE9AB35C7153AE".into();
let output: Script = "A914D8DACDADB7462AE15CD906F1878706D0DA8660E687".into();
let flags = VerificationFlags::default()
.verify_p2sh(true);
assert_eq!(verify_script(&input, &output, &flags, &checker), Ok(()));
assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(()));
}
}

View File

@ -25,7 +25,7 @@ pub use self::interpreter::{eval_script, verify_script};
pub use self::opcode::Opcode;
pub use self::num::Num;
pub use self::script::{Script, ScriptType, ScriptAddress};
pub use self::sign::{TransactionInputSigner, UnsignedTransactionInput};
pub use self::sign::{TransactionInputSigner, UnsignedTransactionInput, SignatureVersion};
pub use self::stack::Stack;
pub use self::verify::{SignatureChecker, NoopSignatureChecker, TransactionSignatureChecker};

View File

@ -1,17 +1,17 @@
//! Transaction signer
use bytes::Bytes;
use keys::KeyPair;
use crypto::dhash256;
use hash::H256;
use ser::Stream;
use chain::{Transaction, TransactionOutput, OutPoint, TransactionInput};
use {Script, Builder};
use Script;
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum SignatureVersion {
Base,
WitnessV0,
ForkId,
}
#[derive(Debug, PartialEq, Clone, Copy)]
@ -34,67 +34,72 @@ impl From<SighashBase> for u32 {
pub struct Sighash {
pub base: SighashBase,
pub anyone_can_pay: bool,
pub fork_id: bool,
}
impl From<Sighash> for u32 {
fn from(s: Sighash) -> Self {
let base = s.base as u32;
if s.anyone_can_pay {
let base = if s.anyone_can_pay {
base | 0x80
} else {
base
};
if s.fork_id {
base | 0x40
} else {
base
}
}
}
impl From<u32> for Sighash {
fn from(u: u32) -> Self {
// use 0x9f istead of 0x1f to catch 0x80
match u & 0x9f {
2 => Sighash::new(SighashBase::None, false),
3 => Sighash::new(SighashBase::Single, false),
0x81 => Sighash::new(SighashBase::All, true),
0x82 => Sighash::new(SighashBase::None, true),
0x83 => Sighash::new(SighashBase::Single, true),
x if x & 0x80 == 0x80 => Sighash::new(SighashBase::All, true),
// 0 is handled like all...
1 | _ => Sighash::new(SighashBase::All, false),
}
Sighash::new(match u & 0x1f {
2 => SighashBase::None,
3 => SighashBase::Single,
1 | _ => SighashBase::All,
},
u & 0x80 == 0x80,
u & 0x40 == 0x40)
}
}
impl Sighash {
pub fn new(base: SighashBase, anyone_can_pay: bool) -> Self {
pub fn new(base: SighashBase, anyone_can_pay: bool, fork_id: bool) -> Self {
Sighash {
base: base,
anyone_can_pay: anyone_can_pay,
fork_id: fork_id,
}
}
pub fn is_defined(u: u32) -> bool {
// use 0x9f istead of 0x1f to catch 0x80
match u & 0x9f {
// use 0xdf istead of 0x1f to catch 0x40 | 0x80
match u & 0xdf {
1 | 2 | 3 |
0x81 | 0x82 | 0x83 => true,
0x81 | 0x82 | 0x83 |
0x41 | 0x42 | 0x43 |
0xc1 | 0xc2 | 0xc3 => true,
x if x & 0x80 == 0x80 => true,
x if x & 0x40 == 0x40 => true,
_ => false,
}
}
pub fn from_u32(u: u32) -> Option<Self> {
// use 0x9f istead of 0x1f to catch 0x80
let (base, anyone_can_pay) = match u & 0x9f {
1 => (SighashBase::All, false),
2 => (SighashBase::None, false),
3 => (SighashBase::Single, false),
0x81 => (SighashBase::All, true),
0x82 => (SighashBase::None, true),
0x83 => (SighashBase::Single, true),
x if x & 0x80 == 0x80 => (SighashBase::All, true),
let anyone_can_pay = (u & 0x80) == 0x80;
let fork_id = (u & 0x40) == 0x40;
let base = match u & 0x1f {
2 => SighashBase::None,
3 => SighashBase::Single,
1 => SighashBase::All,
_ if anyone_can_pay || fork_id => SighashBase::All,
_ => return None,
};
Some(Sighash::new(base, anyone_can_pay))
Some(Sighash::new(base, anyone_can_pay, fork_id))
}
}
@ -133,7 +138,7 @@ impl From<Transaction> for TransactionInputSigner {
}
impl TransactionInputSigner {
pub fn signature_hash(&self, input_index: usize, script_pubkey: &Script, sighashtype: u32) -> H256 {
pub fn signature_hash(&self, input_index: usize, input_amount: u64, script_pubkey: &Script, sigversion: SignatureVersion, sighashtype: u32) -> H256 {
let sighash = Sighash::from(sighashtype);
if input_index >= self.inputs.len() {
return 1u8.into();
@ -143,6 +148,14 @@ impl TransactionInputSigner {
return 1u8.into();
}
match sighash.fork_id {
true if sigversion == SignatureVersion::ForkId => self.signature_hash_fork_id(input_index, input_amount, script_pubkey, sighashtype, sighash),
false if sigversion == SignatureVersion::Base => self.signature_hash_original(input_index, script_pubkey, sighashtype, sighash),
_ => 1u8.into(),
}
}
pub fn signature_hash_original(&self, input_index: usize, script_pubkey: &Script, sighashtype: u32, sighash: Sighash) -> H256 {
let script_pubkey = script_pubkey.without_separators();
let inputs = if sighash.anyone_can_pay {
@ -198,30 +211,58 @@ impl TransactionInputSigner {
dhash256(&out)
}
/// input_index - index of input to sign
/// script_pubkey - script_pubkey of input's previous_output pubkey
pub fn signed_input(
&self,
keypair: &KeyPair,
input_index: usize,
script_pubkey: &Script,
sighash: u32,
) -> TransactionInput {
let hash = self.signature_hash(input_index, script_pubkey, sighash);
fn signature_hash_fork_id(&self, input_index: usize, input_amount: u64, script_pubkey: &Script, sighashtype: u32, sighash: Sighash) -> H256 {
let hash_prevouts = match sighash.anyone_can_pay {
false => {
let mut stream = Stream::default();
for input in &self.inputs {
stream.append(&input.previous_output);
}
dhash256(&stream.out())
},
true => 0u8.into(),
};
let mut signature: Vec<u8> = keypair.private().sign(&hash).unwrap().into();
signature.push(sighash as u8);
let script_sig = Builder::default()
.push_data(&signature)
.push_data(keypair.public())
.into_script();
let hash_sequence = match sighash.base {
SighashBase::All if !sighash.anyone_can_pay => {
let mut stream = Stream::default();
for input in &self.inputs {
stream.append(&input.sequence);
}
dhash256(&stream.out())
},
_ => 0u8.into(),
};
let unsigned_input = &self.inputs[input_index];
TransactionInput {
previous_output: unsigned_input.previous_output.clone(),
sequence: unsigned_input.sequence,
script_sig: script_sig.to_bytes(),
}
let hash_outputs = match sighash.base {
SighashBase::All => {
let mut stream = Stream::default();
for output in &self.outputs {
stream.append(output);
}
dhash256(&stream.out())
},
SighashBase::Single if input_index < self.outputs.len() => {
let mut stream = Stream::default();
stream.append(&self.outputs[input_index]);
dhash256(&stream.out())
},
_ => 0u8.into(),
};
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_slice(&**script_pubkey);
stream.append(&input_amount);
stream.append(&self.inputs[input_index].sequence);
stream.append(&hash_outputs);
stream.append(&self.lock_time);
stream.append(&sighashtype); // this also includes 24-bit fork id. which is 0 for BitcoinCash
let out = stream.out();
dhash256(&out)
}
}
@ -232,7 +273,7 @@ mod tests {
use keys::{KeyPair, Private, Address};
use chain::{OutPoint, TransactionOutput, Transaction};
use script::Script;
use super::{UnsignedTransactionInput, TransactionInputSigner, SighashBase};
use super::{UnsignedTransactionInput, TransactionInputSigner, SighashBase, SignatureVersion};
// http://www.righto.com/2014/02/bitcoins-hard-way-using-raw-bitcoin.html
// https://blockchain.info/rawtx/81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48
@ -274,7 +315,7 @@ mod tests {
outputs: vec![output],
};
let hash = input_signer.signature_hash(0, &previous_output, SighashBase::All.into());
let hash = input_signer.signature_hash(0, 0, &previous_output, SignatureVersion::Base, SighashBase::All.into());
assert_eq!(hash, expected_signature_hash);
}
@ -290,7 +331,7 @@ mod tests {
let script: Script = script.into();
let expected = H256::from_reversed_str(result);
let hash = signer.signature_hash(input_index, &script, hash_type as u32);
let hash = signer.signature_hash_original(input_index, &script, hash_type as u32, (hash_type as u32).into());
assert_eq!(expected, hash);
}

View File

@ -41,6 +41,7 @@ impl SignatureChecker for NoopSignatureChecker {
pub struct TransactionSignatureChecker {
pub signer: TransactionInputSigner,
pub input_index: usize,
pub input_amount: u64,
}
impl SignatureChecker for TransactionSignatureChecker {
@ -50,9 +51,9 @@ impl SignatureChecker for TransactionSignatureChecker {
public: &Public,
script_code: &Script,
sighashtype: u32,
_version: SignatureVersion
version: SignatureVersion
) -> bool {
let hash = self.signer.signature_hash(self.input_index, script_code, sighashtype);
let hash = self.signer.signature_hash(self.input_index, self.input_amount, script_code, version, sighashtype);
public.verify(&hash, signature).unwrap_or(false)
}

View File

@ -2,7 +2,7 @@ use primitives::hash::H256;
use primitives::bytes::Bytes;
use db::{TransactionMetaProvider, TransactionOutputProvider, BlockHeaderProvider};
use network::{ConsensusParams, ConsensusFork};
use script::{Script, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner};
use script::{Script, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner, SignatureVersion};
use duplex_store::DuplexTransactionOutputProvider;
use deployments::Deployments;
use script::Builder;
@ -284,6 +284,7 @@ pub struct TransactionEval<'a> {
verify_locktime: bool,
verify_checksequence: bool,
verify_dersig: bool,
signature_version: SignatureVersion,
}
impl<'a> TransactionEval<'a> {
@ -299,6 +300,10 @@ impl<'a> TransactionEval<'a> {
let verify_p2sh = time >= params.bip16_time;
let verify_locktime = height >= params.bip65_height;
let verify_dersig = height >= params.bip66_height;
let signature_version = match params.fork {
ConsensusFork::BitcoinCash(fork_height) if height >= fork_height => SignatureVersion::ForkId,
_ => SignatureVersion::Base,
};
let verify_checksequence = deployments.csv(height, headers, params);
@ -309,6 +314,7 @@ impl<'a> TransactionEval<'a> {
verify_locktime: verify_locktime,
verify_checksequence: verify_checksequence,
verify_dersig: verify_dersig,
signature_version: signature_version,
}
}
@ -322,6 +328,7 @@ impl<'a> TransactionEval<'a> {
let mut checker = TransactionSignatureChecker {
signer: signer,
input_index: 0,
input_amount: 0,
};
for (index, input) in self.transaction.raw.inputs.iter().enumerate() {
@ -329,6 +336,7 @@ impl<'a> TransactionEval<'a> {
.ok_or_else(|| TransactionError::UnknownReference(input.previous_output.hash.clone()))?;
checker.input_index = index;
checker.input_amount = output.value;
let input: Script = input.script_sig.clone().into();
let output: Script = output.script_pubkey.into();
@ -339,7 +347,8 @@ impl<'a> TransactionEval<'a> {
.verify_checksequence(self.verify_checksequence)
.verify_dersig(self.verify_dersig);
try!(verify_script(&input, &output, &flags, &checker).map_err(|_| TransactionError::Signature(index)));
try!(verify_script(&input, &output, &flags, &checker, self.signature_version)
.map_err(|_| TransactionError::Signature(index)));
}
Ok(())