diff --git a/contracts/multisig-account/src/lib.rs b/contracts/multisig-account/src/lib.rs index 50b0a48..1e0f19d 100644 --- a/contracts/multisig-account/src/lib.rs +++ b/contracts/multisig-account/src/lib.rs @@ -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, } @@ -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>, threshold: u32) { if threshold == 0 || threshold > signers.len() { panic_with_error!(&env, Error::InvalidThreshold); @@ -73,6 +89,15 @@ impl CustomAccountInterface for MultisigAccount { type Signature = Vec; 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>, diff --git a/contracts/multisig-account/src/test.rs b/contracts/multisig-account/src/test.rs index 638e820..e4a26ee 100644 --- a/contracts/multisig-account/src/test.rs +++ b/contracts/multisig-account/src/test.rs @@ -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(())); +}