blvm_sdk/governance/
nested_multisig.rs1use crate::governance::error::{GovernanceError, GovernanceResult};
7use crate::governance::{PublicKey, Signature};
8
9#[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#[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 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, });
50 }
51
52 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 pub fn verify(
78 &self,
79 message: &[u8],
80 signatures: &[(String, Signature)], ) -> GovernanceResult<NestedMultisigResult> {
82 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 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 let mut valid_sigs = 0;
103 if let Some(sigs) = team_signatures.get(&team.id) {
104 for (github, sig) in sigs {
105 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 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#[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}