1use crate::verify::verify_attestation_parts;
8use crate::{
9 bitmap::{bitmap_load, for_each_set_bit_u256},
10 secp256k1_scalar_is_valid_nonzero, verify_aggregate_over_hash, AttestationError,
11 AttestationPayload, NodeEntry, RegistryTransitionType, RegistryView, SchnorrSignature,
12 SignerXy, VIRTUAL_INDEX,
13};
14
15#[inline(always)]
16pub fn expected_node_index(bit_pos: u32, registry: &RegistryView, apply_remove_remap: bool) -> u32 {
17 if !apply_remove_remap {
18 return bit_pos;
19 }
20 if bit_pos == registry.removed_old_index {
21 VIRTUAL_INDEX
22 } else if registry.last_transition_type == RegistryTransitionType::RemoveSwap
23 && bit_pos == registry.moved_old_index
24 {
25 registry.removed_old_index
26 } else {
27 bit_pos
28 }
29}
30
31#[inline(always)]
32pub fn validate_remove_transition_for_previous(
33 registry: &RegistryView,
34) -> Result<(), AttestationError> {
35 if registry.is_remove_transition() {
36 Ok(())
37 } else {
38 Err(AttestationError::InvalidTransitionAccount)
39 }
40}
41
42pub fn resolve_ordered_signers(
49 entries: &[NodeEntry],
50 registry: &RegistryView,
51 registry_version: u32,
52 signers_bitmap: &[u8; 32],
53 now: i64,
54) -> Result<(u32, Vec<SignerXy>), AttestationError> {
55 let signers = bitmap_load(signers_bitmap);
56 let signer_count = signers.count_ones();
57
58 let is_current = registry_version == registry.current_version;
59 let is_previous_live =
60 registry_version == registry.previous_version && now <= registry.previous_expires_at;
61 if !is_current && !is_previous_live {
62 return Err(AttestationError::InvalidRegistryVersion);
63 }
64
65 let node_count = if is_current {
66 registry.current_node_count
67 } else {
68 registry.previous_node_count
69 };
70
71 if entries.len() != signer_count as usize {
72 return Err(AttestationError::MissingSignerAccount);
73 }
74
75 let apply_remove_remap =
76 !is_current && registry.last_transition_type != RegistryTransitionType::Add;
77 if apply_remove_remap {
78 validate_remove_transition_for_previous(registry)?;
79 }
80
81 let mut ordered = Vec::with_capacity(signer_count as usize);
82 let mut entry_cursor = 0usize;
83 for_each_set_bit_u256(signers, |bit_pos| {
84 let bit_pos = bit_pos as u32;
85 if bit_pos >= node_count {
86 return Err(AttestationError::InvalidSignersBitmap);
87 }
88
89 let entry = entries
90 .get(entry_cursor)
91 .ok_or(AttestationError::MissingSignerAccount)?;
92 entry_cursor = entry_cursor.saturating_add(1);
93
94 let expected_index = expected_node_index(bit_pos, registry, apply_remove_remap);
95 if entry.index != expected_index {
96 return Err(AttestationError::InvalidNodeIndex);
97 }
98 ordered.push((entry.x, entry.y));
99 Ok(())
100 })?;
101
102 if entry_cursor != entries.len() {
103 return Err(AttestationError::MissingSignerAccount);
104 }
105 Ok((node_count, ordered))
106}
107
108#[allow(clippy::too_many_arguments)]
109pub fn verify_attestation_resolved(
110 payload: &AttestationPayload,
111 signature: &SchnorrSignature,
112 registry: &RegistryView,
113 redundancy_buffer: u8,
114 now: i64,
115 entries: &[NodeEntry],
116) -> Result<(), AttestationError> {
117 let (node_count, ordered) = resolve_ordered_signers(
118 entries,
119 registry,
120 payload.registry_version,
121 &signature.signers_bitmap,
122 now,
123 )?;
124 verify_attestation_parts(payload, signature, node_count, redundancy_buffer, &ordered)
125}
126
127#[allow(clippy::too_many_arguments)]
128pub fn verify_aggregate_over_hash_resolved(
129 registry: &RegistryView,
130 registry_version: u32,
131 signers_bitmap: &[u8; 32],
132 agg_sig_s: &[u8; 32],
133 commitment_addr: &[u8; 20],
134 message_hash: &[u8; 32],
135 now: i64,
136 entries: &[NodeEntry],
137) -> Result<bool, AttestationError> {
138 if !secp256k1_scalar_is_valid_nonzero(agg_sig_s) {
139 return Ok(false);
140 }
141 let (_, ordered) =
142 resolve_ordered_signers(entries, registry, registry_version, signers_bitmap, now)?;
143 verify_aggregate_over_hash(&ordered, agg_sig_s, commitment_addr, message_hash)
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use crate::bitmap::{bitmap_set_bit, for_each_set_bit};
150 use crate::fixtures::{
151 CANONICAL_TIMESTAMP, COMMITMENT, PUBKEYS, REDUNDANCY_BUFFER, REGISTERED_NODE_COUNT,
152 REGISTRY_VERSION, S, SIGNATURES_REQUIRED, SIGNERS_BITMAP, SOURCE_ID, VALUE,
153 };
154 use crate::message::compute_message_hash;
155 use libsecp256k1::{PublicKey, PublicKeyFormat};
156
157 fn pubkey_to_entry(index: u32, compressed: &[u8; 33]) -> NodeEntry {
158 let pk = PublicKey::parse_slice(compressed, Some(PublicKeyFormat::Compressed))
159 .expect("fixture pubkey must be a valid curve point");
160 let full = pk.serialize();
161 NodeEntry {
162 index,
163 x: full[1..33].try_into().unwrap(),
164 y: full[33..65].try_into().unwrap(),
165 }
166 }
167
168 fn evm_fixture_registry() -> RegistryView {
169 RegistryView {
170 current_version: REGISTRY_VERSION,
171 previous_version: REGISTRY_VERSION - 1,
172 previous_expires_at: i64::MAX,
173 current_node_count: REGISTERED_NODE_COUNT,
174 previous_node_count: REGISTERED_NODE_COUNT - 1,
175 last_transition_type: RegistryTransitionType::Add,
176 removed_old_index: VIRTUAL_INDEX,
177 moved_old_index: VIRTUAL_INDEX,
178 }
179 }
180
181 fn evm_fixture_payload() -> AttestationPayload {
182 AttestationPayload {
183 value: VALUE,
184 source_id: SOURCE_ID,
185 registry_version: REGISTRY_VERSION,
186 canonical_timestamp: CANONICAL_TIMESTAMP,
187 signatures_required: SIGNATURES_REQUIRED,
188 }
189 }
190
191 fn evm_fixture_signature() -> SchnorrSignature {
192 SchnorrSignature {
193 agg_sig_s: S,
194 commitment_addr: COMMITMENT,
195 signers_bitmap: SIGNERS_BITMAP,
196 }
197 }
198
199 fn evm_fixture_entries_current() -> Vec<NodeEntry> {
200 let mut entries = Vec::new();
201 for_each_set_bit(&SIGNERS_BITMAP, |bit_pos| {
202 entries.push(pubkey_to_entry(bit_pos as u32, &PUBKEYS[bit_pos]));
203 });
204 entries
205 }
206
207 fn remove_swap_registry() -> RegistryView {
208 RegistryView {
209 current_version: 2,
210 previous_version: 1,
211 previous_expires_at: 9_999,
212 current_node_count: 7,
213 previous_node_count: 8,
214 last_transition_type: RegistryTransitionType::RemoveSwap,
215 removed_old_index: 1,
216 moved_old_index: 3,
217 }
218 }
219
220 fn remove_tail_registry() -> RegistryView {
221 let mut registry = remove_swap_registry();
222 registry.last_transition_type = RegistryTransitionType::RemoveTail;
223 registry.removed_old_index = 2;
224 registry.moved_old_index = VIRTUAL_INDEX;
225 registry
226 }
227
228 #[test]
229 fn expected_node_index_remove_swap_remaps_removed_and_moved() {
230 let registry = remove_swap_registry();
231 assert_eq!(expected_node_index(1, ®istry, true), VIRTUAL_INDEX);
232 assert_eq!(expected_node_index(3, ®istry, true), 1);
233 assert_eq!(expected_node_index(0, ®istry, true), 0);
234 }
235
236 #[test]
237 fn expected_node_index_remove_tail_remaps_removed_only() {
238 let registry = remove_tail_registry();
239 assert_eq!(expected_node_index(2, ®istry, true), VIRTUAL_INDEX);
240 assert_eq!(expected_node_index(0, ®istry, true), 0);
241 }
242
243 #[test]
244 fn expected_node_index_without_remove_remap_is_identity() {
245 let registry = remove_swap_registry();
246 assert_eq!(expected_node_index(4, ®istry, false), 4);
247 }
248
249 #[test]
250 fn resolve_ordered_signers_accepts_current_version_evm_fixture() {
251 let registry = evm_fixture_registry();
252 let entries = evm_fixture_entries_current();
253 let (node_count, ordered) =
254 resolve_ordered_signers(&entries, ®istry, REGISTRY_VERSION, &SIGNERS_BITMAP, 0)
255 .expect("current-version fixture must resolve");
256 assert_eq!(node_count, REGISTERED_NODE_COUNT);
257 assert_eq!(ordered.len(), entries.len());
258 }
259
260 #[test]
261 fn resolve_ordered_signers_accepts_previous_version_add_transition() {
262 let registry = RegistryView {
263 current_version: 2,
264 previous_version: 1,
265 previous_expires_at: 9_999,
266 current_node_count: 6,
267 previous_node_count: 5,
268 last_transition_type: RegistryTransitionType::Add,
269 removed_old_index: VIRTUAL_INDEX,
270 moved_old_index: VIRTUAL_INDEX,
271 };
272 let mut signers_bitmap = [0u8; 32];
273 bitmap_set_bit(&mut signers_bitmap, 3);
274
275 let entries = [NodeEntry {
276 index: 3,
277 x: [1u8; 32],
278 y: [2u8; 32],
279 }];
280
281 let (node_count, ordered) = resolve_ordered_signers(
282 &entries,
283 ®istry,
284 registry.previous_version,
285 &signers_bitmap,
286 0,
287 )
288 .expect("add-transition previous version must resolve without remap");
289 assert_eq!(node_count, registry.previous_node_count);
290 assert_eq!(ordered.len(), 1);
291 }
292
293 #[test]
294 fn resolve_ordered_signers_accepts_previous_version_remove_swap() {
295 let registry = remove_swap_registry();
296 let mut signers_bitmap = [0u8; 32];
297 bitmap_set_bit(&mut signers_bitmap, 0);
298 bitmap_set_bit(&mut signers_bitmap, 3);
299
300 let entries = [
301 NodeEntry {
302 index: 0,
303 x: [1u8; 32],
304 y: [2u8; 32],
305 },
306 NodeEntry {
307 index: 1,
308 x: [3u8; 32],
309 y: [4u8; 32],
310 },
311 ];
312
313 let (node_count, ordered) = resolve_ordered_signers(
314 &entries,
315 ®istry,
316 registry.previous_version,
317 &signers_bitmap,
318 0,
319 )
320 .expect("remove-swap previous version must remap moved index");
321 assert_eq!(node_count, registry.previous_node_count);
322 assert_eq!(ordered.len(), 2);
323 assert_eq!(ordered[1], (entries[1].x, entries[1].y));
324 }
325
326 #[test]
327 fn resolve_ordered_signers_accepts_previous_version_remove_tail() {
328 let registry = remove_tail_registry();
329 let mut signers_bitmap = [0u8; 32];
330 bitmap_set_bit(&mut signers_bitmap, 0);
331 bitmap_set_bit(&mut signers_bitmap, 4);
332
333 let entries = [
334 NodeEntry {
335 index: 0,
336 x: [1u8; 32],
337 y: [2u8; 32],
338 },
339 NodeEntry {
340 index: 4,
341 x: [3u8; 32],
342 y: [4u8; 32],
343 },
344 ];
345
346 let (node_count, ordered) = resolve_ordered_signers(
347 &entries,
348 ®istry,
349 registry.previous_version,
350 &signers_bitmap,
351 0,
352 )
353 .expect("remove-tail previous version must resolve unaffected indices");
354 assert_eq!(node_count, registry.previous_node_count);
355 assert_eq!(ordered.len(), 2);
356 }
357
358 #[test]
359 fn resolve_ordered_signers_rejects_out_of_range_bit_on_add_previous() {
360 let registry = RegistryView {
361 current_version: 2,
362 previous_version: 1,
363 previous_expires_at: 9_999,
364 current_node_count: 6,
365 previous_node_count: 5,
366 last_transition_type: RegistryTransitionType::Add,
367 removed_old_index: VIRTUAL_INDEX,
368 moved_old_index: VIRTUAL_INDEX,
369 };
370 let mut signers_bitmap = [0u8; 32];
371 signers_bitmap[0] = 1 << 5;
372
373 let entries = [NodeEntry {
374 index: 5,
375 x: [1u8; 32],
376 y: [2u8; 32],
377 }];
378
379 let err = resolve_ordered_signers(
380 &entries,
381 ®istry,
382 registry.previous_version,
383 &signers_bitmap,
384 0,
385 )
386 .unwrap_err();
387 assert_eq!(err, AttestationError::InvalidSignersBitmap);
388 }
389
390 #[test]
391 fn resolve_ordered_signers_rejects_expired_previous_version() {
392 let registry = RegistryView {
393 current_version: 2,
394 previous_version: 1,
395 previous_expires_at: 100,
396 current_node_count: 6,
397 previous_node_count: 5,
398 last_transition_type: RegistryTransitionType::Add,
399 removed_old_index: VIRTUAL_INDEX,
400 moved_old_index: VIRTUAL_INDEX,
401 };
402 let mut signers_bitmap = [0u8; 32];
403 bitmap_set_bit(&mut signers_bitmap, 2);
404
405 let entries = [NodeEntry {
406 index: 2,
407 x: [1u8; 32],
408 y: [2u8; 32],
409 }];
410
411 let err = resolve_ordered_signers(
412 &entries,
413 ®istry,
414 registry.previous_version,
415 &signers_bitmap,
416 101,
417 )
418 .unwrap_err();
419 assert_eq!(err, AttestationError::InvalidRegistryVersion);
420 }
421
422 #[test]
423 fn verify_attestation_resolved_accepts_evm_fixture() {
424 let registry = evm_fixture_registry();
425 let payload = evm_fixture_payload();
426 let signature = evm_fixture_signature();
427 let entries = evm_fixture_entries_current();
428
429 verify_attestation_resolved(
430 &payload,
431 &signature,
432 ®istry,
433 REDUNDANCY_BUFFER,
434 0,
435 &entries,
436 )
437 .expect("resolved-path EVM fixture must verify");
438 }
439
440 #[test]
441 fn verify_aggregate_over_hash_resolved_roundtrip() {
442 let registry = evm_fixture_registry();
443 let payload = evm_fixture_payload();
444 let signature = evm_fixture_signature();
445 let entries = evm_fixture_entries_current();
446 let message_hash = compute_message_hash(&payload, signature.signers_bitmap);
447
448 assert!(verify_aggregate_over_hash_resolved(
449 ®istry,
450 REGISTRY_VERSION,
451 &signature.signers_bitmap,
452 &signature.agg_sig_s,
453 &signature.commitment_addr,
454 &message_hash,
455 0,
456 &entries,
457 )
458 .unwrap());
459 }
460
461 #[test]
462 fn verify_aggregate_over_hash_resolved_rejects_tampered_hash() {
463 let registry = evm_fixture_registry();
464 let payload = evm_fixture_payload();
465 let signature = evm_fixture_signature();
466 let entries = evm_fixture_entries_current();
467 let mut message_hash = compute_message_hash(&payload, signature.signers_bitmap);
468 message_hash[0] ^= 0xff;
469
470 assert!(!verify_aggregate_over_hash_resolved(
471 ®istry,
472 REGISTRY_VERSION,
473 &signature.signers_bitmap,
474 &signature.agg_sig_s,
475 &signature.commitment_addr,
476 &message_hash,
477 0,
478 &entries,
479 )
480 .unwrap());
481 }
482}