Skip to content

Commit 29be6f6

Browse files
authored
Merge pull request #2548 from input-output-hk/curiecrypt/module-aggregate-signature
Organize STM - Module Aggregate Signature
2 parents ca7cbd9 + 2480c3e commit 29be6f6

File tree

9 files changed

+1114
-1070
lines changed

9 files changed

+1114
-1070
lines changed

mithril-stm/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## 0.4.2 (04-06-2025)
9+
10+
### Added
11+
12+
- Added a `aggregate_signature` module and `StmAggrSig`, `StmAggrVerificationKey`, `StmClerk` and `CoreVerifier` functionality covered by its submodules.
13+
814
## 0.4.1 (04-06-2025)
915

1016
### Added

mithril-stm/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-stm"
3-
version = "0.4.1"
3+
version = "0.4.2"
44
edition = { workspace = true }
55
authors = { workspace = true }
66
homepage = { workspace = true }
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use blake2::digest::{Digest, FixedOutput};
2+
use serde::{Deserialize, Serialize};
3+
4+
use crate::merkle_tree::{BatchPath, MerkleTreeCommitmentBatchCompat};
5+
use crate::{ClosedKeyReg, Stake};
6+
7+
/// Stm aggregate key (batch compatible), which contains the merkle tree commitment and the total stake of the system.
8+
/// Batch Compat Merkle tree commitment includes the number of leaves in the tree in order to obtain batch path.
9+
#[derive(Debug, Clone, Serialize, Deserialize)]
10+
#[serde(bound(
11+
serialize = "BatchPath<D>: Serialize",
12+
deserialize = "BatchPath<D>: Deserialize<'de>"
13+
))]
14+
pub struct StmAggrVerificationKey<D: Clone + Digest + FixedOutput> {
15+
mt_commitment: MerkleTreeCommitmentBatchCompat<D>,
16+
total_stake: Stake,
17+
}
18+
19+
impl<D: Digest + Clone + FixedOutput> StmAggrVerificationKey<D> {
20+
pub fn get_mt_commitment(&self) -> MerkleTreeCommitmentBatchCompat<D> {
21+
self.mt_commitment.clone()
22+
}
23+
24+
pub fn get_total_stake(&self) -> Stake {
25+
self.total_stake
26+
}
27+
}
28+
29+
impl<D: Digest + Clone + FixedOutput> PartialEq for StmAggrVerificationKey<D> {
30+
fn eq(&self, other: &Self) -> bool {
31+
self.mt_commitment == other.mt_commitment && self.total_stake == other.total_stake
32+
}
33+
}
34+
35+
impl<D: Digest + Clone + FixedOutput> Eq for StmAggrVerificationKey<D> {}
36+
37+
impl<D: Clone + Digest + FixedOutput> From<&ClosedKeyReg<D>> for StmAggrVerificationKey<D> {
38+
fn from(reg: &ClosedKeyReg<D>) -> Self {
39+
Self {
40+
mt_commitment: reg.merkle_tree.to_commitment_batch_compat(),
41+
total_stake: reg.total_stake,
42+
}
43+
}
44+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use blake2::digest::{Digest, FixedOutput};
2+
3+
use crate::{
4+
AggregationError, ClosedKeyReg, CoreVerifier, Index, Stake, StmAggrSig, StmAggrVerificationKey,
5+
StmParameters, StmSig, StmSigRegParty, StmSigner, StmVerificationKey,
6+
};
7+
8+
/// `StmClerk` can verify and aggregate `StmSig`s and verify `StmMultiSig`s.
9+
/// Clerks can only be generated with the registration closed.
10+
/// This avoids that a Merkle Tree is computed before all parties have registered.
11+
#[derive(Debug, Clone)]
12+
pub struct StmClerk<D: Clone + Digest> {
13+
pub(crate) closed_reg: ClosedKeyReg<D>,
14+
pub(crate) params: StmParameters,
15+
}
16+
17+
impl<D: Digest + Clone + FixedOutput> StmClerk<D> {
18+
/// Create a new `Clerk` from a closed registration instance.
19+
pub fn from_registration(params: &StmParameters, closed_reg: &ClosedKeyReg<D>) -> Self {
20+
Self {
21+
params: *params,
22+
closed_reg: closed_reg.clone(),
23+
}
24+
}
25+
26+
/// Create a Clerk from a signer.
27+
pub fn from_signer(signer: &StmSigner<D>) -> Self {
28+
let closed_reg = signer
29+
.get_closed_reg()
30+
.clone()
31+
.expect("Core signer does not include closed registration. StmClerk, and so, the Stm certificate cannot be built without closed registration!")
32+
;
33+
34+
Self {
35+
params: signer.get_params(),
36+
closed_reg,
37+
}
38+
}
39+
40+
/// Aggregate a set of signatures for their corresponding indices.
41+
///
42+
/// This function first deduplicates the repeated signatures, and if there are enough signatures, it collects the merkle tree indexes of unique signatures.
43+
/// The list of merkle tree indexes is used to create a batch proof, to prove that all signatures are from eligible signers.
44+
///
45+
/// It returns an instance of `StmAggrSig`.
46+
pub fn aggregate(
47+
&self,
48+
sigs: &[StmSig],
49+
msg: &[u8],
50+
) -> Result<StmAggrSig<D>, AggregationError> {
51+
let sig_reg_list = sigs
52+
.iter()
53+
.map(|sig| StmSigRegParty {
54+
sig: sig.clone(),
55+
reg_party: self.closed_reg.reg_parties[sig.signer_index as usize],
56+
})
57+
.collect::<Vec<StmSigRegParty>>();
58+
59+
let avk = StmAggrVerificationKey::from(&self.closed_reg);
60+
let msgp = avk.get_mt_commitment().concat_with_msg(msg);
61+
let mut unique_sigs = CoreVerifier::dedup_sigs_for_indices(
62+
&self.closed_reg.total_stake,
63+
&self.params,
64+
&msgp,
65+
&sig_reg_list,
66+
)?;
67+
68+
unique_sigs.sort_unstable();
69+
70+
let mt_index_list = unique_sigs
71+
.iter()
72+
.map(|sig_reg| sig_reg.sig.signer_index as usize)
73+
.collect::<Vec<usize>>();
74+
75+
let batch_proof = self.closed_reg.merkle_tree.get_batched_path(mt_index_list);
76+
77+
Ok(StmAggrSig {
78+
signatures: unique_sigs,
79+
batch_proof,
80+
})
81+
}
82+
83+
/// Compute the `StmAggrVerificationKey` related to the used registration.
84+
pub fn compute_avk(&self) -> StmAggrVerificationKey<D> {
85+
StmAggrVerificationKey::from(&self.closed_reg)
86+
}
87+
88+
/// Get the (VK, stake) of a party given its index.
89+
pub fn get_reg_party(&self, party_index: &Index) -> Option<(StmVerificationKey, Stake)> {
90+
self.closed_reg
91+
.reg_parties
92+
.get(*party_index as usize)
93+
.map(|&r| r.into())
94+
}
95+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
use std::collections::{BTreeMap, HashMap, HashSet};
2+
3+
use crate::bls_multi_signature::{Signature, VerificationKey};
4+
use crate::key_reg::RegParty;
5+
use crate::merkle_tree::MTLeaf;
6+
use crate::{
7+
AggregationError, CoreVerifierError, Index, Stake, StmParameters, StmSig, StmSigRegParty,
8+
};
9+
10+
/// Full node verifier including the list of eligible signers and the total stake of the system.
11+
pub struct CoreVerifier {
12+
/// List of registered parties.
13+
pub eligible_parties: Vec<RegParty>,
14+
/// Total stake of registered parties.
15+
pub total_stake: Stake,
16+
}
17+
18+
impl CoreVerifier {
19+
/// Setup a core verifier for given list of signers.
20+
/// * Collect the unique signers in a hash set,
21+
/// * Calculate the total stake of the eligible signers,
22+
/// * Sort the eligible signers.
23+
pub fn setup(public_signers: &[(VerificationKey, Stake)]) -> Self {
24+
let mut total_stake: Stake = 0;
25+
let mut unique_parties = HashSet::new();
26+
for signer in public_signers.iter() {
27+
let (res, overflow) = total_stake.overflowing_add(signer.1);
28+
if overflow {
29+
panic!("Total stake overflow");
30+
}
31+
total_stake = res;
32+
unique_parties.insert(MTLeaf(signer.0, signer.1));
33+
}
34+
35+
let mut eligible_parties: Vec<_> = unique_parties.into_iter().collect();
36+
eligible_parties.sort_unstable();
37+
CoreVerifier {
38+
eligible_parties,
39+
total_stake,
40+
}
41+
}
42+
43+
/// Preliminary verification that checks whether indices are unique and the quorum is achieved.
44+
pub(crate) fn preliminary_verify(
45+
total_stake: &Stake,
46+
signatures: &[StmSigRegParty],
47+
parameters: &StmParameters,
48+
msg: &[u8],
49+
) -> Result<(), CoreVerifierError> {
50+
let mut nr_indices = 0;
51+
let mut unique_indices = HashSet::new();
52+
53+
for sig_reg in signatures {
54+
sig_reg
55+
.sig
56+
.check_indices(parameters, &sig_reg.reg_party.1, msg, total_stake)?;
57+
for &index in &sig_reg.sig.indexes {
58+
unique_indices.insert(index);
59+
nr_indices += 1;
60+
}
61+
}
62+
63+
if nr_indices != unique_indices.len() {
64+
return Err(CoreVerifierError::IndexNotUnique);
65+
}
66+
if (nr_indices as u64) < parameters.k {
67+
return Err(CoreVerifierError::NoQuorum(nr_indices as u64, parameters.k));
68+
}
69+
70+
Ok(())
71+
}
72+
73+
/// Given a slice of `sig_reg_list`, this function returns a new list of `sig_reg_list` with only valid indices.
74+
/// In case of conflict (having several signatures for the same index)
75+
/// it selects the smallest signature (i.e. takes the signature with the smallest scalar).
76+
/// The function selects at least `self.k` indexes.
77+
/// # Error
78+
/// If there is no sufficient signatures, then the function fails.
79+
// todo: We need to agree on a criteria to dedup (by default we use a BTreeMap that guarantees keys order)
80+
// todo: not good, because it only removes index if there is a conflict (see benches)
81+
pub fn dedup_sigs_for_indices(
82+
total_stake: &Stake,
83+
params: &StmParameters,
84+
msg: &[u8],
85+
sigs: &[StmSigRegParty],
86+
) -> Result<Vec<StmSigRegParty>, AggregationError> {
87+
let mut sig_by_index: BTreeMap<Index, &StmSigRegParty> = BTreeMap::new();
88+
let mut removal_idx_by_vk: HashMap<&StmSigRegParty, Vec<Index>> = HashMap::new();
89+
90+
for sig_reg in sigs.iter() {
91+
if sig_reg
92+
.sig
93+
.verify_core(
94+
params,
95+
&sig_reg.reg_party.0,
96+
&sig_reg.reg_party.1,
97+
msg,
98+
total_stake,
99+
)
100+
.is_err()
101+
{
102+
continue;
103+
}
104+
for index in sig_reg.sig.indexes.iter() {
105+
let mut insert_this_sig = false;
106+
if let Some(&previous_sig) = sig_by_index.get(index) {
107+
let sig_to_remove_index = if sig_reg.sig.sigma < previous_sig.sig.sigma {
108+
insert_this_sig = true;
109+
previous_sig
110+
} else {
111+
sig_reg
112+
};
113+
114+
if let Some(indexes) = removal_idx_by_vk.get_mut(sig_to_remove_index) {
115+
indexes.push(*index);
116+
} else {
117+
removal_idx_by_vk.insert(sig_to_remove_index, vec![*index]);
118+
}
119+
} else {
120+
insert_this_sig = true;
121+
}
122+
123+
if insert_this_sig {
124+
sig_by_index.insert(*index, sig_reg);
125+
}
126+
}
127+
}
128+
129+
let mut dedup_sigs: HashSet<StmSigRegParty> = HashSet::new();
130+
let mut count: u64 = 0;
131+
132+
for (_, &sig_reg) in sig_by_index.iter() {
133+
if dedup_sigs.contains(sig_reg) {
134+
continue;
135+
}
136+
let mut deduped_sig = sig_reg.clone();
137+
if let Some(indexes) = removal_idx_by_vk.get(sig_reg) {
138+
deduped_sig.sig.indexes = deduped_sig
139+
.sig
140+
.indexes
141+
.clone()
142+
.into_iter()
143+
.filter(|i| !indexes.contains(i))
144+
.collect();
145+
}
146+
147+
let size: Result<u64, _> = deduped_sig.sig.indexes.len().try_into();
148+
if let Ok(size) = size {
149+
if dedup_sigs.contains(&deduped_sig) {
150+
panic!("Should not reach!");
151+
}
152+
dedup_sigs.insert(deduped_sig);
153+
count += size;
154+
155+
if count >= params.k {
156+
return Ok(dedup_sigs.into_iter().collect());
157+
}
158+
}
159+
}
160+
161+
Err(AggregationError::NotEnoughSignatures(count, params.k))
162+
}
163+
164+
/// Collect and return `Vec<Signature>, Vec<VerificationKey>` which will be used
165+
/// by the aggregate verification.
166+
pub(crate) fn collect_sigs_vks(
167+
sig_reg_list: &[StmSigRegParty],
168+
) -> (Vec<Signature>, Vec<VerificationKey>) {
169+
let sigs = sig_reg_list
170+
.iter()
171+
.map(|sig_reg| sig_reg.sig.sigma)
172+
.collect::<Vec<Signature>>();
173+
let vks = sig_reg_list
174+
.iter()
175+
.map(|sig_reg| sig_reg.reg_party.0)
176+
.collect::<Vec<VerificationKey>>();
177+
178+
(sigs, vks)
179+
}
180+
181+
/// Core verification
182+
///
183+
/// Verify a list of signatures with respect to given message with given parameters.
184+
pub fn verify(
185+
&self,
186+
signatures: &[StmSig],
187+
parameters: &StmParameters,
188+
msg: &[u8],
189+
) -> Result<(), CoreVerifierError> {
190+
let sig_reg_list = signatures
191+
.iter()
192+
.map(|sig| StmSigRegParty {
193+
sig: sig.clone(),
194+
reg_party: self.eligible_parties[sig.signer_index as usize],
195+
})
196+
.collect::<Vec<StmSigRegParty>>();
197+
198+
let unique_sigs =
199+
Self::dedup_sigs_for_indices(&self.total_stake, parameters, msg, &sig_reg_list)?;
200+
201+
Self::preliminary_verify(&self.total_stake, &unique_sigs, parameters, msg)?;
202+
203+
let (sigs, vks) = Self::collect_sigs_vks(&unique_sigs);
204+
205+
Signature::verify_aggregate(msg.to_vec().as_slice(), &vks, &sigs)?;
206+
207+
Ok(())
208+
}
209+
}

0 commit comments

Comments
 (0)