From 431cc82032989fdbcd91d52281082b7dad2fe8b9 Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Fri, 17 May 2019 18:55:57 -0700 Subject: [PATCH] add Transaction::partial_sign() (#4333) * add partial sign * nits --- ci/nits.sh | 6 ++-- sdk/src/transaction.rs | 75 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/ci/nits.sh b/ci/nits.sh index d84e309a9e..a2277d2811 100755 --- a/ci/nits.sh +++ b/ci/nits.sh @@ -30,7 +30,7 @@ declare print_free_tree=( 'programs/stake_program/src' ) -if _ git grep -n --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then +if _ git --no-pager grep -n --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then exit 1 fi @@ -39,14 +39,14 @@ fi # Default::default() # # Ref: https://github.com/solana-labs/solana/issues/2630 -if _ git grep -n 'Default::default()' -- '*.rs'; then +if _ git --no-pager grep -n 'Default::default()' -- '*.rs'; then exit 1 fi # Let's keep a .gitignore for every crate, ensure it's got # /target/ in it declare gitignores_ok=true -for i in $(git ls-files \*/Cargo.toml ); do +for i in $(git --no-pager ls-files \*/Cargo.toml ); do dir=$(dirname "$i") if [[ ! -f $dir/.gitignore ]]; then echo 'error: nits.sh .gitnore missing for crate '"$dir" >&2 diff --git a/sdk/src/transaction.rs b/sdk/src/transaction.rs index 06b8833e04..3d256f5ff2 100644 --- a/sdk/src/transaction.rs +++ b/sdk/src/transaction.rs @@ -67,7 +67,7 @@ pub struct Transaction { impl Transaction { pub fn new_unsigned(message: Message) -> Self { Self { - signatures: Vec::with_capacity(message.num_required_signatures as usize), + signatures: vec![Signature::default(); message.num_required_signatures as usize], message, } } @@ -180,10 +180,40 @@ impl Transaction { assert_eq!(keypair.pubkey(), signed_keys[i], "keypair-pubkey mismatch"); } assert_eq!(keypairs.len(), signed_keys.len(), "not enough keypairs"); - self.sign_unchecked(keypairs, recent_blockhash); } + /// Sign using some subset of required keys + /// if recent_blockhash is not the same as currently in the transaction, + /// clear any prior signatures and update recent_blockhash + pub fn partial_sign(&mut self, keypairs: &[&T], recent_blockhash: Hash) { + let signed_keys = + &self.message.account_keys[0..self.message.num_required_signatures as usize]; + + // if you change the blockhash, you're re-signing... + if recent_blockhash != self.message.recent_blockhash { + self.message.recent_blockhash = recent_blockhash; + self.signatures + .iter_mut() + .for_each(|signature| *signature = Signature::default()); + } + + for keypair in keypairs { + let i = signed_keys + .iter() + .position(|pubkey| pubkey == &keypair.pubkey()) + .expect("keypair-pubkey mismatch"); + + self.signatures[i] = keypair.sign_message(&self.message_data()) + } + } + + pub fn is_signed(&self) -> bool { + self.signatures + .iter() + .all(|signature| *signature != Signature::default()) + } + /// Verify that references in the instructions are valid pub fn verify_refs(&self) -> bool { let message = self.message(); @@ -204,6 +234,7 @@ impl Transaction { #[cfg(test)] mod tests { use super::*; + use crate::hash::hash; use crate::instruction::AccountMeta; use crate::signature::Keypair; use crate::system_instruction; @@ -397,6 +428,45 @@ mod tests { Transaction::new_unsigned_instructions(vec![]).sign(&[&keypair], Hash::default()); } + #[test] + #[should_panic] + fn test_partial_sign_mismatched_key() { + let keypair = Keypair::new(); + Transaction::new_unsigned_instructions(vec![Instruction::new( + Pubkey::default(), + &0, + vec![AccountMeta::new(Pubkey::new_rand(), true)], + )]) + .partial_sign(&[&keypair], Hash::default()); + } + + #[test] + fn test_partial_sign() { + let keypair0 = Keypair::new(); + let keypair1 = Keypair::new(); + let keypair2 = Keypair::new(); + let mut tx = Transaction::new_unsigned_instructions(vec![Instruction::new( + Pubkey::default(), + &0, + vec![ + AccountMeta::new(keypair0.pubkey(), true), + AccountMeta::new(keypair1.pubkey(), true), + AccountMeta::new(keypair2.pubkey(), true), + ], + )]); + + tx.partial_sign(&[&keypair0, &keypair2], Hash::default()); + assert!(!tx.is_signed()); + tx.partial_sign(&[&keypair1], Hash::default()); + assert!(tx.is_signed()); + + let hash = hash(&[1]); + tx.partial_sign(&[&keypair1], hash); + assert!(!tx.is_signed()); + tx.partial_sign(&[&keypair0, &keypair2], hash); + assert!(tx.is_signed()); + } + #[test] #[should_panic] fn test_transaction_missing_keypair() { @@ -430,5 +500,6 @@ mod tests { tx.message.instructions[0], CompiledInstruction::new(0, &0, vec![0]) ); + assert!(tx.is_signed()); } }