diff --git a/benches/banking_stage.rs b/benches/banking_stage.rs index 94272ec83..bde337078 100644 --- a/benches/banking_stage.rs +++ b/benches/banking_stage.rs @@ -66,7 +66,7 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) { let sig: Vec = (0..64).map(|_| thread_rng().gen()).collect(); new.account_keys[0] = Pubkey::new(&from[0..32]); new.account_keys[1] = Pubkey::new(&to[0..32]); - new.signature = Signature::new(&sig[0..64]); + new.signatures = vec![Signature::new(&sig[0..64])]; new }).collect(); // fund all the accounts @@ -128,7 +128,7 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) { #[bench] fn bench_banking_stage_multi_programs(bencher: &mut Bencher) { - let progs = 5; + let progs = 4; let txes = 1000 * NUM_THREADS; let mint_total = 1_000_000_000_000; let mint = Mint::new(mint_total); @@ -168,7 +168,7 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) { ); } assert_eq!(new.instructions.len(), progs); - new.signature = Signature::new(&sig[0..64]); + new.signatures = vec![Signature::new(&sig[0..64])]; new }).collect(); transactions.iter().for_each(|tx| { diff --git a/src/bank.rs b/src/bank.rs index 5a258081b..dfc51b219 100644 --- a/src/bank.rs +++ b/src/bank.rs @@ -484,7 +484,7 @@ impl Bank { for (i, tx) in txs.iter().enumerate() { Self::update_signature_status_with_last_id( &mut last_ids.entries, - &tx.signature, + &tx.signatures[0], &res[i], &tx.last_id, ); @@ -498,7 +498,7 @@ impl Bank { Err(_) => RpcSignatureStatus::GenericFailure, }; if status != RpcSignatureStatus::SignatureNotFound { - self.check_signature_subscriptions(&tx.signature, status); + self.check_signature_subscriptions(&tx.signatures[0], status); } } } @@ -651,7 +651,8 @@ impl Bank { // There is no way to predict what contract will execute without an error // If a fee can pay for execution then the contract will be scheduled - let err = Self::reserve_signature_with_last_id(last_ids, &tx.last_id, &tx.signature); + let err = + Self::reserve_signature_with_last_id(last_ids, &tx.last_id, &tx.signatures[0]); if let Err(BankError::LastIdNotFound) = err { error_counters.reserve_last_id += 1; } else if let Err(BankError::DuplicateSignature) = err { @@ -1292,7 +1293,7 @@ impl Bank { last_id: Hash, ) -> Result { let tx = Transaction::system_new(keypair, to, n, last_id); - let signature = tx.signature; + let signature = tx.signatures[0]; self.process_transaction(&tx).map(|_| signature) } @@ -1550,10 +1551,13 @@ mod tests { assert_eq!(bank.get_balance(&mint.pubkey()), 0); assert_eq!(bank.get_balance(&key1), 1); assert_eq!(bank.get_balance(&key2), 0); - assert_eq!(bank.get_signature(&t1.last_id, &t1.signature), Some(Ok(()))); + assert_eq!( + bank.get_signature(&t1.last_id, &t1.signatures[0]), + Some(Ok(())) + ); // TODO: Transactions that fail to pay a fee could be dropped silently assert_eq!( - bank.get_signature(&t2.last_id, &t2.signature), + bank.get_signature(&t2.last_id, &t2.signatures[0]), Some(Err(BankError::AccountInUse)) ); } @@ -1579,7 +1583,7 @@ mod tests { ]; let t1 = Transaction::new_with_instructions( - &mint.keypair(), + &[&mint.keypair()], &[key1, key2], mint.last_id(), 0, @@ -1593,7 +1597,7 @@ mod tests { assert_eq!(bank.get_balance(&key1), 0); assert_eq!(bank.get_balance(&key2), 0); assert_eq!( - bank.get_signature(&t1.last_id, &t1.signature), + bank.get_signature(&t1.last_id, &t1.signatures[0]), Some(Err(BankError::ResultWithNegativeTokens(1))) ); } @@ -1616,7 +1620,10 @@ mod tests { assert_eq!(bank.get_balance(&mint.pubkey()), 0); assert_eq!(bank.get_balance(&key1), 1); assert_eq!(bank.get_balance(&key2), 1); - assert_eq!(bank.get_signature(&t1.last_id, &t1.signature), Some(Ok(()))); + assert_eq!( + bank.get_signature(&t1.last_id, &t1.signatures[0]), + Some(Ok(())) + ); } // TODO: This test demonstrates that fees are not paid when a program fails. @@ -1637,7 +1644,7 @@ mod tests { Pubkey::default(), 1, ); - let signature = tx.signature; + let signature = tx.signatures[0]; assert!(!bank.has_signature(&signature)); let res = bank.process_transaction(&tx); @@ -2044,7 +2051,7 @@ mod tests { let bank_sub_id = Keypair::new().pubkey(); let last_id = bank.last_id(); let tx = Transaction::system_move(&mint.keypair(), alice.pubkey(), 20, last_id, 0); - let signature = tx.signature; + let signature = tx.signatures[0]; bank.process_transaction(&tx).unwrap(); let (subscriber, _id_receiver, mut transport_receiver) = diff --git a/src/bin/bench-tps.rs b/src/bin/bench-tps.rs index 192f2ff5c..7f16e8d54 100644 --- a/src/bin/bench-tps.rs +++ b/src/bin/bench-tps.rs @@ -293,7 +293,7 @@ fn do_tx_transfers( const MAX_SPENDS_PER_TX: usize = 5; fn verify_transfer(client: &mut ThinClient, tx: &Transaction) -> bool { - if client.poll_for_signature(&tx.signature).is_err() { + if client.poll_for_signature(&tx.signatures[0]).is_err() { println!("no signature"); return false; } diff --git a/src/budget_transaction.rs b/src/budget_transaction.rs index 0d6e78181..09a925657 100644 --- a/src/budget_transaction.rs +++ b/src/budget_transaction.rs @@ -92,7 +92,14 @@ impl BudgetTransaction for Transaction { transaction::Instruction::new(1, &budget_instruction, vec![1]), ]; - Self::new_with_instructions(from_keypair, &keys, last_id, fee, program_ids, instructions) + Self::new_with_instructions( + &[from_keypair], + &keys, + last_id, + fee, + program_ids, + instructions, + ) } /// Create and sign a new Transaction. Used for unit-testing. @@ -258,9 +265,10 @@ mod tests { let instruction = Instruction::NewBudget(expr); let instructions = vec![transaction::Instruction::new(0, &instruction, vec![])]; let claim0 = Transaction { + signed_keys: vec![], account_keys: vec![], last_id: Default::default(), - signature: Default::default(), + signatures: vec![], program_ids: vec![], instructions, fee: 0, diff --git a/src/rpc.rs b/src/rpc.rs index 42c5fee15..0f92d36ac 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -251,7 +251,7 @@ impl RpcSol for RpcSolImpl { info!("send_transaction: send_to error: {:?}", err); Error::internal_error() })?; - let signature = bs58::encode(tx.signature).into_string(); + let signature = bs58::encode(tx.signatures[0]).into_string(); trace!( "send_transaction: sent {} bytes, signature={}", data.len(), @@ -504,7 +504,7 @@ mod tests { let req = format!( r#"{{"jsonrpc":"2.0","id":1,"method":"confirmTransaction","params":["{}"]}}"#, - tx.signature + tx.signatures[0] ); let res = io.handle_request_sync(&req, meta); let expected = format!(r#"{{"jsonrpc":"2.0","result":true,"id":1}}"#); @@ -523,7 +523,7 @@ mod tests { let req = format!( r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":["{}"]}}"#, - tx.signature + tx.signatures[0] ); let res = io.handle_request_sync(&req, meta.clone()); let expected = format!(r#"{{"jsonrpc":"2.0","result":"Confirmed","id":1}}"#); @@ -537,7 +537,7 @@ mod tests { let tx = Transaction::system_move(&alice_keypair, bob_pubkey, 10, last_id, 0); let req = format!( r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":["{}"]}}"#, - tx.signature + tx.signatures[0] ); let res = io.handle_request_sync(&req, meta); let expected = format!(r#"{{"jsonrpc":"2.0","result":"SignatureNotFound","id":1}}"#); @@ -738,8 +738,8 @@ mod tests { let tx = Transaction::system_move(&Keypair::new(), Keypair::new().pubkey(), 20, hash(&[0]), 0); assert_eq!( - verify_signature(&tx.signature.to_string()).unwrap(), - tx.signature + verify_signature(&tx.signatures[0].to_string()).unwrap(), + tx.signatures[0] ); let bad_signature = "a1b2c3d4"; assert_eq!( diff --git a/src/rpc_pubsub.rs b/src/rpc_pubsub.rs index fd270570e..1bf4bfc86 100644 --- a/src/rpc_pubsub.rs +++ b/src/rpc_pubsub.rs @@ -290,7 +290,7 @@ mod tests { let req = format!( r#"{{"jsonrpc":"2.0","id":1,"method":"signatureSubscribe","params":["{}"]}}"#, - tx.signature.to_string() + tx.signatures[0].to_string() ); let res = io.handle_request_sync(&req, session.clone()); let expected = format!(r#"{{"jsonrpc":"2.0","result":0,"id":1}}"#); @@ -331,7 +331,7 @@ mod tests { let tx = Transaction::system_move(&alice.keypair(), bob_pubkey, 10, last_id, 0); let req = format!( r#"{{"jsonrpc":"2.0","id":1,"method":"signatureSubscribe","params":["{}"]}}"#, - tx.signature.to_string() + tx.signatures[0].to_string() ); let res = io.handle_request_sync(&req, session.clone()); let expected = format!(r#"{{"jsonrpc":"2.0","result":1,"id":1}}"#); @@ -364,7 +364,7 @@ mod tests { let tx = Transaction::system_move(&alice.keypair(), bob_pubkey, 20, last_id, 0); let req = format!( r#"{{"jsonrpc":"2.0","id":1,"method":"signatureSubscribe","params":["{}"]}}"#, - tx.signature.to_string() + tx.signatures[0].to_string() ); let _res = io.handle_request_sync(&req, session.clone()); diff --git a/src/sigverify.rs b/src/sigverify.rs index 2ab5618f1..1d73b61a7 100644 --- a/src/sigverify.rs +++ b/src/sigverify.rs @@ -4,15 +4,23 @@ //! offloaded to the GPU. //! +use byteorder::{LittleEndian, ReadBytesExt}; use counter::Counter; use log::Level; use packet::{Packet, SharedPackets}; +use result::Result; +use signature::Signature; +use solana_sdk::pubkey::Pubkey; +use std::io; use std::mem::size_of; use std::sync::atomic::AtomicUsize; -use transaction::{PUB_KEY_OFFSET, SIGNED_DATA_OFFSET, SIG_OFFSET}; +#[cfg(test)] +use transaction::Transaction; pub const TX_OFFSET: usize = 0; +type TxOffsets = (Vec, Vec, Vec, Vec, Vec>); + #[cfg(feature = "cuda")] #[repr(C)] struct Elems { @@ -29,10 +37,12 @@ extern "C" { vecs: *const Elems, num: u32, //number of vecs message_size: u32, //size of each element inside the elems field of the vec - pubkey_offset: u32, - signature_offset: u32, - signed_message_offset: u32, - signed_message_len_offset: u32, + total_packets: u32, + total_signatures: u32, + message_lens: *const u32, + pubkey_offsets: *const u32, + signature_offsets: *const u32, + signed_message_offsets: *const u32, out: *mut u8, //combined length of all the items in vecs ) -> u32; @@ -64,23 +74,37 @@ fn verify_packet(packet: &Packet) -> u8 { use solana_sdk::pubkey::Pubkey; use untrusted; - let msg_start = TX_OFFSET + SIGNED_DATA_OFFSET; - let sig_start = TX_OFFSET + SIG_OFFSET; - let sig_end = sig_start + size_of::(); - let pubkey_start = TX_OFFSET + PUB_KEY_OFFSET; - let pubkey_end = pubkey_start + size_of::(); + let (sig_len, sig_start, msg_start, pubkey_start) = get_packet_offsets(packet, 0); + let mut sig_start = sig_start as usize; + let mut pubkey_start = pubkey_start as usize; + let msg_start = msg_start as usize; if packet.meta.size <= msg_start { return 0; } let msg_end = packet.meta.size; - signature::verify( - &signature::ED25519, - untrusted::Input::from(&packet.data[pubkey_start..pubkey_end]), - untrusted::Input::from(&packet.data[msg_start..msg_end]), - untrusted::Input::from(&packet.data[sig_start..sig_end]), - ).is_ok() as u8 + for _ in 0..sig_len { + let pubkey_end = pubkey_start as usize + size_of::(); + let sig_end = sig_start as usize + size_of::(); + + if pubkey_end >= packet.meta.size || sig_end >= packet.meta.size { + return 0; + } + + if signature::verify( + &signature::ED25519, + untrusted::Input::from(&packet.data[pubkey_start..pubkey_end]), + untrusted::Input::from(&packet.data[msg_start..msg_end]), + untrusted::Input::from(&packet.data[sig_start..sig_end]), + ).is_err() + { + return 0; + } + pubkey_start += size_of::(); + sig_start += size_of::(); + } + 1 } fn verify_packet_disabled(_packet: &Packet) -> u8 { @@ -100,6 +124,64 @@ pub fn ed25519_verify(batches: &[SharedPackets]) -> Vec> { ed25519_verify_cpu(batches) } +pub fn get_packet_offsets(packet: &Packet, current_offset: u32) -> (u32, u32, u32, u32) { + // Read in u64 as the size of signatures array + let mut rdr = io::Cursor::new(&packet.data[TX_OFFSET..size_of::()]); + let sig_len = rdr.read_u64::().unwrap() as u32; + + let msg_start_offset = + current_offset + size_of::() as u32 + sig_len * size_of::() as u32; + let pubkey_offset = msg_start_offset + size_of::() as u32; + + let sig_start = TX_OFFSET as u32 + size_of::() as u32; + + (sig_len, sig_start, msg_start_offset, pubkey_offset) +} + +pub fn generate_offsets(batches: &[SharedPackets]) -> Result { + let mut signature_offsets: Vec<_> = Vec::new(); + let mut pubkey_offsets: Vec<_> = Vec::new(); + let mut msg_start_offsets: Vec<_> = Vec::new(); + let mut msg_sizes: Vec<_> = Vec::new(); + let mut current_packet = 0; + let mut v_sig_lens = Vec::new(); + batches.into_iter().for_each(|p| { + let mut sig_lens = Vec::new(); + p.read().unwrap().packets.iter().for_each(|packet| { + let current_offset = current_packet as u32 * size_of::() as u32; + + let (sig_len, _sig_start, msg_start_offset, pubkey_offset) = + get_packet_offsets(packet, current_offset); + let mut pubkey_offset = pubkey_offset; + + sig_lens.push(sig_len); + + trace!("pubkey_offset: {}", pubkey_offset); + let mut sig_offset = current_offset + size_of::() as u32; + for _ in 0..sig_len { + signature_offsets.push(sig_offset); + sig_offset += size_of::() as u32; + + pubkey_offsets.push(pubkey_offset); + pubkey_offset += size_of::() as u32; + + msg_start_offsets.push(msg_start_offset); + + msg_sizes.push(current_offset + (packet.meta.size as u32) - msg_start_offset); + } + current_packet += 1; + }); + v_sig_lens.push(sig_lens); + }); + Ok(( + signature_offsets, + pubkey_offsets, + msg_start_offsets, + msg_sizes, + v_sig_lens, + )) +} + pub fn ed25519_verify_cpu(batches: &[SharedPackets]) -> Vec> { use rayon::prelude::*; let count = batch_size(batches); @@ -161,6 +243,9 @@ pub fn ed25519_verify(batches: &[SharedPackets]) -> Vec> { return ed25519_verify_cpu(batches); } + let (signature_offsets, pubkey_offsets, msg_start_offsets, msg_sizes, sig_lens) = + generate_offsets(batches).unwrap(); + info!("CUDA ECDSA for {}", batch_size(batches)); let mut out = Vec::new(); let mut elems = Vec::new(); @@ -170,7 +255,7 @@ pub fn ed25519_verify(batches: &[SharedPackets]) -> Vec> { for packets in batches { locks.push(packets.read().unwrap()); } - let mut num = 0; + let mut num_packets = 0; for p in locks { elems.push(Elems { elems: p.packets.as_ptr(), @@ -179,25 +264,24 @@ pub fn ed25519_verify(batches: &[SharedPackets]) -> Vec> { let mut v = Vec::new(); v.resize(p.packets.len(), 0); rvs.push(v); - num += p.packets.len(); + num_packets += p.packets.len(); } - out.resize(num, 0); - trace!("Starting verify num packets: {}", num); + out.resize(signature_offsets.len(), 0); + trace!("Starting verify num packets: {}", num_packets); trace!("elem len: {}", elems.len() as u32); trace!("packet sizeof: {}", size_of::() as u32); - trace!("pubkey: {}", (TX_OFFSET + PUB_KEY_OFFSET) as u32); - trace!("signature offset: {}", (TX_OFFSET + SIG_OFFSET) as u32); - trace!("sign data: {}", (TX_OFFSET + SIGNED_DATA_OFFSET) as u32); trace!("len offset: {}", PACKET_DATA_SIZE as u32); unsafe { let res = ed25519_verify_many( elems.as_ptr(), elems.len() as u32, size_of::() as u32, - (TX_OFFSET + PUB_KEY_OFFSET) as u32, - (TX_OFFSET + SIG_OFFSET) as u32, - (TX_OFFSET + SIGNED_DATA_OFFSET) as u32, - PACKET_DATA_SIZE as u32, + num_packets as u32, + signature_offsets.len() as u32, + msg_sizes.as_ptr(), + pubkey_offsets.as_ptr(), + signature_offsets.as_ptr(), + msg_start_offsets.as_ptr(), out.as_mut_ptr(), ); if res != 0 { @@ -206,25 +290,47 @@ pub fn ed25519_verify(batches: &[SharedPackets]) -> Vec> { } trace!("done verify"); let mut num = 0; - for vs in rvs.iter_mut() { - for mut v in vs.iter_mut() { - *v = out[num]; + for (vs, sig_vs) in rvs.iter_mut().zip(sig_lens.iter()) { + for (mut v, sig_v) in vs.iter_mut().zip(sig_vs.iter()) { + let mut vout = 1; + for _ in 0..*sig_v { + if 0 == out[num] { + vout = 0; + } + num += 1; + } + *v = vout; if *v != 0 { trace!("VERIFIED PACKET!!!!!"); } - num += 1; } } inc_new_counter_info!("ed25519_verify_gpu", count); rvs } +#[cfg(test)] +pub fn make_packet_from_transaction(tx: Transaction) -> Packet { + use bincode::serialize; + + let tx_bytes = serialize(&tx).unwrap(); + let mut packet = Packet::default(); + packet.meta.size = tx_bytes.len(); + packet.data[..packet.meta.size].copy_from_slice(&tx_bytes); + return packet; +} + #[cfg(test)] mod tests { use bincode::serialize; + use budget_program::BudgetState; + use hash::Hash; use packet::{Packet, SharedPackets}; + use signature::{Keypair, KeypairUtil}; use sigverify; + use system_program::SystemProgram; use system_transaction::{memfind, test_tx}; + use transaction; use transaction::Transaction; #[test] @@ -236,25 +342,25 @@ mod tests { assert_matches!(memfind(&packet, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), None); } - fn make_packet_from_transaction(tx: Transaction) -> Packet { - let tx_bytes = serialize(&tx).unwrap(); - let mut packet = Packet::default(); - packet.meta.size = tx_bytes.len(); - packet.data[..packet.meta.size].copy_from_slice(&tx_bytes); - return packet; + #[test] + fn test_get_packet_offsets() { + let tx = test_tx(); + let packet = sigverify::make_packet_from_transaction(tx); + let (sig_len, sig_start, msg_start_offset, pubkey_offset) = + sigverify::get_packet_offsets(&packet, 0); + assert_eq!(sig_len, 1); + assert_eq!(sig_start, 8); + assert_eq!(msg_start_offset, 72); + assert_eq!(pubkey_offset, 80); } - fn test_verify_n(n: usize, modify_data: bool) { - let tx = test_tx(); - let mut packet = make_packet_from_transaction(tx); - - // jumble some data to test failure - if modify_data { - packet.data[20] = packet.data[20].wrapping_add(10); - } - + fn generate_packet_vec( + packet: &Packet, + num_packets_per_batch: usize, + num_batches: usize, + ) -> Vec { // generate packet vector - let batches: Vec<_> = (0..2) + let batches: Vec<_> = (0..num_batches) .map(|_| { let packets = SharedPackets::default(); packets @@ -262,13 +368,27 @@ mod tests { .unwrap() .packets .resize(0, Default::default()); - for _ in 0..n { + for _ in 0..num_packets_per_batch { packets.write().unwrap().packets.push(packet.clone()); } - assert_eq!(packets.read().unwrap().packets.len(), n); + assert_eq!(packets.read().unwrap().packets.len(), num_packets_per_batch); packets }).collect(); - assert_eq!(batches.len(), 2); + assert_eq!(batches.len(), num_batches); + + batches + } + + fn test_verify_n(n: usize, modify_data: bool) { + let tx = test_tx(); + let mut packet = sigverify::make_packet_from_transaction(tx); + + // jumble some data to test failure + if modify_data { + packet.data[20] = packet.data[20].wrapping_add(10); + } + + let batches = generate_packet_vec(&packet, n, 2); // verify packets let ans = sigverify::ed25519_verify(&batches); @@ -293,6 +413,58 @@ mod tests { test_verify_n(71, false); } + #[test] + fn test_verify_multi_sig() { + use logger; + logger::setup(); + let keypair0 = Keypair::new(); + let keypair1 = Keypair::new(); + let keypairs = vec![&keypair0, &keypair1]; + let tokens = 5; + let fee = 2; + let last_id = Hash::default(); + + let keys = vec![keypair0.pubkey(), keypair1.pubkey()]; + + let system_instruction = SystemProgram::Move { tokens }; + + let program_ids = vec![SystemProgram::id(), BudgetState::id()]; + + let instructions = vec![transaction::Instruction::new( + 0, + &system_instruction, + vec![0, 1], + )]; + + let tx = Transaction::new_with_instructions( + &keypairs, + &keys, + last_id, + fee, + program_ids, + instructions, + ); + + let mut packet = sigverify::make_packet_from_transaction(tx); + + let n = 4; + let num_batches = 3; + let batches = generate_packet_vec(&packet, n, num_batches); + + packet.data[40] = packet.data[40].wrapping_add(8); + + batches[0].write().unwrap().packets.push(packet); + + // verify packets + let ans = sigverify::ed25519_verify(&batches); + + // check result + let ref_ans = 1u8; + let mut ref_vec = vec![vec![ref_ans; n]; num_batches]; + ref_vec[0].push(0u8); + assert_eq!(ans, ref_vec); + } + #[test] fn test_verify_fail() { test_verify_n(5, true); diff --git a/src/storage_stage.rs b/src/storage_stage.rs index 7cb2f6a12..db6f1be29 100644 --- a/src/storage_stage.rs +++ b/src/storage_stage.rs @@ -222,7 +222,7 @@ impl StorageStage { ); let mut storage_keys = storage_keys.write().unwrap(); storage_keys[*current_key_idx..*current_key_idx + size_of::()] - .copy_from_slice(tx.signature.as_ref()); + .copy_from_slice(tx.signatures[0].as_ref()); *current_key_idx += size_of::(); *current_key_idx %= storage_keys.len(); } diff --git a/src/system_transaction.rs b/src/system_transaction.rs index fdb3ec3a6..63216726f 100644 --- a/src/system_transaction.rs +++ b/src/system_transaction.rs @@ -110,7 +110,7 @@ impl SystemTransaction for Transaction { let to_keys: Vec<_> = moves.iter().map(|(to_key, _)| *to_key).collect(); Transaction::new_with_instructions( - from, + &[from], &to_keys, last_id, fee, @@ -149,19 +149,36 @@ mod tests { use super::*; use bincode::{deserialize, serialize}; use packet::PACKET_DATA_SIZE; - use transaction::{PUB_KEY_OFFSET, SIGNED_DATA_OFFSET, SIG_OFFSET}; + use sigverify; + use transaction::SIG_OFFSET; #[test] fn test_layout() { let tx = test_tx(); - let sign_data = tx.get_sign_data(); let tx_bytes = serialize(&tx).unwrap(); - assert_eq!(memfind(&tx_bytes, &sign_data), Some(SIGNED_DATA_OFFSET)); - assert_eq!(memfind(&tx_bytes, &tx.signature.as_ref()), Some(SIG_OFFSET)); + let sign_data = tx.get_sign_data(); + let packet = sigverify::make_packet_from_transaction(tx.clone()); + + let (sig_len, sig_start, msg_start_offset, pubkey_offset) = + sigverify::get_packet_offsets(&packet, 0); + + assert_eq!( + memfind(&tx_bytes, &tx.signatures[0].as_ref()), + Some(SIG_OFFSET) + ); assert_eq!( memfind(&tx_bytes, &tx.account_keys[0].as_ref()), - Some(PUB_KEY_OFFSET) + Some(pubkey_offset as usize) ); + assert_eq!( + memfind(&tx_bytes, &sign_data), + Some(msg_start_offset as usize) + ); + assert_eq!( + memfind(&tx_bytes, &tx.signatures[0].as_ref()), + Some(sig_start as usize) + ); + assert_eq!(sig_len, 1); assert!(tx.verify_signature()); } @@ -172,15 +189,10 @@ mod tests { let sign_data0a = tx0.get_sign_data(); let tx_bytes = serialize(&tx0).unwrap(); assert!(tx_bytes.len() < PACKET_DATA_SIZE); - assert_eq!(memfind(&tx_bytes, &sign_data0a), Some(SIGNED_DATA_OFFSET)); assert_eq!( - memfind(&tx_bytes, &tx0.signature.as_ref()), + memfind(&tx_bytes, &tx0.signatures[0].as_ref()), Some(SIG_OFFSET) ); - assert_eq!( - memfind(&tx_bytes, &tx0.account_keys[0].as_ref()), - Some(PUB_KEY_OFFSET) - ); let tx1 = deserialize(&tx_bytes).unwrap(); assert_eq!(tx0, tx1); assert_eq!(tx1.instructions[0].userdata, vec![1, 2, 3]); diff --git a/src/thin_client.rs b/src/thin_client.rs index 812818133..433e952a9 100644 --- a/src/thin_client.rs +++ b/src/thin_client.rs @@ -71,7 +71,7 @@ impl ThinClient { let data = serialize(&tx).expect("serialize Transaction in pub fn transfer_signed"); self.transactions_socket .send_to(&data, &self.transactions_addr)?; - Ok(tx.signature) + Ok(tx.signatures[0]) } /// Retry a sending a signed Transaction to the server for processing. @@ -82,12 +82,12 @@ impl ThinClient { tries: usize, ) -> io::Result { for x in 0..tries { - tx.sign(&keypair, self.get_last_id()); + tx.sign(&[&keypair], self.get_last_id()); let data = serialize(&tx).expect("serialize Transaction in pub fn transfer_signed"); self.transactions_socket .send_to(&data, &self.transactions_addr)?; - if self.poll_for_signature(&tx.signature).is_ok() { - return Ok(tx.signature); + if self.poll_for_signature(&tx.signatures[0]).is_ok() { + return Ok(tx.signatures[0]); } info!("{} tries failed transfer to {}", x, self.transactions_addr); } diff --git a/src/transaction.rs b/src/transaction.rs index beda8a40b..e15253aee 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -7,9 +7,7 @@ use signature::{Keypair, KeypairUtil, Signature}; use solana_sdk::pubkey::Pubkey; use std::mem::size_of; -pub const SIGNED_DATA_OFFSET: usize = size_of::(); -pub const SIG_OFFSET: usize = 0; -pub const PUB_KEY_OFFSET: usize = size_of::() + size_of::(); +pub const SIG_OFFSET: usize = size_of::(); /// An instruction to execute a program under the `program_id` of `program_ids_index` with the /// specified accounts and userdata @@ -38,8 +36,11 @@ impl Instruction { /// An atomic transaction #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Transaction { - /// A digital signature of `account_keys`, `program_ids`, `last_id`, `fee` and `instructions`, signed by `Pubkey`. - pub signature: Signature, + /// A set of digital signature of `account_keys`, `program_ids`, `last_id`, `fee` and `instructions`, signed by `signed_keys`. + pub signatures: Vec, + + /// The `Pubkeys` that correspond to signature in `signatures` + pub signed_keys: Vec, /// The `Pubkeys` that are executing this transaction userdata. The meaning of each key is /// program-specific. @@ -74,7 +75,7 @@ impl Transaction { let accounts = (0..=transaction_keys.len() as u8).collect(); let instructions = vec![Instruction::new(0, userdata, accounts)]; Self::new_with_instructions( - from_keypair, + &[from_keypair], transaction_keys, last_id, fee, @@ -91,25 +92,31 @@ impl Transaction { /// * `program_ids` - The keys that identify programs used in the `instruction` vector. /// * `instructions` - The programs and their arguments that the transaction will execute atomically pub fn new_with_instructions( - from_keypair: &Keypair, + from_keypairs: &[&Keypair], keys: &[Pubkey], last_id: Hash, fee: u64, program_ids: Vec, instructions: Vec, ) -> Self { - let from = from_keypair.pubkey(); - let mut account_keys = vec![from]; + let from = from_keypairs[0].pubkey(); + let mut account_keys = Vec::with_capacity(keys.len() + 1); + account_keys.push(from); account_keys.extend_from_slice(keys); + let signed_keys = from_keypairs + .iter() + .map(|keypair| keypair.pubkey()) + .collect(); let mut tx = Transaction { - signature: Signature::default(), + signatures: vec![], + signed_keys, account_keys, last_id: Hash::default(), fee, program_ids, instructions, }; - tx.sign(from_keypair, last_id); + tx.sign(from_keypairs, last_id); tx } pub fn userdata(&self, instruction_index: usize) -> &[u8] { @@ -138,7 +145,10 @@ impl Transaction { } /// Get the transaction data to sign. pub fn get_sign_data(&self) -> Vec { - let mut data = serialize(&self.account_keys).expect("serialize account_keys"); + let mut data = serialize(&self.signed_keys).expect("serialize signed keys"); + + let account_keys_data = serialize(&self.account_keys).expect("serialize account_keys"); + data.extend_from_slice(&account_keys_data); let last_id_data = serialize(&self.last_id).expect("serialize last_id"); data.extend_from_slice(&last_id_data); @@ -155,17 +165,21 @@ impl Transaction { } /// Sign this transaction. - pub fn sign(&mut self, keypair: &Keypair, last_id: Hash) { + pub fn sign(&mut self, keypairs: &[&Keypair], last_id: Hash) { self.last_id = last_id; let sign_data = self.get_sign_data(); - self.signature = Signature::new(keypair.sign(&sign_data).as_ref()); + self.signatures = keypairs + .iter() + .map(|keypair| Signature::new(&keypair.sign(&sign_data).as_ref())) + .collect(); } /// Verify only the transaction signature. pub fn verify_signature(&self) -> bool { warn!("transaction signature verification called"); - self.signature - .verify(&self.from().as_ref(), &self.get_sign_data()) + self.signatures + .iter() + .all(|s| s.verify(&self.from().as_ref(), &self.get_sign_data())) } /// Verify that references in the instructions are valid @@ -192,7 +206,7 @@ impl Transaction { let mut hasher = Hasher::default(); transactions .iter() - .for_each(|tx| hasher.hash(&tx.signature.as_ref())); + .for_each(|tx| hasher.hash(&tx.signatures[0].as_ref())); hasher.result() } } @@ -215,7 +229,7 @@ mod tests { Instruction::new(1, &(), vec![0, 2]), ]; let tx = Transaction::new_with_instructions( - &key, + &[&key], &[key1, key2], Default::default(), 0, @@ -250,7 +264,7 @@ mod tests { let key = Keypair::new(); let instructions = vec![Instruction::new(1, &(), vec![])]; let tx = Transaction::new_with_instructions( - &key, + &[&key], &[], Default::default(), 0, @@ -264,7 +278,7 @@ mod tests { let key = Keypair::new(); let instructions = vec![Instruction::new(0, &(), vec![1])]; let tx = Transaction::new_with_instructions( - &key, + &[&key], &[], Default::default(), 0, @@ -301,19 +315,22 @@ mod tests { assert_eq!( serialize(&tx).unwrap(), vec![ - 234, 139, 34, 5, 120, 28, 107, 203, 69, 25, 236, 200, 164, 1, 12, 47, 147, 53, 41, - 143, 23, 116, 230, 203, 59, 228, 153, 14, 22, 241, 103, 226, 186, 169, 181, 65, 49, - 215, 44, 2, 61, 214, 113, 216, 184, 206, 147, 104, 140, 225, 138, 21, 172, 135, - 211, 80, 103, 80, 216, 106, 249, 86, 194, 1, 3, 0, 0, 0, 0, 0, 0, 0, 32, 253, 186, - 201, 177, 11, 117, 135, 187, 167, 181, 188, 22, 59, 206, 105, 231, 150, 215, 30, - 78, 212, 76, 16, 252, 180, 72, 134, 137, 247, 161, 68, 32, 253, 186, 201, 177, 11, - 117, 135, 187, 167, 181, 188, 22, 59, 206, 105, 231, 150, 215, 30, 78, 212, 76, 16, - 252, 180, 72, 134, 137, 247, 161, 68, 1, 1, 1, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3 + 1, 0, 0, 0, 0, 0, 0, 0, 199, 111, 28, 46, 13, 235, 122, 203, 210, 118, 158, 0, 59, + 175, 148, 175, 225, 134, 90, 155, 188, 232, 218, 54, 199, 116, 67, 99, 65, 84, 164, + 94, 224, 60, 187, 249, 92, 210, 242, 99, 58, 121, 202, 5, 140, 152, 14, 166, 13, + 134, 103, 127, 216, 96, 217, 139, 5, 252, 40, 146, 48, 112, 59, 7, 1, 0, 0, 0, 0, + 0, 0, 0, 32, 253, 186, 201, 177, 11, 117, 135, 187, 167, 181, 188, 22, 59, 206, + 105, 231, 150, 215, 30, 78, 212, 76, 16, 252, 180, 72, 134, 137, 247, 161, 68, 3, + 0, 0, 0, 0, 0, 0, 0, 32, 253, 186, 201, 177, 11, 117, 135, 187, 167, 181, 188, 22, + 59, 206, 105, 231, 150, 215, 30, 78, 212, 76, 16, 252, 180, 72, 134, 137, 247, 161, + 68, 32, 253, 186, 201, 177, 11, 117, 135, 187, 167, 181, 188, 22, 59, 206, 105, + 231, 150, 215, 30, 78, 212, 76, 16, 252, 180, 72, 134, 137, 247, 161, 68, 1, 1, 1, + 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 99, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 4, 5, 6, + 7, 8, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4, 2, 2, 2, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, + 3 ], ); } diff --git a/tests/programs.rs b/tests/programs.rs index c67e59c1c..317278068 100644 --- a/tests/programs.rs +++ b/tests/programs.rs @@ -43,7 +43,10 @@ fn create_bpf_path(name: &str) -> PathBuf { fn check_tx_results(bank: &Bank, tx: &Transaction, result: Vec>) { assert_eq!(result.len(), 1); assert_eq!(result[0], Ok(())); - assert_eq!(bank.get_signature(&tx.last_id, &tx.signature), Some(Ok(()))); + assert_eq!( + bank.get_signature(&tx.last_id, &tx.signatures[0]), + Some(Ok(())) + ); } struct Loader {