From 9742b113df98e4846a91d31044f498f91744ce11 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 7 Dec 2018 12:33:57 +0300 Subject: [PATCH] cache signature portions --- script/src/interpreter.rs | 47 +++++++++++--------- script/src/lib.rs | 2 +- script/src/sign.rs | 61 +++++++++++++++++++------- script/src/verify.rs | 10 +++-- verification/src/accept_transaction.rs | 8 ++-- 5 files changed, 83 insertions(+), 45 deletions(-) diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index 589ace38..8a9fe967 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -11,7 +11,7 @@ use { /// Helper function. fn check_signature( - checker: &SignatureChecker, + checker: &mut SignatureChecker, mut script_sig: Vec, public: Vec, script_code: &Script, @@ -33,7 +33,7 @@ fn check_signature( /// Helper function. fn verify_signature( - checker: &SignatureChecker, + checker: &mut SignatureChecker, signature: Vec, public: Vec, message: Message, @@ -249,7 +249,7 @@ pub fn verify_script( script_sig: &Script, script_pubkey: &Script, flags: &VerificationFlags, - checker: &SignatureChecker, + checker: &mut SignatureChecker, ) -> Result<(), Error> { if flags.verify_sigpushonly && !script_sig.is_push_only() { return Err(Error::SignaturePushOnly); @@ -311,7 +311,7 @@ pub fn eval_script( stack: &mut Stack, script: &Script, flags: &VerificationFlags, - checker: &SignatureChecker, + checker: &mut SignatureChecker, ) -> Result { if script.len() > script::MAX_SCRIPT_SIZE { return Err(Error::ScriptSize); @@ -1055,7 +1055,7 @@ mod tests { let expected: Stack = vec![vec![0x5a].into()].into(); let flags = VerificationFlags::default() .verify_p2sh(true); - let checker = NoopSignatureChecker; + let mut checker = NoopSignatureChecker; let direct: Script = vec![Opcode::OP_PUSHBYTES_1 as u8, 0x5a].into(); let pushdata1: Script = vec![Opcode::OP_PUSHDATA1 as u8, 0x1, 0x5a].into(); let pushdata2: Script = vec![Opcode::OP_PUSHDATA2 as u8, 0x1, 0, 0x5a].into(); @@ -1065,10 +1065,10 @@ mod tests { let mut pushdata1_stack = Stack::new(); let mut pushdata2_stack = Stack::new(); let mut pushdata4_stack = Stack::new(); - assert!(eval_script(&mut direct_stack, &direct, &flags, &checker).unwrap()); - assert!(eval_script(&mut pushdata1_stack, &pushdata1, &flags, &checker).unwrap()); - assert!(eval_script(&mut pushdata2_stack, &pushdata2, &flags, &checker).unwrap()); - assert!(eval_script(&mut pushdata4_stack, &pushdata4, &flags, &checker).unwrap()); + assert!(eval_script(&mut direct_stack, &direct, &flags, &mut checker).unwrap()); + assert!(eval_script(&mut pushdata1_stack, &pushdata1, &flags, &mut checker).unwrap()); + assert!(eval_script(&mut pushdata2_stack, &pushdata2, &flags, &mut checker).unwrap()); + assert!(eval_script(&mut pushdata4_stack, &pushdata4, &flags, &mut checker).unwrap()); assert_eq!(direct_stack, expected); assert_eq!(pushdata1_stack, expected); @@ -1077,9 +1077,9 @@ mod tests { } fn basic_test_with_flags(script: &Script, flags: &VerificationFlags, expected: Result, expected_stack: Stack) { - let checker = NoopSignatureChecker; + let mut checker = NoopSignatureChecker; let mut stack = Stack::new(); - assert_eq!(eval_script(&mut stack, script, &flags, &checker), expected); + assert_eq!(eval_script(&mut stack, script, &flags, &mut checker), expected); if expected.is_ok() { assert_eq!(stack, expected_stack); } @@ -1977,17 +1977,18 @@ mod tests { fn test_check_transaction_signature() { let tx: Transaction = "0100000001484d40d45b9ea0d652fca8258ab7caa42541eb52975857f96fb50cd732c8b481000000008a47304402202cb265bf10707bf49346c3515dd3d16fc454618c58ec0a0ff448a676c54ff71302206c6624d762a1fcef4618284ead8f08678ac05b13c84235f1654e6ad168233e8201410414e301b2328f17442c0b8310d787bf3d8a404cfbd0704f135b6ad4b2d3ee751310f981926e53a6e8c39bd7d3fefd576c543cce493cbac06388f2651d1aacbfcdffffffff0162640100000000001976a914c8e90996c7c6080ee06284600c684ed904d14c5c88ac00000000".into(); let signer: TransactionInputSigner = tx.into(); - let checker = TransactionSignatureChecker { + let mut checker = TransactionSignatureChecker { signer: signer, input_index: 0, input_amount: 0, consensus_branch_id: 0, + cache: None, }; 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, &mut checker), Ok(())); } // https://blockchain.info/rawtx/02b082113e35d5386285094c2829e7e2963fa0b5369fb7f4b79c4c90877dcd3d @@ -1995,17 +1996,18 @@ mod tests { fn test_check_transaction_multisig() { let tx: Transaction = "01000000013dcd7d87904c9cb7f4b79f36b5a03f96e2e729284c09856238d5353e1182b00200000000fd5e0100483045022100deeb1f13b5927b5e32d877f3c42a4b028e2e0ce5010fdb4e7f7b5e2921c1dcd2022068631cb285e8c1be9f061d2968a18c3163b780656f30a049effee640e80d9bff01483045022100ee80e164622c64507d243bd949217d666d8b16486e153ac6a1f8e04c351b71a502203691bef46236ca2b4f5e60a82a853a33d6712d6a1e7bf9a65e575aeb7328db8c014cc9524104a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd41046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187410411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e8353aeffffffff0130d90000000000001976a914569076ba39fc4ff6a2291d9ea9196d8c08f9c7ab88ac00000000".into(); let signer: TransactionInputSigner = tx.into(); - let checker = TransactionSignatureChecker { + let mut checker = TransactionSignatureChecker { signer: signer, input_index: 0, input_amount: 0, consensus_branch_id: 0, + cache: None, }; 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, &mut checker), Ok(())); } // https://blockchain.info/en/tx/12b5633bad1f9c167d523ad1aa1947b2732a865bf5414eab2f9e5ae5d5c191ba?show_adv=true @@ -2013,17 +2015,18 @@ mod tests { fn test_transaction_with_high_s_signature() { let tx: Transaction = "010000000173805864da01f15093f7837607ab8be7c3705e29a9d4a12c9116d709f8911e590100000049483045022052ffc1929a2d8bd365c6a2a4e3421711b4b1e1b8781698ca9075807b4227abcb0221009984107ddb9e3813782b095d0d84361ed4c76e5edaf6561d252ae162c2341cfb01ffffffff0200e1f50500000000434104baa9d36653155627c740b3409a734d4eaf5dcca9fb4f736622ee18efcf0aec2b758b2ec40db18fbae708f691edb2d4a2a3775eb413d16e2e3c0f8d4c69119fd1ac009ce4a60000000043410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac00000000".into(); let signer: TransactionInputSigner = tx.into(); - let checker = TransactionSignatureChecker { + let mut checker = TransactionSignatureChecker { signer: signer, input_index: 0, input_amount: 0, consensus_branch_id: 0, + cache: None, }; 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, &mut checker), Ok(())); } // https://blockchain.info/rawtx/fb0a1d8d34fa5537e461ac384bac761125e1bfa7fec286fa72511240fa66864d @@ -2031,17 +2034,18 @@ mod tests { fn test_transaction_from_124276() { let tx: Transaction = "01000000012316aac445c13ff31af5f3d1e2cebcada83e54ba10d15e01f49ec28bddc285aa000000008e4b3048022200002b83d59c1d23c08efd82ee0662fec23309c3adbcbd1f0b8695378db4b14e736602220000334a96676e58b1bb01784cb7c556dd8ce1c220171904da22e18fe1e7d1510db5014104d0fe07ff74c9ef5b00fed1104fad43ecf72dbab9e60733e4f56eacf24b20cf3b8cd945bcabcc73ba0158bf9ce769d43e94bd58c5c7e331a188922b3fe9ca1f5affffffff01c0c62d00000000001976a9147a2a3b481ca80c4ba7939c54d9278e50189d94f988ac00000000".into(); let signer: TransactionInputSigner = tx.into(); - let checker = TransactionSignatureChecker { + let mut checker = TransactionSignatureChecker { signer: signer, input_index: 0, input_amount: 0, consensus_branch_id: 0, + cache: None, }; 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, &mut checker), Ok(())); } // https://blockchain.info/rawtx/54fabd73f1d20c980a0686bf0035078e07f69c58437e4d586fb29aa0bee9814f @@ -2049,16 +2053,17 @@ mod tests { fn test_arithmetic_correct_arguments_order() { let tx: Transaction = "01000000010c0e314bd7bb14721b3cfd8e487cd6866173354f87ca2cf4d13c8d3feb4301a6000000004a483045022100d92e4b61452d91a473a43cde4b469a472467c0ba0cbd5ebba0834e4f4762810402204802b76b7783db57ac1f61d2992799810e173e91055938750815b6d8a675902e014fffffffff0140548900000000001976a914a86e8ee2a05a44613904e18132e49b2448adc4e688ac00000000".into(); let signer: TransactionInputSigner = tx.into(); - let checker = TransactionSignatureChecker { + let mut checker = TransactionSignatureChecker { signer: signer, input_index: 0, input_amount: 0, consensus_branch_id: 0, + cache: None, }; 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, &mut checker), Ok(())); } #[test] diff --git a/script/src/lib.rs b/script/src/lib.rs index 73090213..62fc0e5c 100644 --- a/script/src/lib.rs +++ b/script/src/lib.rs @@ -31,7 +31,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::{SighashCache, TransactionInputSigner, UnsignedTransactionInput}; pub use self::stack::Stack; pub use self::verify::{SignatureChecker, NoopSignatureChecker, TransactionSignatureChecker}; diff --git a/script/src/sign.rs b/script/src/sign.rs index b5bd6c79..18439460 100644 --- a/script/src/sign.rs +++ b/script/src/sign.rs @@ -151,6 +151,7 @@ impl TransactionInputSigner { /// Pass None as input_index to compute transparent input signature pub fn signature_hash( &self, + cache: &mut Option, input_index: Option, input_amount: u64, script_pubkey: &Script, @@ -162,6 +163,7 @@ impl TransactionInputSigner { match signature_version { SignatureVersion::Sprout => self.signature_hash_sprout(input_index, script_pubkey, sighashtype, sighash), SignatureVersion::Overwinter | SignatureVersion::Sapling => self.signature_hash_post_overwinter( + cache, input_index, input_amount, script_pubkey, @@ -246,6 +248,7 @@ impl TransactionInputSigner { /// Overwinter/sapling version of the signature. fn signature_hash_post_overwinter( &self, + cache: &mut Option, input_index: Option, input_amount: u64, script_pubkey: &Script, @@ -254,21 +257,37 @@ impl TransactionInputSigner { consensus_branch_id: u32, sapling: bool, ) -> H256 { - let hash_prevouts = compute_hash_prevouts(sighash, &self.inputs); - let hash_sequence = compute_hash_sequence(sighash, &self.inputs); - let hash_outputs = compute_hash_outputs(sighash, input_index, &self.outputs); - let hash_join_split = compute_hash_join_split(self.join_split.as_ref()); + // compute signature portions that can be reused for other inputs + let hash_prevouts = cache.as_ref().map(|c| c.hash_prevouts) + .unwrap_or_else(|| compute_hash_prevouts(sighash, &self.inputs)); + let hash_sequence = cache.as_ref().map(|c| c.hash_sequence) + .unwrap_or_else(|| compute_hash_sequence(sighash, &self.inputs)); + let hash_outputs = compute_hash_outputs(cache, sighash, input_index, &self.outputs); + let hash_join_split = cache.as_ref().map(|c| c.hash_join_split) + .unwrap_or_else(|| compute_hash_join_split(self.join_split.as_ref())); let hash_sapling_spends = if sapling { - compute_hash_sapling_spends(self.sapling.as_ref()) + cache.as_ref().map(|c| c.hash_sapling_spends) + .unwrap_or_else(|| compute_hash_sapling_spends(self.sapling.as_ref())) } else { - Default::default() + 0u8.into() }; let hash_sapling_outputs = if sapling { - compute_hash_sapling_outputs(self.sapling.as_ref()) + cache.as_ref().map(|c| c.hash_sapling_spends) + .unwrap_or_else(|| compute_hash_sapling_outputs(self.sapling.as_ref())) } else { - Default::default() + 0u8.into() }; + // update cache + *cache = Some(SighashCache { + hash_prevouts, + hash_sequence, + hash_outputs, + hash_join_split, + hash_sapling_spends, + hash_sapling_outputs, + }); + let mut personalization = [0u8; 16]; personalization[..12].copy_from_slice(b"ZcashSigHash"); LittleEndian::write_u32(&mut personalization[12..], consensus_branch_id); @@ -352,16 +371,24 @@ fn compute_hash_sequence(sighash: Sighash, inputs: &[UnsignedTransactionInput]) } } -fn compute_hash_outputs(sighash: Sighash, input_index: Option, outputs: &[TransactionOutput]) -> H256 { +fn compute_hash_outputs( + cache: &mut Option, + sighash: Sighash, + input_index: Option, + outputs: &[TransactionOutput] +) -> H256 { const PERSONALIZATION: &'static [u8; 16] = b"ZcashOutputsHash"; match (sighash.base, input_index) { (SighashBase::All, _) => { - let mut stream = Stream::default(); - for output in outputs { - stream.append(output); - } - blake2b_personal(PERSONALIZATION, &stream.out()) + cache.as_ref().map(|c| c.hash_outputs) + .unwrap_or_else(|| { + let mut stream = Stream::default(); + for output in outputs { + stream.append(output); + } + blake2b_personal(PERSONALIZATION, &stream.out()) + }) }, (SighashBase::Single, Some(input_index)) if input_index < outputs.len() => { let mut stream = Stream::default(); @@ -479,7 +506,8 @@ mod tests { sapling: None, }; - let hash = input_signer.signature_hash(Some(0), 0, &previous_output, SighashBase::All.into(), 0); + let mut cache = None; + let hash = input_signer.signature_hash(&mut cache, Some(0), 0, &previous_output, SighashBase::All.into(), 0); assert_eq!(hash, expected_signature_hash); } @@ -508,8 +536,9 @@ mod tests { let expected: H256 = result.parse().unwrap(); let expected = expected.reversed(); + let mut cache = None; let input_index = if input_index as u64 == ::std::u64::MAX { None } else { Some(input_index) }; - let hash = signer.signature_hash(input_index, 0, &script, hash_type as u32, consensus_branch_id); + let hash = signer.signature_hash(&mut cache, input_index, 0, &script, hash_type as u32, consensus_branch_id); if expected != hash { panic!("Test#{} of {:?} sighash failed: expected {}, got {}", idx, signer.signature_version(), expected, hash); } else { diff --git a/script/src/verify.rs b/script/src/verify.rs index 1679e74f..b0271882 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -3,7 +3,7 @@ use chain::constants::{ SEQUENCE_FINAL, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_MASK, SEQUENCE_LOCKTIME_TYPE_FLAG, LOCKTIME_THRESHOLD }; -use {Script, TransactionInputSigner, Num}; +use {Script, SighashCache, TransactionInputSigner, Num}; /// Checks transaction signature pub trait SignatureChecker { @@ -15,7 +15,7 @@ pub trait SignatureChecker { ) -> bool; fn check_signature( - &self, + &mut self, signature: &Signature, public: &Public, script_code: &Script, @@ -34,7 +34,7 @@ impl SignatureChecker for NoopSignatureChecker { public.verify(hash, signature).unwrap_or(false) } - fn check_signature(&self, _: &Signature, _: &Public, _: &Script, _: u32) -> bool { + fn check_signature(&mut self, _: &Signature, _: &Public, _: &Script, _: u32) -> bool { false } @@ -53,6 +53,7 @@ pub struct TransactionSignatureChecker { pub input_index: usize, pub input_amount: u64, pub consensus_branch_id: u32, + pub cache: Option, } impl SignatureChecker for TransactionSignatureChecker { @@ -66,13 +67,14 @@ impl SignatureChecker for TransactionSignatureChecker { } fn check_signature( - &self, + &mut self, signature: &Signature, public: &Public, script_code: &Script, sighashtype: u32, ) -> bool { let hash = self.signer.signature_hash( + &mut self.cache, Some(self.input_index), self.input_amount, script_code, diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index 0b5c8107..a44e2cfe 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -358,6 +358,7 @@ impl<'a> TransactionEval<'a> { input_index: 0, input_amount: 0, consensus_branch_id: self.consensus_branch_id, + cache: None, }; for (index, input) in self.transaction.raw.inputs.iter().enumerate() { @@ -390,7 +391,7 @@ impl<'a> TransactionEval<'a> { .verify_sigpushonly(self.verify_sigpushonly) .verify_cleanstack(self.verify_cleanstack); - verify_script(&input, &output, &flags, &checker) + verify_script(&input, &output, &flags, &mut checker) .map_err(|e| TransactionError::Signature(index, e))?; } @@ -560,15 +561,16 @@ mod tests { let signer: TransactionInputSigner = spending_tx.into(); - let checker = TransactionSignatureChecker { + let mut checker = TransactionSignatureChecker { signer: signer, input_index: 0, input_amount: 0, consensus_branch_id: 0, + cache: None, }; let flags = VerificationFlags::default() .verify_p2sh(true); - assert_eq!(verify_script(&input_script, &output_script, &flags, &checker), Ok(())); + assert_eq!(verify_script(&input_script, &output_script, &flags, &mut checker), Ok(())); } }