Skip to main content

miden_standards/account/auth/
approver.rs

1use alloc::collections::BTreeSet;
2use alloc::vec::Vec;
3use core::num::NonZeroU32;
4
5use miden_protocol::account::auth::{AuthScheme, PublicKey, PublicKeyCommitment};
6use miden_protocol::errors::AccountError;
7
8// APPROVER
9// ================================================================================================
10
11/// A signer that can approve transactions, identified by its public key commitment and the
12/// signature scheme used to verify its signatures.
13///
14/// Note: an approver using [`AuthScheme::EcdsaK256Keccak`] discloses its public key and signature
15/// at proving time and therefore does not provide public-key privacy, regardless of the component
16/// it is used in (single-sig, ACL, multisig, or guarded multisig). See
17/// [`AuthScheme::EcdsaK256Keccak`] for details, and prefer [`AuthScheme::Falcon512Poseidon2`] if
18/// signer-key privacy is required.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct Approver {
21    pub_key: PublicKeyCommitment,
22    auth_scheme: AuthScheme,
23}
24
25impl Approver {
26    /// Creates a new [`Approver`] from the given public key commitment and signature scheme.
27    pub fn new(pub_key: PublicKeyCommitment, auth_scheme: AuthScheme) -> Self {
28        Self { pub_key, auth_scheme }
29    }
30
31    /// Returns the public key commitment of this approver.
32    pub fn pub_key(&self) -> PublicKeyCommitment {
33        self.pub_key
34    }
35
36    /// Returns the signature scheme of this approver.
37    pub fn auth_scheme(&self) -> AuthScheme {
38        self.auth_scheme
39    }
40}
41
42impl From<&PublicKey> for Approver {
43    fn from(pub_key: &PublicKey) -> Self {
44        Self::new(pub_key.to_commitment(), pub_key.auth_scheme())
45    }
46}
47
48// APPROVER SET
49// ================================================================================================
50
51/// A set of [`Approver`]s together with the threshold of signatures required to approve a
52/// transaction by default.
53///
54/// The set is guaranteed to be valid by construction: the threshold is non-zero and at most the
55/// number of approvers, and no public key commitment appears more than once.
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct ApproverSet {
58    approvers: Vec<Approver>,
59    threshold: NonZeroU32,
60}
61
62impl ApproverSet {
63    /// Creates a new [`ApproverSet`] from the given approvers and default threshold.
64    ///
65    /// # Errors
66    ///
67    /// Returns an error if:
68    /// - `threshold` is zero,
69    /// - `threshold` is greater than the number of approvers, or
70    /// - two approvers share the same public key commitment.
71    pub fn new(approvers: Vec<Approver>, threshold: u32) -> Result<Self, AccountError> {
72        let threshold = NonZeroU32::new(threshold)
73            .ok_or_else(|| AccountError::other("threshold must be at least 1"))?;
74
75        if threshold.get() > approvers.len() as u32 {
76            return Err(AccountError::other(
77                "threshold cannot be greater than number of approvers",
78            ));
79        }
80
81        let unique_approvers: BTreeSet<_> = approvers.iter().map(Approver::pub_key).collect();
82        if unique_approvers.len() != approvers.len() {
83            return Err(AccountError::other("duplicate approver public keys are not allowed"));
84        }
85
86        Ok(Self { approvers, threshold })
87    }
88
89    /// Returns the approvers in this set.
90    pub fn approvers(&self) -> &[Approver] {
91        &self.approvers
92    }
93
94    /// Returns the default threshold of signatures required to approve a transaction.
95    pub fn threshold(&self) -> NonZeroU32 {
96        self.threshold
97    }
98}
99
100// TESTS
101// ================================================================================================
102
103#[cfg(test)]
104mod tests {
105    use alloc::string::ToString;
106
107    use miden_protocol::Word;
108    use miden_protocol::account::auth::AuthScheme;
109
110    use super::*;
111
112    fn approver(seed: u32) -> Approver {
113        Approver::new(PublicKeyCommitment::from(Word::from([seed; 4])), AuthScheme::EcdsaK256Keccak)
114    }
115
116    #[test]
117    fn rejects_zero_threshold() {
118        let err = ApproverSet::new(vec![approver(1)], 0).unwrap_err();
119        assert!(err.to_string().contains("threshold must be at least 1"));
120    }
121
122    #[test]
123    fn rejects_threshold_above_approver_count() {
124        let err = ApproverSet::new(vec![approver(1)], 2).unwrap_err();
125        assert!(err.to_string().contains("threshold cannot be greater than number of approvers"));
126    }
127
128    #[test]
129    fn rejects_duplicate_approvers() {
130        let err = ApproverSet::new(vec![approver(1), approver(1)], 2).unwrap_err();
131        assert!(err.to_string().contains("duplicate approver public keys are not allowed"));
132    }
133
134    #[test]
135    fn accepts_valid_set() {
136        let set = ApproverSet::new(vec![approver(1), approver(2)], 2).unwrap();
137        assert_eq!(set.approvers().len(), 2);
138        assert_eq!(set.threshold().get(), 2);
139    }
140}