* 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
(cherry picked from commit 98228c392e
)
Co-authored-by: Trent Nelson <trent@solana.com>
This commit is contained in:
@@ -1015,17 +1015,31 @@ pub fn get_blockhash_and_fee_calculator(
|
||||
}
|
||||
|
||||
pub fn return_signers(tx: &Transaction) -> ProcessResult {
|
||||
println_signers(tx);
|
||||
let signers: Vec<_> = tx
|
||||
.signatures
|
||||
let verify_results = tx.verify_with_results();
|
||||
let mut signers = Vec::new();
|
||||
let mut absent = Vec::new();
|
||||
let mut bad_sig = Vec::new();
|
||||
tx.signatures
|
||||
.iter()
|
||||
.zip(tx.message.account_keys.clone())
|
||||
.map(|(signature, pubkey)| format!("{}={}", pubkey, signature))
|
||||
.collect();
|
||||
.zip(tx.message.account_keys.iter())
|
||||
.zip(verify_results.into_iter())
|
||||
.for_each(|((sig, key), res)| {
|
||||
if res {
|
||||
signers.push(format!("{}={}", key, sig))
|
||||
} else if *sig == Signature::default() {
|
||||
absent.push(key.to_string());
|
||||
} else {
|
||||
bad_sig.push(key.to_string());
|
||||
}
|
||||
});
|
||||
|
||||
println_signers(&tx.message.recent_blockhash, &signers, &absent, &bad_sig);
|
||||
|
||||
Ok(json!({
|
||||
"blockhash": tx.message.recent_blockhash.to_string(),
|
||||
"signers": &signers,
|
||||
"absent": &absent,
|
||||
"badSig": &bad_sig,
|
||||
})
|
||||
.to_string())
|
||||
}
|
||||
@@ -1310,11 +1324,12 @@ fn process_pay(
|
||||
Message::new(&[ix])
|
||||
};
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, blockhash)?;
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, blockhash)?;
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
let nonce_account = rpc_client.get_account(nonce_account)?;
|
||||
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &blockhash)?;
|
||||
@@ -1350,10 +1365,11 @@ fn process_pay(
|
||||
);
|
||||
let message = Message::new(&ixs);
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&[config.signers[0], &contract_state], blockhash)?;
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&[config.signers[0], &contract_state], blockhash)?;
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&[config.signers[0], &contract_state], blockhash)?;
|
||||
check_account_for_fee(
|
||||
rpc_client,
|
||||
&config.signers[0].pubkey(),
|
||||
@@ -1395,10 +1411,11 @@ fn process_pay(
|
||||
);
|
||||
let message = Message::new(&ixs);
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&[config.signers[0], &contract_state], blockhash)?;
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&[config.signers[0], &contract_state], blockhash)?;
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&[config.signers[0], &contract_state], blockhash)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(
|
||||
&mut tx,
|
||||
&[config.signers[0], &contract_state],
|
||||
@@ -1505,11 +1522,12 @@ fn process_transfer(
|
||||
Message::new_with_payer(&ixs, Some(&fee_payer.pubkey()))
|
||||
};
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
let nonce_account = rpc_client.get_account(nonce_account)?;
|
||||
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
|
||||
@@ -2536,7 +2554,9 @@ mod tests {
|
||||
use solana_client::mock_rpc_client_request::SIGNATURE;
|
||||
use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, read_keypair_file, write_keypair_file, Presigner},
|
||||
signature::{
|
||||
keypair_from_seed, read_keypair_file, write_keypair_file, NullSigner, Presigner,
|
||||
},
|
||||
transaction::TransactionError,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
@@ -3664,4 +3684,52 @@ mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_return_signers() {
|
||||
struct BadSigner {
|
||||
pubkey: Pubkey,
|
||||
}
|
||||
|
||||
impl BadSigner {
|
||||
pub fn new(pubkey: Pubkey) -> Self {
|
||||
Self { pubkey }
|
||||
}
|
||||
}
|
||||
|
||||
impl Signer for BadSigner {
|
||||
fn try_pubkey(&self) -> Result<Pubkey, SignerError> {
|
||||
Ok(self.pubkey)
|
||||
}
|
||||
|
||||
fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, SignerError> {
|
||||
Ok(Signature::new(&[1u8; 64]))
|
||||
}
|
||||
}
|
||||
|
||||
let present: Box<dyn Signer> = Box::new(keypair_from_seed(&[2u8; 32]).unwrap());
|
||||
let absent: Box<dyn Signer> = Box::new(NullSigner::new(&Pubkey::new(&[3u8; 32])));
|
||||
let bad: Box<dyn Signer> = Box::new(BadSigner::new(Pubkey::new(&[4u8; 32])));
|
||||
let to = Pubkey::new(&[5u8; 32]);
|
||||
let nonce = Pubkey::new(&[6u8; 32]);
|
||||
let from = present.pubkey();
|
||||
let fee_payer = absent.pubkey();
|
||||
let nonce_auth = bad.pubkey();
|
||||
let mut tx = Transaction::new_unsigned(Message::new_with_nonce(
|
||||
vec![system_instruction::transfer(&from, &to, 42)],
|
||||
Some(&fee_payer),
|
||||
&nonce,
|
||||
&nonce_auth,
|
||||
));
|
||||
|
||||
let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()];
|
||||
let blockhash = Hash::new(&[7u8; 32]);
|
||||
tx.try_partial_sign(&signers, blockhash).unwrap();
|
||||
let res = return_signers(&tx).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&res);
|
||||
assert_eq!(sign_only.blockhash, blockhash);
|
||||
assert_eq!(sign_only.present_signers[0].0, present.pubkey());
|
||||
assert_eq!(sign_only.absent_signers[0], absent.pubkey());
|
||||
assert_eq!(sign_only.bad_signers[0], bad.pubkey());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user