Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions contracts/multisig-account/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,34 @@ enum DataKey {
SignerCount,
}

/// A single ed25519 signature from one signer in the multisig set.
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Signature {
/// The public key of the signer who created this signature.
pub public_key: BytesN<32>,
/// The ed25519 signature bytes.
pub signature: BytesN<64>,
}

/// Errors returned by the multisig-account contract's public entry points.
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum Error {
/// The configured threshold is zero or exceeds the signer count.
InvalidThreshold = 1,
/// The signer configuration contains duplicate public keys.
DuplicateSigner = 2,
/// The supplied signature count is below the configured threshold.
NotEnoughSigners = 3,
/// Signatures are not strictly ordered by ascending public key.
BadSignatureOrder = 4,
/// A signature corresponds to a public key that is not a configured signer.
UnknownSigner = 5,
/// The contract has not been initialized; threshold or signer count is unavailable.
NotInitialized = 6,
/// The supplied signature count exceeds the configured signer count.
TooManySigners = 7,
}

Expand All @@ -46,6 +57,11 @@ pub struct MultisigAccount;

#[contractimpl]
impl MultisigAccount {
/// Initialize the multisig account with a set of authorized signers and a signature threshold.
///
/// # Arguments
/// * `signers` — A vector of ed25519 public keys (32 bytes each) authorized to sign transactions.
/// * `threshold` — The minimum number of signatures required to authorize a transaction; must be > 0 and ≤ the signer count.
pub fn __constructor(env: Env, signers: Vec<BytesN<32>>, threshold: u32) {
if threshold == 0 || threshold > signers.len() {
panic_with_error!(&env, Error::InvalidThreshold);
Expand Down Expand Up @@ -73,6 +89,15 @@ impl CustomAccountInterface for MultisigAccount {
type Signature = Vec<Signature>;
type Error = Error;

/// Verify the authorization of a transaction by checking N-of-M ed25519 signatures.
///
/// Verifies that the supplied signatures meet the configured threshold and each belongs to
/// an authorized signer, with signatures ordered in ascending public-key order.
///
/// # Arguments
/// * `signature_payload` — A 32-byte hash of the transaction to authorize.
/// * `signatures` — A vector of ed25519 signatures, each with a public key and signature bytes, ordered by ascending public key.
/// * `_auth_contexts` — Intentionally unused; see [ADR-0007](../adr/0007-unscoped-multisig-authorization.md) for why this account does not scope authorization to specific contracts or functions during pre-alpha.
fn __check_auth(
env: Env,
signature_payload: Hash<32>,
Expand Down
19 changes: 19 additions & 0 deletions contracts/multisig-account/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,22 @@ fn too_many_signatures_is_rejected() {
Err(Ok(Error::TooManySigners))
);
}

#[test]
fn three_of_five_signers_authorize() {
let env = Env::default();
let mut keys = std::vec![
SigningKey::from_bytes(&[1; 32]),
SigningKey::from_bytes(&[2; 32]),
SigningKey::from_bytes(&[3; 32]),
SigningKey::from_bytes(&[4; 32]),
SigningKey::from_bytes(&[5; 32]),
];
keys.sort_by_key(|key| key.verifying_key().to_bytes());
let account = register_account(&env, &keys, 3);
let payload = BytesN::from_array(&env, &[7; 32]);
// Supply exactly 3 signatures from the 5 signers in correct order
let signatures = signatures_for(&env, &keys[..3], &payload.to_array());

assert_eq!(check_auth(&env, &account, &payload, signatures), Ok(()));
}