Support transaction signing by heterogenous lists of keypairs (#8342)

automerge
This commit is contained in:
Greg Fitzgerald
2020-02-20 13:13:23 -07:00
committed by GitHub
parent 1720fe6a46
commit e8124324ff
16 changed files with 196 additions and 81 deletions

View File

@ -17,6 +17,7 @@ use crate::{
message::Message,
pubkey::Pubkey,
signature::{Keypair, Signature},
signers::Signers,
transaction,
transport::Result,
};
@ -29,7 +30,7 @@ pub trait Client: SyncClient + AsyncClient {
pub trait SyncClient {
/// Create a transaction from the given message, and send it to the
/// server, retrying as-needed.
fn send_message(&self, keypairs: &[&Keypair], message: Message) -> Result<Signature>;
fn send_message<T: Signers>(&self, keypairs: &T, message: Message) -> Result<Signature>;
/// Create a transaction from a single instruction that only requires
/// a single signer. Then send it to the server, retrying as-needed.
@ -121,9 +122,9 @@ pub trait AsyncClient {
/// Create a transaction from the given message, and send it to the
/// server, but don't wait for to see if the server accepted it.
fn async_send_message(
fn async_send_message<T: Signers>(
&self,
keypairs: &[&Keypair],
keypairs: &T,
message: Message,
recent_blockhash: Hash,
) -> io::Result<Signature>;

View File

@ -72,6 +72,8 @@ pub mod genesis_config;
#[cfg(not(feature = "program"))]
pub mod signature;
#[cfg(not(feature = "program"))]
pub mod signers;
#[cfg(not(feature = "program"))]
pub mod system_transaction;
#[cfg(not(feature = "program"))]
pub mod transaction;

97
sdk/src/signers.rs Normal file
View File

@ -0,0 +1,97 @@
use crate::{
pubkey::Pubkey,
signature::{KeypairUtil, Signature},
};
pub trait Signers {
fn pubkeys(&self) -> Vec<Pubkey>;
fn sign_message(&self, message: &[u8]) -> Vec<Signature>;
}
macro_rules! default_keypairs_impl {
() => (
fn pubkeys(&self) -> Vec<Pubkey> {
self.iter().map(|keypair| keypair.pubkey()).collect()
}
fn sign_message(&self, message: &[u8]) -> Vec<Signature> {
self.iter()
.map(|keypair| keypair.sign_message(message))
.collect()
}
);
}
impl<T: KeypairUtil> Signers for [&T] {
default_keypairs_impl!();
}
impl Signers for [Box<dyn KeypairUtil>] {
default_keypairs_impl!();
}
impl<T: KeypairUtil> Signers for [&T; 0] {
default_keypairs_impl!();
}
impl<T: KeypairUtil> Signers for [&T; 1] {
default_keypairs_impl!();
}
impl<T: KeypairUtil> Signers for [&T; 2] {
default_keypairs_impl!();
}
impl<T: KeypairUtil> Signers for [&T; 3] {
default_keypairs_impl!();
}
impl<T: KeypairUtil> Signers for [&T; 4] {
default_keypairs_impl!();
}
impl<T: KeypairUtil> Signers for Vec<&T> {
default_keypairs_impl!();
}
#[cfg(test)]
mod tests {
use super::*;
use std::error;
struct Foo;
impl KeypairUtil for Foo {
fn try_pubkey(&self) -> Result<Pubkey, Box<dyn error::Error>> {
Ok(Pubkey::default())
}
fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, Box<dyn error::Error>> {
Ok(Signature::default())
}
}
struct Bar;
impl KeypairUtil for Bar {
fn try_pubkey(&self) -> Result<Pubkey, Box<dyn error::Error>> {
Ok(Pubkey::default())
}
fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, Box<dyn error::Error>> {
Ok(Signature::default())
}
}
#[test]
fn test_dyn_keypairs_compile() {
let xs: Vec<Box<dyn KeypairUtil>> = vec![Box::new(Foo {}), Box::new(Bar {})];
assert_eq!(
xs.sign_message(b""),
vec![Signature::default(), Signature::default()],
);
// Same as above, but less compiler magic.
let xs_ref: &[Box<dyn KeypairUtil>] = &xs;
assert_eq!(
Signers::sign_message(xs_ref, b""),
vec![Signature::default(), Signature::default()],
);
}
}

View File

@ -6,7 +6,8 @@ use crate::{
message::Message,
pubkey::Pubkey,
short_vec,
signature::{KeypairUtil, Signature},
signature::Signature,
signers::Signers,
system_instruction,
};
use std::result;
@ -95,20 +96,20 @@ impl Transaction {
Self::new_unsigned(message)
}
pub fn new_signed_with_payer<T: KeypairUtil>(
pub fn new_signed_with_payer<T: Signers>(
instructions: Vec<Instruction>,
payer: Option<&Pubkey>,
signing_keypairs: &[&T],
signing_keypairs: &T,
recent_blockhash: Hash,
) -> Self {
let message = Message::new_with_payer(instructions, payer);
Self::new(signing_keypairs, message, recent_blockhash)
}
pub fn new_signed_with_nonce<T: KeypairUtil>(
pub fn new_signed_with_nonce<T: Signers>(
mut instructions: Vec<Instruction>,
payer: Option<&Pubkey>,
signing_keypairs: &[&T],
signing_keypairs: &T,
nonce_account_pubkey: &Pubkey,
nonce_authority_pubkey: &Pubkey,
nonce_hash: Hash,
@ -126,8 +127,8 @@ impl Transaction {
Self::new_unsigned(message)
}
pub fn new<T: KeypairUtil>(
from_keypairs: &[&T],
pub fn new<T: Signers>(
from_keypairs: &T,
message: Message,
recent_blockhash: Hash,
) -> Transaction {
@ -136,8 +137,8 @@ impl Transaction {
tx
}
pub fn new_signed_instructions<T: KeypairUtil>(
from_keypairs: &[&T],
pub fn new_signed_instructions<T: Signers>(
from_keypairs: &T,
instructions: Vec<Instruction>,
recent_blockhash: Hash,
) -> Transaction {
@ -152,21 +153,19 @@ impl Transaction {
/// * `recent_blockhash` - The PoH hash.
/// * `program_ids` - The keys that identify programs used in the `instruction` vector.
/// * `instructions` - Instructions that will be executed atomically.
pub fn new_with_compiled_instructions<T: KeypairUtil>(
from_keypairs: &[&T],
pub fn new_with_compiled_instructions<T: Signers>(
from_keypairs: &T,
keys: &[Pubkey],
recent_blockhash: Hash,
program_ids: Vec<Pubkey>,
instructions: Vec<CompiledInstruction>,
) -> Self {
let mut account_keys: Vec<_> = from_keypairs
.iter()
.map(|keypair| (*keypair).pubkey())
.collect();
let mut account_keys = from_keypairs.pubkeys();
let from_keypairs_len = account_keys.len();
account_keys.extend_from_slice(keys);
account_keys.extend(&program_ids);
let message = Message::new_with_compiled_instructions(
from_keypairs.len() as u8,
from_keypairs_len as u8,
0,
program_ids.len() as u8,
account_keys,
@ -214,7 +213,7 @@ impl Transaction {
}
/// Check keys and keypair lengths, then sign this transaction.
pub fn sign<T: KeypairUtil>(&mut self, keypairs: &[&T], recent_blockhash: Hash) {
pub fn sign<T: Signers>(&mut self, keypairs: &T, recent_blockhash: Hash) {
self.partial_sign(keypairs, recent_blockhash);
assert_eq!(self.is_signed(), true, "not enough keypairs");
@ -223,9 +222,9 @@ impl Transaction {
/// 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<T: KeypairUtil>(&mut self, keypairs: &[&T], recent_blockhash: Hash) {
pub fn partial_sign<T: Signers>(&mut self, keypairs: &T, recent_blockhash: Hash) {
let positions = self
.get_signing_keypair_positions(keypairs)
.get_signing_keypair_positions(&keypairs.pubkeys())
.expect("account_keys doesn't contain num_required_signatures keys");
let positions: Vec<usize> = positions
.iter()
@ -236,9 +235,9 @@ impl Transaction {
/// Sign the transaction and place the signatures in their associated positions in `signatures`
/// without checking that the positions are correct.
pub fn partial_sign_unchecked<T: KeypairUtil>(
pub fn partial_sign_unchecked<T: Signers>(
&mut self,
keypairs: &[&T],
keypairs: &T,
positions: Vec<usize>,
recent_blockhash: Hash,
) {
@ -250,8 +249,9 @@ impl Transaction {
.for_each(|signature| *signature = Signature::default());
}
let signatures = keypairs.sign_message(&self.message_data());
for i in 0..positions.len() {
self.signatures[positions[i]] = keypairs[i].sign_message(&self.message_data())
self.signatures[positions[i]] = signatures[i];
}
}
@ -271,23 +271,16 @@ impl Transaction {
}
/// Get the positions of the pubkeys in `account_keys` associated with signing keypairs
pub fn get_signing_keypair_positions<T: KeypairUtil>(
&self,
keypairs: &[&T],
) -> Result<Vec<Option<usize>>> {
pub fn get_signing_keypair_positions(&self, pubkeys: &[Pubkey]) -> Result<Vec<Option<usize>>> {
if self.message.account_keys.len() < self.message.header.num_required_signatures as usize {
return Err(TransactionError::InvalidAccountIndex);
}
let signed_keys =
&self.message.account_keys[0..self.message.header.num_required_signatures as usize];
Ok(keypairs
Ok(pubkeys
.iter()
.map(|keypair| {
signed_keys
.iter()
.position(|pubkey| pubkey == &keypair.pubkey())
})
.map(|pubkey| signed_keys.iter().position(|x| x == pubkey))
.collect())
}
@ -338,7 +331,12 @@ impl Transaction {
#[cfg(test)]
mod tests {
use super::*;
use crate::{hash::hash, instruction::AccountMeta, signature::Keypair, system_instruction};
use crate::{
hash::hash,
instruction::AccountMeta,
signature::{Keypair, KeypairUtil},
system_instruction,
};
use bincode::{deserialize, serialize, serialized_size};
use std::mem::size_of;