-
Notifications
You must be signed in to change notification settings - Fork 27
feat: add signature aggregation using python bindings #238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
db09205 to
d5c1566
Compare
|
In my bindings of I've commented out testcases related to invalid signatures as current the aggregation & verification is in test mode. Once we have working |
packages/testing/src/consensus_testing/test_fixtures/fork_choice.py
Outdated
Show resolved
Hide resolved
5caa25a to
a8af346
Compare
6661a38 to
a763fc4
Compare
|
I wonder for the process of If I understand these parts correctly (and I have low confidence I do 😅 ), I think these are all to do with re-using/merging/splitting/deduplicating aggregated attestations that are the complex ones:
But can we say that:
If this all makes sense then maybe we can drop all the aggregation merging and splitting from the specs and only need 2 features around aggregation?
|
| else: | ||
| signature = Signature( | ||
| path=HashTreeOpening(siblings=HashDigestList(data=[])), | ||
| rho=Randomness(data=[Fp(0) for _ in range(PROD_CONFIG.RAND_LEN_FE)]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that we should have any PROD_CONFIG in the codebase, we should use the env variable right?
| For each aggregated attestation, collect the participating validators' public keys and | ||
| signatures, then produce a single leanVM aggregated signature proof blob using | ||
| `xmss_aggregate_signatures` (via `aggregate_signatures`). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These implementation details should change in the future (especially when we will not rely on the bindings anylire) so no need to add them here in the doc.
| For each aggregated attestation, collect the participating validators' public keys and | |
| signatures, then produce a single leanVM aggregated signature proof blob using | |
| `xmss_aggregate_signatures` (via `aggregate_signatures`). | |
| For each aggregated attestation, collect the participating validators' public keys and | |
| signatures, then produce a single leanVM aggregated signature proof. |
| return True | ||
| seen.add(attestation.data) | ||
| return False | ||
| def each_duplicate_attestation_has_unique_participant(self) -> bool: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here maybe we could have a less verbose name such as validate_unique_contributions ? Or maybe another idea?
| groups: dict[bytes, list[AggregationBits]] = defaultdict(list) | ||
|
|
||
| for att in self: | ||
| groups[att.data.data_root_bytes()].append(att.aggregation_bits) | ||
|
|
||
| for bits_list in groups.values(): | ||
| if len(bits_list) <= 1: | ||
| continue | ||
|
|
||
| counts: Counter[int] = Counter() | ||
|
|
||
| # Pass 1: count participants across the group | ||
| for bits in bits_list: | ||
| for i, bit in enumerate(bits.data): | ||
| if bit: | ||
| counts[i] += 1 | ||
|
|
||
| # Pass 2: each attestation must have a participant that appears exactly once | ||
| for bits in bits_list: | ||
| unique = False | ||
| for i, bit in enumerate(bits.data): | ||
| if bit and counts[i] == 1: | ||
| unique = True | ||
| break | ||
| if not unique: | ||
| return False | ||
|
|
||
| return True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like this should be more pythonic and compact no?
groups = defaultdict(list)
for att in self:
groups[att.data.data_root_bytes()].append(att.aggregation_bits)
for bits_list in groups.values():
if len(bits_list) <= 1:
continue
# 1. Convert bitfields to sets of active indices
sets = [{i for i, bit in enumerate(bits.data) if bit} for bits in bits_list]
# 2. Count index occurrences across the entire group
counts = Counter(idx for s in sets for idx in s)
# 3. Verify EVERY attestation has ANY index that appears EXACTLY once
if not all(any(counts[i] == 1 for i in s) for s in sets):
return False
return TrueThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes looks simple to read
| def make_attestation(validator_index: int, data: AttestationData) -> Attestation: | ||
| """Create an attestation for the provided validator.""" | ||
| return Attestation(validator_id=Uint64(validator_index), data=data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same I think that is useless
| # ============================================================================ | ||
| # Additional edge case tests for _aggregate_signatures_from_gossip | ||
| # ============================================================================ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for this we can directly put the other tests without this comment.
| # ============================================================================ | |
| # Additional edge case tests for _aggregate_signatures_from_gossip | |
| # ============================================================================ |
| # ============================================================================ | ||
| # Additional edge case tests for _aggregate_signatures_from_block_payload | ||
| # ============================================================================ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same
| # ============================================================================ | |
| # Additional edge case tests for _aggregate_signatures_from_block_payload | |
| # ============================================================================ |
| # ============================================================================ | ||
| # Additional edge case tests for split_aggregated_attestations | ||
| # ============================================================================ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| # ============================================================================ | |
| # Additional edge case tests for split_aggregated_attestations | |
| # ============================================================================ |
| # ============================================================================ | ||
| # Additional edge case tests for compute_aggregated_signatures | ||
| # ============================================================================ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| # ============================================================================ | |
| # Additional edge case tests for compute_aggregated_signatures | |
| # ============================================================================ |
| aggregated_payloads and aggregated_payloads.get(attestation_key) | ||
| ) | ||
|
|
||
| if has_gossip_sig or has_block_payload: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this condition should always be true, we just need to track if we have gossip sig for it or not
unnawut
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to spend a bit more time to understand compute_aggregated_signatures() and split_aggregated_attestations() and the tests, but like Thomas said these can go separate conversations.
|
I've removed In the updated logic there is a possibility that there are some attestations that are do not contribute with any unique validator, this is fine as in the following devnet, we will recursively aggregate the attestations with same Other changes are nicely described here |
🗒️ Description
Supports aggregation as per our discussion for devnet2.
The current implementation of aggregation bindings don't actually aggregate the signatures and instead return a single 0 byte, same for the verification API. This will be updated when we have aggregation support for current signature encoding scheme on TEST_CONFIG.
✅ Checklist
toxchecks to avoid unnecessary CI fails:uvx tox