af_keys/
lib.rs

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