af_keys/
lib.rs

1#![cfg_attr(all(doc, not(doctest)), feature(doc_cfg))]
2
3//! Light-weight, read-only version of Sui's file-based keystore.
4//!
5//! This crate uses [`eyre`] and is meant for applications, not libraries.
6//!
7//! <div class="warning">
8//!
9//! The medium-term plan is to deprecate most of this in favor of [`sui_crypto`].
10//!
11//! </div>
12//!
13//! [`sui_crypto`]: https://docs.rs/sui-crypto/latest/sui_crypto/
14use crypto::Signature;
15use eyre::{Context as _, Result};
16pub use intent::Intent;
17pub use keystore::{FileBasedKeystore, ReadOnlyAccountKeystore};
18use multisig::{MultiSig, MultiSigSigner, ThresholdUnit};
19use sui_sdk_types::{Address as SuiAddress, GasPayment, Transaction, UserSignature};
20
21pub mod crypto;
22pub mod intent;
23pub mod keystore;
24pub mod multisig;
25
26/// Computes the required signatures for a transaction's data.
27///
28/// [`Transaction`] has a sender and a sponsor (which may be equal to sender), which are
29/// [`SuiAddress`]es. This function then gets that information and knows who it has to sign for.
30///
31/// For simple cases, it just uses those `SuiAddress`es and signs for them using the `keystore`.
32///
33/// However, there's no way to know if `SuiAddress` corresponds to a multisig. So the function has
34/// two optional arguments: `multisig_sender` and `multisig_sponsor`. They exist so that the caller
35/// can tell the function if the sender and/or sponsor are multisigs. Their value encodes all the
36/// public keys that compose the multisig, their weights, and the threshold (public information).
37///
38/// The function can then sign for the simple addresses that compose the multisig (assuming
39/// `Keystore` has the private keys for each) and combine the simple signatures into a generic
40/// signature.
41///
42/// The [`MultiSigSigner`] message declares what public keys the `Keystore` has to sign for. It's
43/// not required to sign for all of them, only a subset that has enough weight.
44pub fn signatures<K: ReadOnlyAccountKeystore>(
45    tx_data: &Transaction,
46    multisig_sender: Option<MultiSigSigner>,
47    multisig_sponsor: Option<MultiSigSigner>,
48    keystore: &K,
49) -> Result<Vec<UserSignature>> {
50    let Transaction {
51        sender,
52        gas_payment: GasPayment { owner: sponsor, .. },
53        ..
54    } = tx_data;
55
56    let sender_signature = sign_for_address(tx_data, sender, multisig_sender, keystore)
57        .context("Signing for sender")?;
58    let mut signatures = vec![sender_signature];
59
60    if sender != sponsor {
61        signatures.push(
62            sign_for_address(tx_data, sponsor, multisig_sponsor, keystore)
63                .context("Signing for sponsor")?,
64        );
65    };
66    Ok(signatures)
67}
68
69pub fn sign_for_address<K: ReadOnlyAccountKeystore>(
70    tx_data: &Transaction,
71    address: &SuiAddress,
72    multisig_signer: Option<MultiSigSigner>,
73    keystore: &K,
74) -> Result<UserSignature> {
75    let signature = if let Some(multisig) = multisig_signer {
76        let msig_address = SuiAddress::from(&multisig.multisig_pk);
77        eyre::ensure!(
78            msig_address == *address,
79            "Multisig address {msig_address} doesn't match target address {address}"
80        );
81        sign_transaction_multisig(tx_data, multisig, keystore)?.into()
82    } else {
83        sign_transaction(tx_data, address, keystore)?.into()
84    };
85    Ok(signature)
86}
87
88pub fn sign_transaction<K: ReadOnlyAccountKeystore>(
89    tx_data: &Transaction,
90    signer: &SuiAddress,
91    keystore: &K,
92) -> Result<Signature> {
93    let signature = keystore.sign_hashed(signer, &tx_data.signing_digest())?;
94    Ok(signature)
95}
96
97pub fn sign_transaction_multisig<K: ReadOnlyAccountKeystore>(
98    tx_data: &Transaction,
99    MultiSigSigner {
100        multisig_pk,
101        signers,
102    }: MultiSigSigner,
103    keystore: &K,
104) -> Result<MultiSig> {
105    let mut total_weight = 0;
106    let mut signatures = vec![];
107
108    let hashed = tx_data.signing_digest();
109    for idx in signers {
110        let (pk, weight) = multisig_pk.pubkeys().get(idx).ok_or_else(|| {
111            eyre::eyre!("Signer idx {idx} out of bounds for multisig {multisig_pk:?}")
112        })?;
113        total_weight += *weight as ThresholdUnit;
114        signatures.push(keystore.sign_hashed(&pk.to_sui_address(), &hashed)?);
115    }
116
117    if total_weight < *multisig_pk.threshold() {
118        eyre::bail!("Signers do not have enought weight to sign for multisig");
119    }
120
121    Ok(MultiSig::combine(signatures, multisig_pk)?)
122}