af_keys/multisig.rs
1//! Additional helpers for multisig signing.
2
3use anyhow::{Context, Error};
4use serde::{Deserialize, Serialize};
5use sui_sdk_types::{Address, MultisigCommittee, SignedTransaction, Transaction, UserSignature};
6
7use crate::Keystore;
8
9/// Data needed for signing as a multisig.
10#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
11pub struct MultisigIntent {
12 pub committee: MultisigCommittee,
13 /// The indexes of the public keys in `committee` to sign for.
14 pub signers: Vec<usize>,
15}
16
17/// Sign the transaction data with the private key(s) for an address.
18///
19/// If the address is a native Sui multisig, `multisig_intent` should be specified to tell the
20/// function which constituents to sign for.
21pub fn sign_for_address(
22 transaction: &Transaction,
23 address: Address,
24 multisig_intent: Option<MultisigIntent>,
25 keystore: &Keystore,
26) -> Result<UserSignature, Error> {
27 let signature = if let Some(intent) = multisig_intent {
28 let msig_address = intent.committee.derive_address();
29 anyhow::ensure!(
30 msig_address == address,
31 "multisig address {msig_address} doesn't match target address {address}"
32 );
33 UserSignature::Multisig(keystore.multisign_tx(
34 transaction,
35 intent.committee,
36 &intent.signers,
37 )?)
38 } else {
39 UserSignature::Simple(keystore.sign_tx(transaction, address)?)
40 };
41 Ok(signature)
42}
43
44/// Signs a transaction for all required signers and assembles a [`SignedTransaction`].
45///
46/// This is a convenience wrapper around [`signatures`] that bundles the transaction data with
47/// the collected signatures into a ready-to-submit [`SignedTransaction`].
48pub fn signed_transaction(
49 transaction: Transaction,
50 multisig_sender: Option<MultisigIntent>,
51 multisig_sponsor: Option<MultisigIntent>,
52 keystore: &Keystore,
53) -> Result<SignedTransaction, Error> {
54 let sigs = signatures(&transaction, multisig_sender, multisig_sponsor, keystore)?;
55 Ok(SignedTransaction {
56 transaction,
57 signatures: sigs,
58 })
59}
60
61/// Computes the required signatures for a transaction's data.
62///
63/// [`Transaction`] has a sender and a sponsor (which may be equal to sender), which are
64/// [`Address`]es. This function then gets that information and knows who it has to sign for.
65///
66/// For simple cases, it just uses those `Address`es and signs for them using the [`Keystore`].
67///
68/// However, there's no way to know if `Address` corresponds to a multisig. So the function has
69/// two optional arguments: `multisig_sender` and `multisig_sponsor`. They exist so that the caller
70/// can tell the function if the sender and/or sponsor are multisigs. Their value encodes all the
71/// public keys that compose the multisig, their weights, and the threshold (public information).
72///
73/// The function can then sign for the simple addresses that compose the multisig (assuming
74/// [`Keystore`] has the private keys for each) and combine the simple signatures into a generic
75/// signature.
76///
77/// The [`MultisigIntent`] message declares what public keys the [`Keystore`] has to sign for. It's
78/// not required to sign for all of them, only a subset that has enough weight.
79pub fn signatures(
80 transaction: &Transaction,
81 multisig_sender: Option<MultisigIntent>,
82 multisig_sponsor: Option<MultisigIntent>,
83 keystore: &Keystore,
84) -> Result<Vec<UserSignature>, Error> {
85 let sender_signature =
86 sign_for_address(transaction, transaction.sender, multisig_sender, keystore)
87 .context("Signing for sender")?;
88 let mut signatures = vec![sender_signature];
89 if transaction.sender == transaction.gas_payment.owner {
90 if multisig_sponsor.is_some() {
91 log::warn!("Ignoring multisig_sponsor since sender owns the gas inputs");
92 };
93 } else {
94 signatures.push(
95 sign_for_address(
96 transaction,
97 transaction.gas_payment.owner,
98 multisig_sponsor,
99 keystore,
100 )
101 .context("Signing for sponsor")?,
102 );
103 };
104 Ok(signatures)
105}