Skip to main content

accumulate_client/runtime/
signing.rs

1//! Runtime helpers for signatures (validation beyond serde shape).
2use crate::generated::signatures::Signature;
3use crate::generated::signatures::{DelegatedSignature, SignatureSet};
4
5#[derive(thiserror::Error, Debug)]
6pub enum SigRuntimeError {
7    #[error("delegation depth exceeded (max 5)")]
8    DelegationDepthExceeded,
9    #[error("invalid signature set threshold")]
10    InvalidSignatureSetThreshold,
11    #[error("verification failed")]
12    VerificationFailed,
13}
14
15/// Calculate delegated depth:
16/// - `Signature::Delegated(inner)` increases depth by 1 and recurses.
17/// - Other signatures are leaves (depth 0).
18pub fn delegated_depth(sig: &Signature) -> usize {
19    match sig {
20        Signature::Delegated(inner) => 1 + delegated_depth(&inner.signature),
21        _ => 0,
22    }
23}
24
25/// Enforce max depth (≤ 5). Returns `Ok(())` if within limit.
26pub fn enforce_delegated_depth(sig: &Signature) -> Result<(), SigRuntimeError> {
27    if delegated_depth(sig) > 5 {
28        Err(SigRuntimeError::DelegationDepthExceeded)
29    } else {
30        Ok(())
31    }
32}
33
34impl DelegatedSignature {
35    /// Smart constructor that enforces depth limit when wrapped in Signature::Delegated.
36    pub fn new_enforced(signature: Box<Signature>, delegator: String) -> Result<Self, SigRuntimeError> {
37        let wrapper = Signature::Delegated(DelegatedSignature {
38            signature: signature.clone(),
39            delegator: delegator.clone()
40        });
41        enforce_delegated_depth(&wrapper)?;
42        Ok(DelegatedSignature { signature, delegator })
43    }
44}
45
46/// Extended SignatureSet with threshold for runtime validation
47/// Since the generated SignatureSet doesn't have a threshold field in the YAML,
48/// we'll simulate threshold semantics by treating all signatures as requiring consensus
49#[derive(Debug, Clone)]
50pub struct SignatureSetWithThreshold {
51    pub inner: SignatureSet,
52    pub threshold: u32,
53}
54
55impl SignatureSetWithThreshold {
56    pub fn new(inner: SignatureSet, threshold: u32) -> Result<Self, SigRuntimeError> {
57        if threshold == 0 || (threshold as usize) > inner.signatures.len() {
58            return Err(SigRuntimeError::InvalidSignatureSetThreshold);
59        }
60        Ok(Self { inner, threshold })
61    }
62}
63
64/// Count signatures that verify `true`. If a signature cannot be verified due to an error,
65/// treat as `false` (verification errors are counted as invalid).
66pub fn count_valid_sigs(sigs: &[Box<Signature>], message: &[u8]) -> usize {
67    sigs.iter()
68        .filter(|s| {
69            // Use the AccSignature trait for verification
70            use crate::generated::signatures::AccSignature;
71            match s.as_ref() {
72                Signature::ED25519(sig) => sig.verify(message).unwrap_or(false),
73                Signature::LegacyED25519(sig) => sig.verify(message).unwrap_or(false),
74                Signature::RCD1(sig) => sig.verify(message).unwrap_or(false),
75                Signature::BTC(sig) => sig.verify(message).unwrap_or(false),
76                Signature::BTCLegacy(sig) => sig.verify(message).unwrap_or(false),
77                Signature::ETH(sig) => sig.verify(message).unwrap_or(false),
78                Signature::RsaSha256(sig) => sig.verify(message).unwrap_or(false),
79                Signature::EcdsaSha256(sig) => sig.verify(message).unwrap_or(false),
80                Signature::TypedData(sig) => sig.verify(message).unwrap_or(false),
81                Signature::Receipt(sig) => sig.verify(message).unwrap_or(false),
82                Signature::Partition(sig) => sig.verify(message).unwrap_or(false),
83                Signature::Set(sig) => sig.verify(message).unwrap_or(false),
84                Signature::Remote(sig) => sig.verify(message).unwrap_or(false),
85                Signature::Delegated(sig) => sig.verify(message).unwrap_or(false),
86                Signature::Internal(sig) => sig.verify(message).unwrap_or(false),
87                Signature::Authority(sig) => sig.verify(message).unwrap_or(false),
88            }
89        })
90        .count()
91}
92
93/// Validate threshold invariants and evaluate.
94/// - threshold must be >=1 and <= signatures.len()
95/// - returns Ok(true) iff valid_count >= threshold
96pub fn evaluate_signature_set(set: &SignatureSetWithThreshold, message: &[u8]) -> Result<bool, SigRuntimeError> {
97    let n = set.inner.signatures.len();
98    if set.threshold == 0 || (set.threshold as usize) > n {
99        return Err(SigRuntimeError::InvalidSignatureSetThreshold);
100    }
101    let valid = count_valid_sigs(&set.inner.signatures, message) as u32;
102    Ok(valid >= set.threshold)
103}