Merge pull request #525 from paritytech/bch_nov2018_checkdatasig

BCH Nov2018 HF: OP_CHECKDATASIG + OP_CHECKDATASIGVERIFY
This commit is contained in:
Svyatoslav Nikolsky 2018-11-12 09:41:25 +03:00 committed by GitHub
commit 4376d5d300
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 344 additions and 43 deletions

View File

@ -3,7 +3,7 @@ use primitives::hash::H256;
use primitives::compact::Compact; use primitives::compact::Compact;
use chain::{OutPoint, TransactionOutput, IndexedTransaction}; use chain::{OutPoint, TransactionOutput, IndexedTransaction};
use storage::{SharedStore, TransactionOutputProvider}; use storage::{SharedStore, TransactionOutputProvider};
use network::{ConsensusParams, TransactionOrdering}; use network::{ConsensusParams, ConsensusFork, TransactionOrdering};
use memory_pool::{MemoryPool, OrderingStrategy, Entry}; use memory_pool::{MemoryPool, OrderingStrategy, Entry};
use verification::{work_required, block_reward_satoshi, transaction_sigops, median_timestamp_inclusive}; use verification::{work_required, block_reward_satoshi, transaction_sigops, median_timestamp_inclusive};
@ -132,6 +132,8 @@ struct FittingTransactionsIterator<'a, T> {
block_height: u32, block_height: u32,
/// New block time /// New block time
block_time: u32, block_time: u32,
/// Are OP_CHECKDATASIG && OP_CHECKDATASIGVERIFY enabled for this block.
checkdatasig_active: bool,
/// Size policy decides if transactions size fits the block /// Size policy decides if transactions size fits the block
block_size: SizePolicy, block_size: SizePolicy,
/// Sigops policy decides if transactions sigops fits the block /// Sigops policy decides if transactions sigops fits the block
@ -145,12 +147,21 @@ struct FittingTransactionsIterator<'a, T> {
} }
impl<'a, T> FittingTransactionsIterator<'a, T> where T: Iterator<Item = &'a Entry> { impl<'a, T> FittingTransactionsIterator<'a, T> where T: Iterator<Item = &'a Entry> {
fn new(store: &'a TransactionOutputProvider, iter: T, max_block_size: u32, max_block_sigops: u32, block_height: u32, block_time: u32) -> Self { fn new(
store: &'a TransactionOutputProvider,
iter: T,
max_block_size: u32,
max_block_sigops: u32,
block_height: u32,
block_time: u32,
checkdatasig_active: bool,
) -> Self {
FittingTransactionsIterator { FittingTransactionsIterator {
store: store, store: store,
iter: iter, iter: iter,
block_height: block_height, block_height: block_height,
block_time: block_time, block_time: block_time,
checkdatasig_active,
// reserve some space for header and transations len field // reserve some space for header and transations len field
block_size: SizePolicy::new(BLOCK_HEADER_SIZE + 4, max_block_size, 1_000, 50), block_size: SizePolicy::new(BLOCK_HEADER_SIZE + 4, max_block_size, 1_000, 50),
sigops: SizePolicy::new(0, max_block_sigops, 8, 50), sigops: SizePolicy::new(0, max_block_sigops, 8, 50),
@ -192,7 +203,7 @@ impl<'a, T> Iterator for FittingTransactionsIterator<'a, T> where T: Iterator<It
let transaction_size = entry.size as u32; let transaction_size = entry.size as u32;
let bip16_active = true; let bip16_active = true;
let sigops_count = transaction_sigops(&entry.transaction, self, bip16_active) as u32; let sigops_count = transaction_sigops(&entry.transaction, self, bip16_active, self.checkdatasig_active) as u32;
let size_step = self.block_size.decide(transaction_size); let size_step = self.block_size.decide(transaction_size);
let sigops_step = self.sigops.decide(sigops_count); let sigops_step = self.sigops.decide(sigops_count);
@ -235,7 +246,7 @@ impl<'a, T> Iterator for FittingTransactionsIterator<'a, T> where T: Iterator<It
} }
impl BlockAssembler { impl BlockAssembler {
pub fn create_new_block(&self, store: &SharedStore, mempool: &MemoryPool, time: u32, consensus: &ConsensusParams) -> BlockTemplate { pub fn create_new_block(&self, store: &SharedStore, mempool: &MemoryPool, time: u32, median_timestamp: u32, consensus: &ConsensusParams) -> BlockTemplate {
// get best block // get best block
// take it's hash && height // take it's hash && height
let best_block = store.best_block(); let best_block = store.best_block();
@ -244,6 +255,11 @@ impl BlockAssembler {
let bits = work_required(previous_header_hash.clone(), time, height, store.as_block_header_provider(), consensus); let bits = work_required(previous_header_hash.clone(), time, height, store.as_block_header_provider(), consensus);
let version = BLOCK_VERSION; let version = BLOCK_VERSION;
let checkdatasig_active = match consensus.fork {
ConsensusFork::BitcoinCash(ref fork) => median_timestamp >= fork.magnetic_anomaly_time,
_ => false
};
let mut coinbase_value = block_reward_satoshi(height); let mut coinbase_value = block_reward_satoshi(height);
let mut transactions = Vec::new(); let mut transactions = Vec::new();
@ -254,7 +270,8 @@ impl BlockAssembler {
self.max_block_size, self.max_block_size,
self.max_block_sigops, self.max_block_sigops,
height, height,
time); time,
checkdatasig_active);
for entry in tx_iter { for entry in tx_iter {
// miner_fee is i64, but we can safely cast it to u64 // miner_fee is i64, but we can safely cast it to u64
// memory pool should restrict miner fee to be positive // memory pool should restrict miner fee to be positive
@ -361,7 +378,7 @@ mod tests {
(BlockAssembler { (BlockAssembler {
max_block_size: 0xffffffff, max_block_size: 0xffffffff,
max_block_sigops: 0xffffffff, max_block_sigops: 0xffffffff,
}.create_new_block(&storage, &pool, 0, &consensus), hash0, hash1) }.create_new_block(&storage, &pool, 0, 0, &consensus), hash0, hash1)
} }
// when topological consensus is used // when topological consensus is used

View File

@ -19,11 +19,13 @@ pub enum Error {
NumberNotMinimallyEncoded, NumberNotMinimallyEncoded,
SigCount, SigCount,
PubkeyCount, PubkeyCount,
InvalidOperandSize,
// Failed verify operations // Failed verify operations
Verify, Verify,
EqualVerify, EqualVerify,
CheckSigVerify, CheckSigVerify,
CheckDataSigVerify,
NumEqualVerify, NumEqualVerify,
// Logical/Format/Canonical errors. // Logical/Format/Canonical errors.
@ -33,7 +35,6 @@ pub enum Error {
InvalidAltstackOperation, InvalidAltstackOperation,
UnbalancedConditional, UnbalancedConditional,
InvalidSplitRange, InvalidSplitRange,
InvalidBitwiseOperation,
DivisionByZero, DivisionByZero,
ImpossibleEncoding, ImpossibleEncoding,
@ -78,6 +79,7 @@ impl fmt::Display for Error {
Error::Verify => "Failed verify operation".fmt(f), Error::Verify => "Failed verify operation".fmt(f),
Error::EqualVerify => "Failed equal verify operation".fmt(f), Error::EqualVerify => "Failed equal verify operation".fmt(f),
Error::CheckSigVerify => "Failed signature check".fmt(f), Error::CheckSigVerify => "Failed signature check".fmt(f),
Error::CheckDataSigVerify => "Failed data signature check".fmt(f),
Error::NumEqualVerify => "Failed num equal verify operation".fmt(f), Error::NumEqualVerify => "Failed num equal verify operation".fmt(f),
Error::SigCount => "Maximum number of signature exceeded".fmt(f), Error::SigCount => "Maximum number of signature exceeded".fmt(f),
Error::PubkeyCount => "Maximum number of pubkeys per multisig exceeded".fmt(f), Error::PubkeyCount => "Maximum number of pubkeys per multisig exceeded".fmt(f),
@ -97,7 +99,7 @@ impl fmt::Display for Error {
Error::InvalidAltstackOperation => "Invalid altstack operation".fmt(f), Error::InvalidAltstackOperation => "Invalid altstack operation".fmt(f),
Error::UnbalancedConditional => "Unbalanced conditional".fmt(f), Error::UnbalancedConditional => "Unbalanced conditional".fmt(f),
Error::InvalidSplitRange => "Invalid OP_SPLIT range".fmt(f), Error::InvalidSplitRange => "Invalid OP_SPLIT range".fmt(f),
Error::InvalidBitwiseOperation => "Invalid bitwise operation (check length of inputs)".fmt(f), Error::InvalidOperandSize => "Invalid operand size".fmt(f),
Error::DivisionByZero => "Invalid division operation".fmt(f), Error::DivisionByZero => "Invalid division operation".fmt(f),
Error::ImpossibleEncoding => "The requested encoding is impossible to satisfy".fmt(f), Error::ImpossibleEncoding => "The requested encoding is impossible to satisfy".fmt(f),

View File

@ -98,6 +98,9 @@ pub struct VerificationFlags {
/// ///
/// This opcode replaces OP_LEFT => enabling both OP_NUM2BIN && OP_LEFT would be an error /// This opcode replaces OP_LEFT => enabling both OP_NUM2BIN && OP_LEFT would be an error
pub verify_num2bin: bool, pub verify_num2bin: bool,
/// Support OP_CHECKDATASIG and OP_CHECKDATASIGVERIFY opcodes.
pub verify_checkdatasig: bool,
} }
impl VerificationFlags { impl VerificationFlags {
@ -185,4 +188,9 @@ impl VerificationFlags {
self.verify_num2bin = value; self.verify_num2bin = value;
self self
} }
pub fn verify_checkdatasig(mut self, value: bool) -> Self {
self.verify_checkdatasig = value;
self
}
} }

View File

@ -1,6 +1,6 @@
use std::{cmp, mem}; use std::{cmp, mem};
use bytes::Bytes; use bytes::Bytes;
use keys::{Signature, Public}; use keys::{Message, Signature, Public};
use chain::constants::SEQUENCE_LOCKTIME_DISABLE_FLAG; use chain::constants::SEQUENCE_LOCKTIME_DISABLE_FLAG;
use crypto::{sha1, sha256, dhash160, dhash256, ripemd160}; use crypto::{sha1, sha256, dhash160, dhash256, ripemd160};
use sign::{SignatureVersion, Sighash}; use sign::{SignatureVersion, Sighash};
@ -32,6 +32,25 @@ fn check_signature(
checker.check_signature(&signature, &public, script_code, hash_type, version) checker.check_signature(&signature, &public, script_code, hash_type, version)
} }
/// Helper function.
fn verify_signature(
checker: &SignatureChecker,
signature: Vec<u8>,
public: Vec<u8>,
message: Message,
) -> bool {
let public = match Public::from_slice(&public) {
Ok(public) => public,
_ => return false,
};
if signature.is_empty() {
return false;
}
checker.verify_signature(&signature.into(), &public, &message.into())
}
fn is_public_key(v: &[u8]) -> bool { fn is_public_key(v: &[u8]) -> bool {
match v.len() { match v.len() {
33 if v[0] == 2 || v[0] == 3 => true, 33 if v[0] == 2 || v[0] == 3 => true,
@ -603,7 +622,7 @@ pub fn eval_script(
let mask_len = mask.len(); let mask_len = mask.len();
let value_to_update = stack.last_mut()?; let value_to_update = stack.last_mut()?;
if mask_len != value_to_update.len() { if mask_len != value_to_update.len() {
return Err(Error::InvalidBitwiseOperation); return Err(Error::InvalidOperandSize);
} }
for (byte_to_update, byte_mask) in (*value_to_update).iter_mut().zip(mask.iter()) { for (byte_to_update, byte_mask) in (*value_to_update).iter_mut().zip(mask.iter()) {
*byte_to_update = *byte_to_update & byte_mask; *byte_to_update = *byte_to_update & byte_mask;
@ -614,7 +633,7 @@ pub fn eval_script(
let mask_len = mask.len(); let mask_len = mask.len();
let value_to_update = stack.last_mut()?; let value_to_update = stack.last_mut()?;
if mask_len != value_to_update.len() { if mask_len != value_to_update.len() {
return Err(Error::InvalidBitwiseOperation); return Err(Error::InvalidOperandSize);
} }
for (byte_to_update, byte_mask) in (*value_to_update).iter_mut().zip(mask.iter()) { for (byte_to_update, byte_mask) in (*value_to_update).iter_mut().zip(mask.iter()) {
*byte_to_update = *byte_to_update | byte_mask; *byte_to_update = *byte_to_update | byte_mask;
@ -625,7 +644,7 @@ pub fn eval_script(
let mask_len = mask.len(); let mask_len = mask.len();
let value_to_update = stack.last_mut()?; let value_to_update = stack.last_mut()?;
if mask_len != value_to_update.len() { if mask_len != value_to_update.len() {
return Err(Error::InvalidBitwiseOperation); return Err(Error::InvalidOperandSize);
} }
for (byte_to_update, byte_mask) in (*value_to_update).iter_mut().zip(mask.iter()) { for (byte_to_update, byte_mask) in (*value_to_update).iter_mut().zip(mask.iter()) {
*byte_to_update = *byte_to_update ^ byte_mask; *byte_to_update = *byte_to_update ^ byte_mask;
@ -1116,6 +1135,34 @@ pub fn eval_script(
Opcode::OP_VERNOTIF => { Opcode::OP_VERNOTIF => {
return Err(Error::DisabledOpcode(opcode)); return Err(Error::DisabledOpcode(opcode));
}, },
Opcode::OP_CHECKDATASIG | Opcode::OP_CHECKDATASIGVERIFY if flags.verify_checkdatasig => {
let pubkey = stack.pop()?;
let message = stack.pop()?;
let signature = stack.pop()?;
check_signature_encoding(&signature, flags, version)?;
check_pubkey_encoding(&pubkey, flags)?;
let signature: Vec<u8> = signature.into();
let message_hash = sha256(&message);
let success = verify_signature(checker, signature.into(), pubkey.into(), message_hash);
match opcode {
Opcode::OP_CHECKDATASIG => {
if success {
stack.push(vec![1].into());
} else {
stack.push(vec![0].into());
}
},
Opcode::OP_CHECKDATASIGVERIFY if !success => {
return Err(Error::CheckDataSigVerify);
},
_ => {},
}
},
Opcode::OP_CHECKDATASIG | Opcode::OP_CHECKDATASIGVERIFY => {
return Err(Error::DisabledOpcode(opcode));
},
} }
if stack.len() + altstack.len() > 1000 { if stack.len() + altstack.len() > 1000 {
@ -1139,6 +1186,8 @@ pub fn eval_script(
mod tests { mod tests {
use bytes::Bytes; use bytes::Bytes;
use chain::Transaction; use chain::Transaction;
use crypto::sha256;
use keys::{KeyPair, Private, Message, Network};
use sign::SignatureVersion; use sign::SignatureVersion;
use script::MAX_SCRIPT_ELEMENT_SIZE; use script::MAX_SCRIPT_ELEMENT_SIZE;
use { use {
@ -2245,7 +2294,6 @@ mod tests {
#[test] #[test]
fn test_script_with_forkid_signature() { fn test_script_with_forkid_signature() {
use keys::{KeyPair, Private, Network};
use sign::UnsignedTransactionInput; use sign::UnsignedTransactionInput;
use chain::{OutPoint, TransactionOutput}; use chain::{OutPoint, TransactionOutput};
@ -3423,7 +3471,7 @@ mod tests {
.push_data(&[0x22]) .push_data(&[0x22])
.push_opcode(Opcode::OP_AND) .push_opcode(Opcode::OP_AND)
.into_script(); .into_script();
let result = Err(Error::InvalidBitwiseOperation); let result = Err(Error::InvalidOperandSize);
basic_test_with_flags(&script, &VerificationFlags::default().verify_and(true), result, basic_test_with_flags(&script, &VerificationFlags::default().verify_and(true), result,
vec![].into()); vec![].into());
} }
@ -3459,7 +3507,7 @@ mod tests {
.push_data(&[0x22]) .push_data(&[0x22])
.push_opcode(Opcode::OP_OR) .push_opcode(Opcode::OP_OR)
.into_script(); .into_script();
let result = Err(Error::InvalidBitwiseOperation); let result = Err(Error::InvalidOperandSize);
basic_test_with_flags(&script, &VerificationFlags::default().verify_or(true), result, basic_test_with_flags(&script, &VerificationFlags::default().verify_or(true), result,
vec![].into()); vec![].into());
} }
@ -3495,7 +3543,7 @@ mod tests {
.push_data(&[0x22]) .push_data(&[0x22])
.push_opcode(Opcode::OP_XOR) .push_opcode(Opcode::OP_XOR)
.into_script(); .into_script();
let result = Err(Error::InvalidBitwiseOperation); let result = Err(Error::InvalidOperandSize);
basic_test_with_flags(&script, &VerificationFlags::default().verify_xor(true), result, basic_test_with_flags(&script, &VerificationFlags::default().verify_xor(true), result,
vec![].into()); vec![].into());
} }
@ -3843,4 +3891,160 @@ mod tests {
.verify_split(true); .verify_split(true);
basic_test_with_flags(&script, &flags, Ok(true), vec![vec![0x01].into()].into()); basic_test_with_flags(&script, &flags, Ok(true), vec![vec![0x01].into()].into());
} }
#[test]
fn checkdatasig_spec_tests() {
// official tests from:
// https://github.com/bitcoincashorg/bitcoincash.org/blob/0c6f91b0b713aae3bc6c9834b46e80e247ff5fab/spec/op_checkdatasig.md
let kp = KeyPair::from_private(Private { network: Network::Mainnet, secret: 1.into(), compressed: false, }).unwrap();
let pubkey = kp.public().clone();
let message = vec![42u8; 32];
let correct_signature = kp.private().sign(&Message::from(sha256(&message))).unwrap();
let correct_signature_for_other_message = kp.private().sign(&[43u8; 32].into()).unwrap();
let mut correct_signature = correct_signature.to_vec();
let mut correct_signature_for_other_message = correct_signature_for_other_message.to_vec();
correct_signature.push(0x81);
correct_signature_for_other_message.push(0x81);
let correct_flags = VerificationFlags::default()
.verify_checkdatasig(true)
.verify_dersig(true)
.verify_strictenc(true);
let incorrect_flags = VerificationFlags::default().verify_checkdatasig(false);
let correct_signature_script = Builder::default()
.push_data(&*correct_signature)
.push_data(&*message)
.push_data(&*pubkey)
.push_opcode(Opcode::OP_CHECKDATASIG)
.into_script();
// <sig> <msg> <pubKey> OP_CHECKDATASIG fails if 15 November 2018 protocol upgrade is not yet activated.
basic_test_with_flags(&correct_signature_script, &incorrect_flags, Err(Error::DisabledOpcode(Opcode::OP_CHECKDATASIG)), vec![].into());
// <sig> <msg> OP_CHECKDATASIG fails if there are fewer than 3 items on stack.
let too_few_args_sig_script = Builder::default()
.push_data(&[1u8; 32])
.push_data(&*message)
.push_opcode(Opcode::OP_CHECKDATASIG)
.into_script();
basic_test_with_flags(&too_few_args_sig_script, &correct_flags, Err(Error::InvalidStackOperation), vec![].into());
// <sig> <msg> <pubKey> OP_CHECKDATASIG fails if <pubKey> is not a validly encoded public key.
let incorrect_pubkey_script = Builder::default()
.push_data(&*correct_signature)
.push_data(&*message)
.push_data(&[77u8; 15])
.push_opcode(Opcode::OP_CHECKDATASIG)
.into_script();
basic_test_with_flags(&incorrect_pubkey_script, &correct_flags, Err(Error::PubkeyType), vec![].into());
// assuming that check_signature_encoding correctness is proved by other tests:
// <sig> <msg> <pubKey> OP_CHECKDATASIG fails if <sig> is not a validly encoded signature with strict DER encoding.
// <sig> <msg> <pubKey> OP_CHECKDATASIG fails if signature <sig> is not empty and does not pass the Low S check.
let incorrectly_encoded_signature_script = Builder::default()
.push_data(&[0u8; 65])
.push_data(&*message)
.push_data(&*pubkey)
.push_opcode(Opcode::OP_CHECKDATASIG)
.into_script();
basic_test_with_flags(&incorrectly_encoded_signature_script, &correct_flags, Err(Error::SignatureDer), vec![].into());
// <sig> <msg> <pubKey> OP_CHECKDATASIG fails if signature <sig> is not empty and does not pass signature validation of <msg> and <pubKey>.
let incorrect_signature_script = Builder::default()
.push_data(&*correct_signature_for_other_message)
.push_data(&*message)
.push_data(&*pubkey)
.push_opcode(Opcode::OP_CHECKDATASIG)
.into_script();
basic_test_with_flags(&incorrect_signature_script, &correct_flags, Ok(false), vec![vec![0].into()].into());
// <sig> <msg> <pubKey> OP_CHECKDATASIG pops three elements and pushes false onto the stack if <sig> is an empty byte array.
let empty_signature_script = Builder::default()
.push_data(&[])
.push_data(&*message)
.push_data(&*pubkey)
.push_opcode(Opcode::OP_CHECKDATASIG)
.into_script();
basic_test_with_flags(&empty_signature_script, &correct_flags, Ok(false), vec![vec![0].into()].into());
// <sig> <msg> <pubKey> OP_CHECKDATASIG pops three elements and pushes true onto the stack if <sig> is a valid signature of <msg> with respect to <pubKey>.
basic_test_with_flags(&correct_signature_script, &correct_flags, Ok(true), vec![vec![1].into()].into());
}
#[test]
fn checkdatasigverify_spec_tests() {
// official tests from:
// https://github.com/bitcoincashorg/bitcoincash.org/blob/0c6f91b0b713aae3bc6c9834b46e80e247ff5fab/spec/op_checkdatasig.md
let kp = KeyPair::from_private(Private { network: Network::Mainnet, secret: 1.into(), compressed: false, }).unwrap();
let pubkey = kp.public().clone();
let message = vec![42u8; 32];
let correct_signature = kp.private().sign(&Message::from(sha256(&message))).unwrap();
let correct_signature_for_other_message = kp.private().sign(&[43u8; 32].into()).unwrap();
let mut correct_signature = correct_signature.to_vec();
let mut correct_signature_for_other_message = correct_signature_for_other_message.to_vec();
correct_signature.push(0x81);
correct_signature_for_other_message.push(0x81);
let correct_flags = VerificationFlags::default()
.verify_checkdatasig(true)
.verify_dersig(true)
.verify_strictenc(true);
let incorrect_flags = VerificationFlags::default().verify_checkdatasig(false);
let correct_signature_script = Builder::default()
.push_data(&*correct_signature)
.push_data(&*message)
.push_data(&*pubkey)
.push_opcode(Opcode::OP_CHECKDATASIGVERIFY)
.into_script();
// <sig> <msg> <pubKey> OP_CHECKDATASIGVERIFY fails if 15 November 2018 protocol upgrade is not yet activated.
basic_test_with_flags(&correct_signature_script, &incorrect_flags, Err(Error::DisabledOpcode(Opcode::OP_CHECKDATASIGVERIFY)), vec![].into());
// <sig> <msg> OP_CHECKDATASIGVERIFY fails if there are fewer than 3 item on stack.
let too_few_args_sig_script = Builder::default()
.push_data(&[1u8; 32])
.push_data(&*message)
.push_opcode(Opcode::OP_CHECKDATASIGVERIFY)
.into_script();
basic_test_with_flags(&too_few_args_sig_script, &correct_flags, Err(Error::InvalidStackOperation), vec![].into());
// <sig> <msg> <pubKey> OP_CHECKDATASIGVERIFYfails if <pubKey> is not a validly encoded public key.
let incorrect_pubkey_script = Builder::default()
.push_data(&*correct_signature)
.push_data(&*message)
.push_data(&[77u8; 15])
.push_opcode(Opcode::OP_CHECKDATASIGVERIFY)
.into_script();
basic_test_with_flags(&incorrect_pubkey_script, &correct_flags, Err(Error::PubkeyType), vec![].into());
// assuming that check_signature_encoding correctness is proved by other tests:
// <sig> <msg> <pubKey> OP_CHECKDATASIGVERIFY fails if <sig> is not a validly encoded signature with strict DER encoding.
// <sig> <msg> <pubKey> OP_CHECKDATASIGVERIFY fails if signature <sig> is not empty and does not pass the Low S check.
let incorrectly_encoded_signature_script = Builder::default()
.push_data(&[0u8; 65])
.push_data(&*message)
.push_data(&*pubkey)
.push_opcode(Opcode::OP_CHECKDATASIGVERIFY)
.into_script();
basic_test_with_flags(&incorrectly_encoded_signature_script, &correct_flags, Err(Error::SignatureDer), vec![].into());
// <sig> <msg> <pubKey> OP_CHECKDATASIGVERIFY fails if <sig> is not a valid signature of <msg> with respect to <pubKey>.
let incorrect_signature_script = Builder::default()
.push_data(&*correct_signature_for_other_message)
.push_data(&*message)
.push_data(&*pubkey)
.push_opcode(Opcode::OP_CHECKDATASIGVERIFY)
.into_script();
basic_test_with_flags(&incorrect_signature_script, &correct_flags, Err(Error::CheckDataSigVerify), vec![].into());
// <sig> <msg> <pubKey> OP_CHECKDATASIGVERIFY pops the top three stack elements if <sig> is a valid signature of <msg> with respect to <pubKey>.
// Ok(false) means success here, because OP_CHECKDATASIGVERIFY leaves empty stack
basic_test_with_flags(&correct_signature_script, &correct_flags, Ok(false), vec![].into());
}
} }

View File

@ -213,6 +213,10 @@ pub enum Opcode {
OP_NOP8 = 0xb7, OP_NOP8 = 0xb7,
OP_NOP9 = 0xb8, OP_NOP9 = 0xb8,
OP_NOP10 = 0xb9, OP_NOP10 = 0xb9,
// BCH crypto
OP_CHECKDATASIG = 0xba,
OP_CHECKDATASIGVERIFY = 0xbb,
} }
impl fmt::Display for Opcode { impl fmt::Display for Opcode {
@ -430,6 +434,11 @@ impl Opcode {
0xb7 => Some(OP_NOP8), 0xb7 => Some(OP_NOP8),
0xb8 => Some(OP_NOP9), 0xb8 => Some(OP_NOP9),
0xb9 => Some(OP_NOP10), 0xb9 => Some(OP_NOP10),
// BCH crypto
0xba => Some(OP_CHECKDATASIG),
0xbb => Some(OP_CHECKDATASIGVERIFY),
_ => None, _ => None,
} }
} }
@ -688,5 +697,9 @@ mod tests {
assert_eq!(Opcode::OP_NOP8, Opcode::from_u8(Opcode::OP_NOP8 as u8).unwrap()); assert_eq!(Opcode::OP_NOP8, Opcode::from_u8(Opcode::OP_NOP8 as u8).unwrap());
assert_eq!(Opcode::OP_NOP9, Opcode::from_u8(Opcode::OP_NOP9 as u8).unwrap()); assert_eq!(Opcode::OP_NOP9, Opcode::from_u8(Opcode::OP_NOP9 as u8).unwrap());
assert_eq!(Opcode::OP_NOP10, Opcode::from_u8(Opcode::OP_NOP10 as u8).unwrap()); assert_eq!(Opcode::OP_NOP10, Opcode::from_u8(Opcode::OP_NOP10 as u8).unwrap());
// BCH crypto
assert_eq!(Opcode::OP_CHECKDATASIG, Opcode::from_u8(Opcode::OP_CHECKDATASIG as u8).unwrap());
assert_eq!(Opcode::OP_CHECKDATASIGVERIFY, Opcode::from_u8(Opcode::OP_CHECKDATASIGVERIFY as u8).unwrap());
} }
} }

View File

@ -367,7 +367,7 @@ impl Script {
Opcodes { position: 0, script: self } Opcodes { position: 0, script: self }
} }
pub fn sigops_count(&self, serialized_script: bool) -> usize { pub fn sigops_count(&self, checkdatasig_active: bool, serialized_script: bool) -> usize {
let mut last_opcode = Opcode::OP_0; let mut last_opcode = Opcode::OP_0;
let mut total = 0; let mut total = 0;
for opcode in self.opcodes() { for opcode in self.opcodes() {
@ -381,6 +381,9 @@ impl Script {
Opcode::OP_CHECKSIG | Opcode::OP_CHECKSIGVERIFY => { Opcode::OP_CHECKSIG | Opcode::OP_CHECKSIGVERIFY => {
total += 1; total += 1;
}, },
Opcode::OP_CHECKDATASIG | Opcode::OP_CHECKDATASIGVERIFY if checkdatasig_active => {
total += 1;
},
Opcode::OP_CHECKMULTISIG | Opcode::OP_CHECKMULTISIGVERIFY => { Opcode::OP_CHECKMULTISIG | Opcode::OP_CHECKMULTISIGVERIFY => {
if serialized_script && last_opcode.is_within_op_n() { if serialized_script && last_opcode.is_within_op_n() {
total += last_opcode.decode_op_n() as usize; total += last_opcode.decode_op_n() as usize;
@ -454,7 +457,7 @@ impl Script {
} }
} }
pub fn pay_to_script_hash_sigops(&self, prev_out: &Script) -> usize { pub fn pay_to_script_hash_sigops(&self, checkdatasig_active: bool, prev_out: &Script) -> usize {
if !prev_out.is_pay_to_script_hash() { if !prev_out.is_pay_to_script_hash() {
return 0; return 0;
} }
@ -470,7 +473,7 @@ impl Script {
.to_vec() .to_vec()
.into(); .into();
script.sigops_count(true) script.sigops_count(checkdatasig_active, true)
} }
} }
@ -669,11 +672,11 @@ OP_ADD
#[test] #[test]
fn test_sigops_count() { fn test_sigops_count() {
assert_eq!(1usize, Script::from("76a914aab76ba4877d696590d94ea3e02948b55294815188ac").sigops_count(false)); assert_eq!(1usize, Script::from("76a914aab76ba4877d696590d94ea3e02948b55294815188ac").sigops_count(false, false));
assert_eq!(2usize, Script::from("522102004525da5546e7603eefad5ef971e82f7dad2272b34e6b3036ab1fe3d299c22f21037d7f2227e6c646707d1c61ecceb821794124363a2cf2c1d2a6f28cf01e5d6abe52ae").sigops_count(true)); assert_eq!(2usize, Script::from("522102004525da5546e7603eefad5ef971e82f7dad2272b34e6b3036ab1fe3d299c22f21037d7f2227e6c646707d1c61ecceb821794124363a2cf2c1d2a6f28cf01e5d6abe52ae").sigops_count(false, true));
assert_eq!(20usize, Script::from("522102004525da5546e7603eefad5ef971e82f7dad2272b34e6b3036ab1fe3d299c22f21037d7f2227e6c646707d1c61ecceb821794124363a2cf2c1d2a6f28cf01e5d6abe52ae").sigops_count(false)); assert_eq!(20usize, Script::from("522102004525da5546e7603eefad5ef971e82f7dad2272b34e6b3036ab1fe3d299c22f21037d7f2227e6c646707d1c61ecceb821794124363a2cf2c1d2a6f28cf01e5d6abe52ae").sigops_count(false, false));
assert_eq!(0usize, Script::from("a9146262b64aec1f4a4c1d21b32e9c2811dd2171fd7587").sigops_count(false)); assert_eq!(0usize, Script::from("a9146262b64aec1f4a4c1d21b32e9c2811dd2171fd7587").sigops_count(false, false));
assert_eq!(1usize, Script::from("4104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac").sigops_count(false)); assert_eq!(1usize, Script::from("4104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac").sigops_count(false, false));
} }
#[test] #[test]
@ -688,7 +691,7 @@ OP_ADD
script[max_block_sigops - block_sigops + 3] = (overmax >> 16) as u8; script[max_block_sigops - block_sigops + 3] = (overmax >> 16) as u8;
script[max_block_sigops - block_sigops + 4] = (overmax >> 24) as u8; script[max_block_sigops - block_sigops + 4] = (overmax >> 24) as u8;
let script: Script = script.into(); let script: Script = script.into();
assert_eq!(script.sigops_count(false), 20001); assert_eq!(script.sigops_count(false, false), 20001);
} }
#[test] #[test]
@ -702,7 +705,7 @@ OP_ADD
script[max_block_sigops - block_sigops + 4] = 0xff; script[max_block_sigops - block_sigops + 4] = 0xff;
script[max_block_sigops - block_sigops + 5] = 0xff; script[max_block_sigops - block_sigops + 5] = 0xff;
let script: Script = script.into(); let script: Script = script.into();
assert_eq!(script.sigops_count(false), 20001); assert_eq!(script.sigops_count(false, false), 20001);
} }
#[test] #[test]
@ -802,4 +805,14 @@ OP_ADD
assert_eq!(script.script_type(), ScriptType::ScriptHash); assert_eq!(script.script_type(), ScriptType::ScriptHash);
assert_eq!(script.num_signatures_required(), 1); assert_eq!(script.num_signatures_required(), 1);
} }
#[test]
fn test_num_signatures_with_checkdatasig() {
let script = Builder::default().push_opcode(Opcode::OP_CHECKDATASIG).into_script();
assert_eq!(script.sigops_count(false, false), 0);
assert_eq!(script.sigops_count(true, false), 1);
let script = Builder::default().push_opcode(Opcode::OP_CHECKDATASIGVERIFY).into_script();
assert_eq!(script.sigops_count(false, false), 0);
assert_eq!(script.sigops_count(true, false), 1);
}
} }

View File

@ -1,4 +1,4 @@
use keys::{Public, Signature}; use keys::{Public, Signature, Message};
use chain::constants::{ use chain::constants::{
SEQUENCE_FINAL, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_FINAL, SEQUENCE_LOCKTIME_DISABLE_FLAG,
SEQUENCE_LOCKTIME_MASK, SEQUENCE_LOCKTIME_TYPE_FLAG, LOCKTIME_THRESHOLD SEQUENCE_LOCKTIME_MASK, SEQUENCE_LOCKTIME_TYPE_FLAG, LOCKTIME_THRESHOLD
@ -8,6 +8,13 @@ use {Script, TransactionInputSigner, Num};
/// Checks transaction signature /// Checks transaction signature
pub trait SignatureChecker { pub trait SignatureChecker {
fn verify_signature(
&self,
signature: &Signature,
public: &Public,
hash: &Message,
) -> bool;
fn check_signature( fn check_signature(
&self, &self,
signature: &Signature, signature: &Signature,
@ -25,6 +32,10 @@ pub trait SignatureChecker {
pub struct NoopSignatureChecker; pub struct NoopSignatureChecker;
impl SignatureChecker for NoopSignatureChecker { impl SignatureChecker for NoopSignatureChecker {
fn verify_signature(&self, signature: &Signature, public: &Public, hash: &Message) -> bool {
public.verify(hash, signature).unwrap_or(false)
}
fn check_signature(&self, _: &Signature, _: &Public, _: &Script, _: u32, _: SignatureVersion) -> bool { fn check_signature(&self, _: &Signature, _: &Public, _: &Script, _: u32, _: SignatureVersion) -> bool {
false false
} }
@ -46,6 +57,15 @@ pub struct TransactionSignatureChecker {
} }
impl SignatureChecker for TransactionSignatureChecker { impl SignatureChecker for TransactionSignatureChecker {
fn verify_signature(
&self,
signature: &Signature,
public: &Public,
hash: &Message,
) -> bool {
public.verify(hash, signature).unwrap_or(false)
}
fn check_signature( fn check_signature(
&self, &self,
signature: &Signature, signature: &Signature,
@ -55,7 +75,7 @@ impl SignatureChecker for TransactionSignatureChecker {
version: SignatureVersion version: SignatureVersion
) -> bool { ) -> bool {
let hash = self.signer.signature_hash(self.input_index, self.input_amount, script_code, version, sighashtype); let hash = self.signer.signature_hash(self.input_index, self.input_amount, script_code, version, sighashtype);
public.verify(&hash, signature).unwrap_or(false) self.verify_signature(signature, public, &hash)
} }
fn check_lock_time(&self, lock_time: Num) -> bool { fn check_lock_time(&self, lock_time: Num) -> bool {

View File

@ -286,7 +286,7 @@ impl<T, U, V> LocalNode<T, U, V> where T: TaskExecutor, U: Server, V: Client {
max_block_sigops: self.consensus.fork.max_block_sigops(new_block_height, max_block_size) as u32, max_block_sigops: self.consensus.fork.max_block_sigops(new_block_height, max_block_size) as u32,
}; };
let memory_pool = &*self.memory_pool.read(); let memory_pool = &*self.memory_pool.read();
block_assembler.create_new_block(&self.storage, memory_pool, time::get_time().sec as u32, &self.consensus) block_assembler.create_new_block(&self.storage, memory_pool, time::get_time().sec as u32, median_timestamp, &self.consensus)
} }
/// Install synchronization events listener /// Install synchronization events listener

View File

@ -37,7 +37,7 @@ impl<'a> BlockAcceptor<'a> {
serialized_size: BlockSerializedSize::new(block, consensus, deployments, height, median_time_past), serialized_size: BlockSerializedSize::new(block, consensus, deployments, height, median_time_past),
coinbase_script: BlockCoinbaseScript::new(block, consensus, height), coinbase_script: BlockCoinbaseScript::new(block, consensus, height),
coinbase_claim: BlockCoinbaseClaim::new(block, consensus, store, height, median_time_past), coinbase_claim: BlockCoinbaseClaim::new(block, consensus, store, height, median_time_past),
sigops: BlockSigops::new(block, store, consensus, height), sigops: BlockSigops::new(block, store, consensus, height, median_time_past),
witness: BlockWitness::new(block, deployments), witness: BlockWitness::new(block, deployments),
ordering: BlockTransactionOrdering::new(block, consensus, median_time_past), ordering: BlockTransactionOrdering::new(block, consensus, median_time_past),
} }
@ -140,18 +140,30 @@ pub struct BlockSigops<'a> {
consensus: &'a ConsensusParams, consensus: &'a ConsensusParams,
height: u32, height: u32,
bip16_active: bool, bip16_active: bool,
checkdatasig_active: bool,
} }
impl<'a> BlockSigops<'a> { impl<'a> BlockSigops<'a> {
fn new(block: CanonBlock<'a>, store: &'a TransactionOutputProvider, consensus: &'a ConsensusParams, height: u32) -> Self { fn new(
block: CanonBlock<'a>,
store: &'a TransactionOutputProvider,
consensus: &'a ConsensusParams,
height: u32,
median_time_past: u32,
) -> Self {
let bip16_active = block.header.raw.time >= consensus.bip16_time; let bip16_active = block.header.raw.time >= consensus.bip16_time;
let checkdatasig_active = match consensus.fork {
ConsensusFork::BitcoinCash(ref fork) => median_time_past >= fork.magnetic_anomaly_time,
_ => false,
};
BlockSigops { BlockSigops {
block: block, block: block,
store: store, store: store,
consensus: consensus, consensus: consensus,
height: height, height: height,
bip16_active: bip16_active, bip16_active,
checkdatasig_active,
} }
} }
@ -159,7 +171,7 @@ impl<'a> BlockSigops<'a> {
let store = DuplexTransactionOutputProvider::new(self.store, &*self.block); let store = DuplexTransactionOutputProvider::new(self.store, &*self.block);
let (sigops, sigops_cost) = self.block.transactions.iter() let (sigops, sigops_cost) = self.block.transactions.iter()
.map(|tx| { .map(|tx| {
let tx_sigops = transaction_sigops(&tx.raw, &store, self.bip16_active); let tx_sigops = transaction_sigops(&tx.raw, &store, self.bip16_active, self.checkdatasig_active);
let tx_sigops_cost = transaction_sigops_cost(&tx.raw, &store, tx_sigops); let tx_sigops_cost = transaction_sigops_cost(&tx.raw, &store, tx_sigops);
(tx_sigops, tx_sigops_cost) (tx_sigops, tx_sigops_cost)
}) })

View File

@ -275,7 +275,11 @@ impl<'a> TransactionSigops<'a> {
fn check(&self) -> Result<(), TransactionError> { fn check(&self) -> Result<(), TransactionError> {
let bip16_active = self.time >= self.consensus_params.bip16_time; let bip16_active = self.time >= self.consensus_params.bip16_time;
let sigops = transaction_sigops(&self.transaction.raw, &self.store, bip16_active); let checkdatasig_active = match self.consensus_params.fork {
ConsensusFork::BitcoinCash(ref fork) => self.time >= fork.magnetic_anomaly_time,
_ => false
};
let sigops = transaction_sigops(&self.transaction.raw, &self.store, bip16_active, checkdatasig_active);
if sigops > self.max_sigops { if sigops > self.max_sigops {
Err(TransactionError::MaxSigops) Err(TransactionError::MaxSigops)
} else { } else {
@ -296,6 +300,7 @@ pub struct TransactionEval<'a> {
verify_witness: bool, verify_witness: bool,
verify_nulldummy: bool, verify_nulldummy: bool,
verify_monolith_opcodes: bool, verify_monolith_opcodes: bool,
verify_magnetic_anomaly_opcodes: bool,
signature_version: SignatureVersion, signature_version: SignatureVersion,
} }
@ -318,7 +323,11 @@ impl<'a> TransactionEval<'a> {
let verify_locktime = height >= params.bip65_height; let verify_locktime = height >= params.bip65_height;
let verify_dersig = height >= params.bip66_height; let verify_dersig = height >= params.bip66_height;
let verify_monolith_opcodes = match params.fork { let verify_monolith_opcodes = match params.fork {
ConsensusFork::BitcoinCash(ref fork) if median_timestamp >= fork.monolith_time => true, ConsensusFork::BitcoinCash(ref fork) => median_timestamp >= fork.monolith_time,
_ => false,
};
let verify_magnetic_anomaly_opcodes = match params.fork {
ConsensusFork::BitcoinCash(ref fork) => median_timestamp >= fork.magnetic_anomaly_time,
_ => false, _ => false,
}; };
let signature_version = match params.fork { let signature_version = match params.fork {
@ -342,6 +351,7 @@ impl<'a> TransactionEval<'a> {
verify_witness: verify_witness, verify_witness: verify_witness,
verify_nulldummy: verify_nulldummy, verify_nulldummy: verify_nulldummy,
verify_monolith_opcodes: verify_monolith_opcodes, verify_monolith_opcodes: verify_monolith_opcodes,
verify_magnetic_anomaly_opcodes: verify_magnetic_anomaly_opcodes,
signature_version: signature_version, signature_version: signature_version,
} }
} }
@ -391,7 +401,8 @@ impl<'a> TransactionEval<'a> {
.verify_div(self.verify_monolith_opcodes) .verify_div(self.verify_monolith_opcodes)
.verify_mod(self.verify_monolith_opcodes) .verify_mod(self.verify_monolith_opcodes)
.verify_bin2num(self.verify_monolith_opcodes) .verify_bin2num(self.verify_monolith_opcodes)
.verify_num2bin(self.verify_monolith_opcodes); .verify_num2bin(self.verify_monolith_opcodes)
.verify_checkdatasig(self.verify_magnetic_anomaly_opcodes);
try!(verify_script(&input, &output, &script_witness, &flags, &checker, self.signature_version) try!(verify_script(&input, &output, &script_witness, &flags, &checker, self.signature_version)
.map_err(|e| TransactionError::Signature(index, e))); .map_err(|e| TransactionError::Signature(index, e)));

View File

@ -10,11 +10,12 @@ use script::{Script, ScriptWitness};
pub fn transaction_sigops( pub fn transaction_sigops(
transaction: &Transaction, transaction: &Transaction,
store: &TransactionOutputProvider, store: &TransactionOutputProvider,
bip16_active: bool bip16_active: bool,
checkdatasig_active: bool,
) -> usize { ) -> usize {
let output_sigops: usize = transaction.outputs.iter().map(|output| { let output_sigops: usize = transaction.outputs.iter().map(|output| {
let output_script: Script = output.script_pubkey.clone().into(); let output_script: Script = output.script_pubkey.clone().into();
output_script.sigops_count(false) output_script.sigops_count(checkdatasig_active, false)
}).sum(); }).sum();
// TODO: bitcoin/bitcoin also includes input_sigops here // TODO: bitcoin/bitcoin also includes input_sigops here
@ -27,14 +28,14 @@ pub fn transaction_sigops(
for input in &transaction.inputs { for input in &transaction.inputs {
let input_script: Script = input.script_sig.clone().into(); let input_script: Script = input.script_sig.clone().into();
input_sigops += input_script.sigops_count(false); input_sigops += input_script.sigops_count(checkdatasig_active, false);
if bip16_active { if bip16_active {
let previous_output = match store.transaction_output(&input.previous_output, usize::max_value()) { let previous_output = match store.transaction_output(&input.previous_output, usize::max_value()) {
Some(output) => output, Some(output) => output,
None => continue, None => continue,
}; };
let prevout_script: Script = previous_output.script_pubkey.into(); let prevout_script: Script = previous_output.script_pubkey.into();
bip16_sigops += input_script.pay_to_script_hash_sigops(&prevout_script); bip16_sigops += input_script.pay_to_script_hash_sigops(checkdatasig_active, &prevout_script);
} }
} }
@ -86,7 +87,7 @@ fn witness_program_sigops(
match witness_version { match witness_version {
0 if witness_program.len() == 20 => 1, 0 if witness_program.len() == 20 => 1,
0 if witness_program.len() == 32 => match script_witness.last() { 0 if witness_program.len() == 32 => match script_witness.last() {
Some(subscript) => Script::new(subscript.clone()).sigops_count(true), Some(subscript) => Script::new(subscript.clone()).sigops_count(false, true),
_ => 0, _ => 0,
}, },
_ => 0, _ => 0,

View File

@ -163,7 +163,7 @@ impl<'a> BlockSigops<'a> {
fn check(&self) -> Result<(), Error> { fn check(&self) -> Result<(), Error> {
// We cannot know if bip16 is enabled at this point so we disable it. // We cannot know if bip16 is enabled at this point so we disable it.
let sigops = self.block.transactions.iter() let sigops = self.block.transactions.iter()
.map(|tx| transaction_sigops(&tx.raw, &NoopStore, false)) .map(|tx| transaction_sigops(&tx.raw, &NoopStore, false, false))
.sum::<usize>(); .sum::<usize>();
if sigops > self.max_sigops { if sigops > self.max_sigops {

View File

@ -186,7 +186,7 @@ impl<'a> TransactionSigops<'a> {
} }
fn check(&self) -> Result<(), TransactionError> { fn check(&self) -> Result<(), TransactionError> {
let sigops = transaction_sigops(&self.transaction.raw, &NoopStore, false); let sigops = transaction_sigops(&self.transaction.raw, &NoopStore, false, false);
if sigops > self.max_sigops { if sigops > self.max_sigops {
Err(TransactionError::MaxSigops) Err(TransactionError::MaxSigops)
} else { } else {