CLI: Add multi-session signing support (#8927)

* SDK: Add `NullSigner` implementation

* SDK: Split `Transaction::verify()` to gain access to results

* CLI: Minor refactor of --sign_only result parsing

* CLI: Enable paritial signing

Signers specified by pubkey, but without a matching --signer arg
supplied fall back to a `NullSigner` when --sign-only is in effect.
This allows their pubkey to be used for TX construction as usual,
but leaves their `sign_message()` a NOP. As such, with --sign-only
in effect, signing and verification must be done separately, with
the latter's per-signature results considered

* CLI: Surface/report missing/bad signers to user

* CLI: Suppress --sign-only JSON output

* nits

* Docs for multi-session offline signing
This commit is contained in:
Trent Nelson
2020-03-18 21:49:38 -06:00
committed by GitHub
parent aeb7278b00
commit 98228c392e
12 changed files with 455 additions and 86 deletions

View File

@ -269,6 +269,39 @@ where
}
}
/// NullSigner - A `Signer` implementation that always produces `Signature::default()`.
/// Used as a placeholder for absentee signers whose 'Pubkey` is required to construct
/// the transaction
#[derive(Clone, Debug, Default)]
pub struct NullSigner {
pubkey: Pubkey,
}
impl NullSigner {
pub fn new(pubkey: &Pubkey) -> Self {
Self { pubkey: *pubkey }
}
}
impl Signer for NullSigner {
fn try_pubkey(&self) -> Result<Pubkey, SignerError> {
Ok(self.pubkey)
}
fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, SignerError> {
Ok(Signature::default())
}
}
impl<T> PartialEq<T> for NullSigner
where
T: Signer,
{
fn eq(&self, other: &T) -> bool {
self.pubkey == other.pubkey()
}
}
pub fn read_keypair<R: Read>(reader: &mut R) -> Result<Keypair, Box<dyn error::Error>> {
let bytes: Vec<u8> = serde_json::from_reader(reader)?;
let dalek_keypair = ed25519_dalek::Keypair::from_bytes(&bytes)

View File

@ -300,14 +300,20 @@ impl Transaction {
Ok(())
}
/// Verify the transaction
pub fn verify(&self) -> Result<()> {
if !self
.signatures
pub fn verify_with_results(&self) -> Vec<bool> {
self.signatures
.iter()
.zip(&self.message.account_keys)
.map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), &self.message_data()))
.all(|verify_result| verify_result)
.collect()
}
/// Verify the transaction
pub fn verify(&self) -> Result<()> {
if !self
.verify_with_results()
.iter()
.all(|verify_result| *verify_result)
{
Err(TransactionError::SignatureFailure)
} else {