Skip to main content

blvm_sdk/governance/
nested_multisig.rs

1//! Nested Multisig Support
2//!
3//! Provides cryptographic primitives for nested multisig operations.
4//! Supports team-based signature aggregation for nested 7×7 multisig structure.
5
6use crate::governance::error::{GovernanceError, GovernanceResult};
7use crate::governance::{PublicKey, Signature};
8
9/// Team structure for nested multisig
10#[derive(Debug, Clone)]
11pub struct Team {
12    pub id: String,
13    pub name: String,
14    pub maintainers: Vec<TeamMaintainer>,
15}
16
17#[derive(Debug, Clone)]
18pub struct TeamMaintainer {
19    pub github: String,
20    pub public_key: PublicKey,
21}
22
23/// Nested multisig configuration
24#[derive(Debug, Clone)]
25pub struct NestedMultisig {
26    teams: Vec<Team>,
27    teams_required: usize,
28    maintainers_per_team_required: usize,
29}
30
31impl NestedMultisig {
32    /// Create a new nested multisig configuration
33    pub fn new(
34        teams: Vec<Team>,
35        teams_required: usize,
36        maintainers_per_team_required: usize,
37    ) -> GovernanceResult<Self> {
38        if teams_required == 0 || teams_required > teams.len() {
39            return Err(GovernanceError::InvalidThreshold {
40                threshold: teams_required,
41                total: teams.len(),
42            });
43        }
44
45        if maintainers_per_team_required == 0 {
46            return Err(GovernanceError::InvalidThreshold {
47                threshold: maintainers_per_team_required,
48                total: 7, // Assuming 7 maintainers per team
49            });
50        }
51
52        // Validate each team has enough maintainers
53        for team in &teams {
54            if team.maintainers.len() < maintainers_per_team_required {
55                return Err(GovernanceError::InvalidMultisig(format!(
56                    "Team {} has {} maintainers, but {} required",
57                    team.id,
58                    team.maintainers.len(),
59                    maintainers_per_team_required
60                )));
61            }
62        }
63
64        Ok(Self {
65            teams,
66            teams_required,
67            maintainers_per_team_required,
68        })
69    }
70
71    /// Verify nested multisig signatures
72    ///
73    /// Process:
74    /// 1. Group signatures by team
75    /// 2. Count team approvals (maintainers_per_team_required per team)
76    /// 3. Count inter-team approvals (teams_required)
77    pub fn verify(
78        &self,
79        message: &[u8],
80        signatures: &[(String, Signature)], // (github_username, signature)
81    ) -> GovernanceResult<NestedMultisigResult> {
82        // Group signatures by team
83        let mut team_signatures: std::collections::HashMap<String, Vec<(String, Signature)>> =
84            std::collections::HashMap::new();
85
86        for (github, signature) in signatures {
87            if let Some(team_id) = self.find_maintainer_team(github) {
88                team_signatures
89                    .entry(team_id)
90                    .or_default()
91                    .push((github.clone(), signature.clone()));
92            }
93        }
94
95        // Count team approvals
96        let mut teams_approved = 0;
97        let mut total_maintainers_approved = 0;
98        let mut team_details = Vec::new();
99
100        for team in &self.teams {
101            // Verify signatures for this team
102            let mut valid_sigs = 0;
103            if let Some(sigs) = team_signatures.get(&team.id) {
104                for (github, sig) in sigs {
105                    // Find maintainer's public key
106                    if let Some(maintainer) = team.maintainers.iter().find(|m| m.github == *github)
107                    {
108                        if crate::governance::verify_signature(
109                            sig,
110                            message,
111                            &maintainer.public_key,
112                        )? {
113                            valid_sigs += 1;
114                        }
115                    }
116                }
117            }
118
119            let team_approved = valid_sigs >= self.maintainers_per_team_required;
120
121            if team_approved {
122                teams_approved += 1;
123                total_maintainers_approved += valid_sigs;
124            }
125
126            team_details.push(TeamApprovalStatus {
127                team_id: team.id.clone(),
128                team_name: team.name.clone(),
129                maintainers_signed: valid_sigs,
130                maintainers_required: self.maintainers_per_team_required,
131                approved: team_approved,
132            });
133        }
134
135        let inter_team_approved = teams_approved >= self.teams_required;
136        let total_maintainers_required = self.teams_required * self.maintainers_per_team_required;
137
138        Ok(NestedMultisigResult {
139            teams_approved,
140            teams_required: self.teams_required,
141            maintainers_approved: total_maintainers_approved,
142            maintainers_required: total_maintainers_required,
143            inter_team_approved,
144            team_details,
145        })
146    }
147
148    /// Find which team a maintainer belongs to
149    fn find_maintainer_team(&self, github: &str) -> Option<String> {
150        for team in &self.teams {
151            if team.maintainers.iter().any(|m| m.github == github) {
152                return Some(team.id.clone());
153            }
154        }
155        None
156    }
157}
158
159/// Result of nested multisig verification
160#[derive(Debug, Clone)]
161pub struct NestedMultisigResult {
162    pub teams_approved: usize,
163    pub teams_required: usize,
164    pub maintainers_approved: usize,
165    pub maintainers_required: usize,
166    pub inter_team_approved: bool,
167    pub team_details: Vec<TeamApprovalStatus>,
168}
169
170#[derive(Debug, Clone)]
171pub struct TeamApprovalStatus {
172    pub team_id: String,
173    pub team_name: String,
174    pub maintainers_signed: usize,
175    pub maintainers_required: usize,
176    pub approved: bool,
177}