Skip to content

Commit f8f1a8b

Browse files
authored
feat: Implement signature aggregator precursor (#1091)
* feat: Implement signature aggregator precursor * remove extra lines
1 parent d327512 commit f8f1a8b

File tree

17 files changed

+916
-29
lines changed

17 files changed

+916
-29
lines changed

bin/ream/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ version.workspace = true
1414
name = "ream"
1515
path = "src/main.rs"
1616

17+
[features]
18+
default = ["devnet1"]
19+
devnet1 = []
20+
devnet2 = []
21+
1722
[dependencies]
1823
alloy-primitives.workspace = true
1924
anyhow.workspace = true

bin/ream/src/main.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,14 @@ use ream_chain_lean::{
3535
messages::LeanChainServiceMessage, p2p_request::LeanP2PRequest, service::LeanChainService,
3636
};
3737
use ream_checkpoint_sync::initialize_db_from_checkpoint;
38+
#[cfg(feature = "devnet2")]
39+
use ream_consensus_lean::attestation::AggregatedAttestations;
40+
#[cfg(feature = "devnet1")]
41+
use ream_consensus_lean::attestation::Attestation;
42+
#[cfg(feature = "devnet2")]
43+
use ream_consensus_lean::block::BlockSignatures;
3844
use ream_consensus_lean::{
39-
attestation::{Attestation, AttestationData},
45+
attestation::AttestationData,
4046
block::{BlockWithAttestation, SignedBlockWithAttestation},
4147
checkpoint::Checkpoint,
4248
validator::Validator,
@@ -61,6 +67,8 @@ use ream_p2p::{
6167
},
6268
network::lean::{LeanNetworkConfig, LeanNetworkService},
6369
};
70+
#[cfg(feature = "devnet2")]
71+
use ream_post_quantum_crypto::leansig::signature::Signature;
6472
use ream_post_quantum_crypto::leansig::{
6573
private_key::PrivateKey as LeanSigPrivateKey, public_key::PublicKey,
6674
};
@@ -220,6 +228,7 @@ pub async fn run_lean_node(config: LeanNodeConfig, executor: ReamExecutor, ream_
220228
SignedBlockWithAttestation {
221229
message: BlockWithAttestation {
222230
block: genesis_block,
231+
#[cfg(feature = "devnet1")]
223232
proposer_attestation: Attestation {
224233
validator_id: 0,
225234
data: AttestationData {
@@ -229,8 +238,24 @@ pub async fn run_lean_node(config: LeanNodeConfig, executor: ReamExecutor, ream_
229238
source: Checkpoint::default(),
230239
},
231240
},
241+
#[cfg(feature = "devnet2")]
242+
proposer_attestation: AggregatedAttestations {
243+
validator_id: 0,
244+
data: AttestationData {
245+
slot: 0,
246+
head: Checkpoint::default(),
247+
target: Checkpoint::default(),
248+
source: Checkpoint::default(),
249+
},
250+
},
232251
},
252+
#[cfg(feature = "devnet1")]
233253
signature: VariableList::default(),
254+
#[cfg(feature = "devnet2")]
255+
signature: BlockSignatures {
256+
attestation_signatures: VariableList::default(),
257+
proposer_signature: Signature::blank(),
258+
},
234259
},
235260
genesis_state,
236261
lean_db,

crates/common/chain/lean/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ repository.workspace = true
99
rust-version.workspace = true
1010
version.workspace = true
1111

12+
[features]
13+
default = ["devnet1"]
14+
devnet1 = []
15+
devnet2 = []
16+
1217
[dependencies]
1318
alloy-primitives.workspace = true
1419
anyhow.workspace = true

crates/common/chain/lean/src/service.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ impl LeanChainService {
163163
}
164164
LeanChainServiceMessage::ProcessAttestation { signed_attestation, need_gossip } => {
165165
if enabled!(Level::DEBUG) {
166+
#[cfg(feature = "devnet1")]
166167
debug!(
167168
slot = signed_attestation.message.slot(),
168169
head = ?signed_attestation.message.head(),
@@ -171,14 +172,33 @@ impl LeanChainService {
171172
"Processing attestation by Validator {}",
172173
signed_attestation.message.validator_id,
173174
);
175+
#[cfg(feature = "devnet2")]
176+
debug!(
177+
slot = signed_attestation.message.slot,
178+
head = ?signed_attestation.message.head,
179+
source = ?signed_attestation.message.source,
180+
target = ?signed_attestation.message.target,
181+
"Processing attestation by Validator {}",
182+
signed_attestation.validator_id,
183+
);
174184
} else {
185+
#[cfg(feature = "devnet1")]
175186
info!(
176187
slot = signed_attestation.message.slot(),
177188
source_slot = signed_attestation.message.source().slot,
178189
target_slot = signed_attestation.message.target().slot,
179190
"Processing attestation by Validator {}",
180191
signed_attestation.message.validator_id,
181192
);
193+
#[cfg(feature = "devnet2")]
194+
debug!(
195+
slot = signed_attestation.message.slot,
196+
head = ?signed_attestation.message.head,
197+
source = ?signed_attestation.message.source,
198+
target = ?signed_attestation.message.target,
199+
"Processing attestation by Validator {}",
200+
signed_attestation.validator_id,
201+
);
182202
}
183203

184204
if let Err(err) = self.handle_process_attestation(*signed_attestation.clone()).await {

crates/common/consensus/lean/src/attestation.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use tree_hash_derive::TreeHash;
88
use crate::checkpoint::Checkpoint;
99

1010
/// Attestation content describing the validator's observed chain view.
11-
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
11+
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, Hash)]
1212
pub struct AttestationData {
1313
pub slot: u64,
1414
pub head: Checkpoint,
@@ -17,12 +17,14 @@ pub struct AttestationData {
1717
}
1818

1919
/// Validator specific attestation wrapping shared attestation data.
20+
#[cfg(feature = "devnet1")]
2021
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
2122
pub struct Attestation {
2223
pub validator_id: u64,
2324
pub data: AttestationData,
2425
}
2526

27+
#[cfg(feature = "devnet1")]
2628
impl Attestation {
2729
/// Return the attested slot.
2830
pub fn slot(&self) -> u64 {
@@ -45,6 +47,36 @@ impl Attestation {
4547
}
4648
}
4749

50+
#[cfg(feature = "devnet2")]
51+
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
52+
pub struct AggregatedAttestations {
53+
pub validator_id: u64,
54+
pub data: AttestationData,
55+
}
56+
57+
#[cfg(feature = "devnet2")]
58+
impl AggregatedAttestation {
59+
/// Return the attested slot.
60+
pub fn slot(&self) -> u64 {
61+
self.message.slot
62+
}
63+
64+
/// Return the attested head checkpoint.
65+
pub fn head(&self) -> Checkpoint {
66+
self.message.head
67+
}
68+
69+
/// Return the attested target checkpoint.
70+
pub fn target(&self) -> Checkpoint {
71+
self.message.target
72+
}
73+
74+
/// Return the attested source checkpoint.
75+
pub fn source(&self) -> Checkpoint {
76+
self.message.source
77+
}
78+
}
79+
4880
/// Validator attestation bundled with its signature.
4981
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
5082
pub struct SignedAttestation {
@@ -76,7 +108,6 @@ pub struct SignedAggregatedAttestation {
76108

77109
#[cfg(test)]
78110
mod tests {
79-
80111
use alloy_primitives::hex;
81112
use ssz::{Decode, Encode};
82113

@@ -86,6 +117,7 @@ mod tests {
86117
#[test]
87118
fn test_encode_decode_signed_attestation_roundtrip() -> anyhow::Result<()> {
88119
let signed_attestation = SignedAttestation {
120+
#[cfg(feature = "devnet1")]
89121
message: Attestation {
90122
validator_id: 0,
91123
data: AttestationData {
@@ -95,6 +127,15 @@ mod tests {
95127
source: Checkpoint::default(),
96128
},
97129
},
130+
#[cfg(feature = "devnet2")]
131+
message: AttestationData {
132+
slot: 1,
133+
head: Checkpoint::default(),
134+
target: Checkpoint::default(),
135+
source: Checkpoint::default(),
136+
},
137+
#[cfg(feature = "devnet2")]
138+
validator_id: 0,
98139
signature: Signature {
99140
inner: FixedBytes::default(),
100141
},

crates/common/consensus/lean/src/block.rs

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ use ssz_types::{VariableList, typenum::U4096};
88
use tree_hash::TreeHash;
99
use tree_hash_derive::TreeHash;
1010

11-
use crate::{attestation::Attestation, state::LeanState};
11+
#[cfg(feature = "devnet2")]
12+
use crate::attestation::AggregatedAttestation;
13+
#[cfg(feature = "devnet2")]
14+
use crate::attestation::AggregatedAttestations;
15+
#[cfg(feature = "devnet1")]
16+
use crate::attestation::Attestation;
17+
use crate::state::LeanState;
1218

1319
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode)]
1420
pub struct BlockSignatures {
@@ -34,18 +40,34 @@ impl SignedBlockWithAttestation {
3440
) -> anyhow::Result<bool> {
3541
let block = &self.message.block;
3642
let signatures = &self.signature;
43+
#[cfg(feature = "devnet1")]
3744
let mut all_attestations = block.body.attestations.to_vec();
45+
#[cfg(feature = "devnet2")]
46+
let aggregated_attestations = &block.body.attestations;
47+
#[cfg(feature = "devnet2")]
48+
let attestation_signatures = &signatures.attestation_signatures;
3849

50+
#[cfg(feature = "devnet1")]
3951
all_attestations.push(self.message.proposer_attestation.clone());
4052

53+
#[cfg(feature = "devnet1")]
4154
ensure!(
4255
signatures.len() == all_attestations.len(),
4356
"Number of signatures {} does not match number of attestations {}",
4457
signatures.len(),
4558
all_attestations.len(),
4659
);
60+
#[cfg(feature = "devnet2")]
61+
ensure!(
62+
attestation_signatures.len() == aggregated_attestations.len(),
63+
"Number of signatures {} does not match number of attestations {}",
64+
attestation_signatures.len(),
65+
aggregated_attestations.len(),
66+
);
67+
4768
let validators = &parent_state.validators;
4869

70+
#[cfg(feature = "devnet1")]
4971
for (attestation, signature) in all_attestations.iter().zip(signatures.iter()) {
5072
ensure!(
5173
attestation.validator_id < validators.len() as u64,
@@ -69,6 +91,73 @@ impl SignedBlockWithAttestation {
6991
}
7092
}
7193

94+
#[cfg(feature = "devnet2")]
95+
{
96+
let mut signature_iter = attestation_signatures.iter();
97+
for aggregated_attestation in aggregated_attestations.iter() {
98+
let validator_ids: Vec<usize> = aggregated_attestation
99+
.aggregation_bits
100+
.iter()
101+
.enumerate()
102+
.filter(|(_, bit)| *bit)
103+
.map(|(index, _)| index)
104+
.collect();
105+
106+
let attestation_root = aggregated_attestation.message.tree_hash_root();
107+
108+
for validator_id in validator_ids {
109+
let signature = signature_iter.next().ok_or_else(|| {
110+
anyhow!("Missing signature for validator index {validator_id}")
111+
})?;
112+
113+
ensure!(
114+
validator_id < validators.len(),
115+
"Validator index out of range"
116+
);
117+
118+
let validator = validators
119+
.get(validator_id)
120+
.ok_or_else(|| anyhow!("Failed to get validator"))?;
121+
122+
if verify_signatures {
123+
let timer = start_timer(&PQ_SIGNATURE_ATTESTATION_VERIFICATION_TIME, &[]);
124+
ensure!(
125+
signature.verify(
126+
&validator.public_key,
127+
aggregated_attestation.message.slot as u32,
128+
&attestation_root,
129+
)?,
130+
"Attestation signature verification failed"
131+
);
132+
stop_timer(timer);
133+
}
134+
}
135+
}
136+
137+
let proposer_attestation = &self.message.proposer_attestation;
138+
let proposer_signature = &signatures.proposer_signature;
139+
140+
ensure!(
141+
proposer_attestation.validator_id < validators.len() as u64,
142+
"Proposer index out of range"
143+
);
144+
145+
let proposer = validators
146+
.get(proposer_attestation.validator_id as usize)
147+
.ok_or_else(|| anyhow!("Failed to get proposer validator"))?;
148+
149+
if verify_signatures {
150+
ensure!(
151+
proposer_signature.verify(
152+
&proposer.public_key,
153+
proposer_attestation.data.slot as u32,
154+
&proposer_attestation.data.tree_hash_root(),
155+
)?,
156+
"Failed to verify"
157+
);
158+
}
159+
}
160+
72161
Ok(true)
73162
}
74163
}
@@ -77,7 +166,10 @@ impl SignedBlockWithAttestation {
77166
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode)]
78167
pub struct BlockWithAttestation {
79168
pub block: Block,
169+
#[cfg(feature = "devnet1")]
80170
pub proposer_attestation: Attestation,
171+
#[cfg(feature = "devnet2")]
172+
pub proposer_attestation: AggregatedAttestations,
81173
}
82174

83175
/// Represents a block in the Lean chain.
@@ -117,10 +209,10 @@ impl From<Block> for BlockHeader {
117209
/// Represents the body of a block in the Lean chain.
118210
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
119211
pub struct BlockBody {
120-
#[cfg(feature = "devnet2")]
121-
pub attestations: VariableList<AggregatedAttestations, U4096>,
122212
#[cfg(feature = "devnet1")]
123213
pub attestations: VariableList<Attestation, U4096>,
214+
#[cfg(feature = "devnet2")]
215+
pub attestations: VariableList<AggregatedAttestation, U4096>,
124216
}
125217

126218
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
@@ -151,6 +243,7 @@ mod tests {
151243
attestations: Default::default(),
152244
},
153245
},
246+
#[cfg(feature = "devnet1")]
154247
proposer_attestation: Attestation {
155248
validator_id: 0,
156249
data: AttestationData {
@@ -160,8 +253,24 @@ mod tests {
160253
source: Checkpoint::default(),
161254
},
162255
},
256+
#[cfg(feature = "devnet2")]
257+
proposer_attestation: AggregatedAttestations {
258+
validator_id: 0,
259+
data: AttestationData {
260+
slot: 0,
261+
head: Checkpoint::default(),
262+
target: Checkpoint::default(),
263+
source: Checkpoint::default(),
264+
},
265+
},
163266
},
267+
#[cfg(feature = "devnet1")]
164268
signature: VariableList::default(),
269+
#[cfg(feature = "devnet2")]
270+
signature: BlockSignatures {
271+
attestation_signatures: VariableList::default(),
272+
proposer_signature: Signature::blank(),
273+
},
165274
};
166275

167276
let encode = signed_block_with_attestation.as_ssz_bytes();

0 commit comments

Comments
 (0)