Skip to content

Conversation

spalladino
Copy link
Contributor

@spalladino spalladino commented Jul 23, 2025

Feistel permutations remove the need for auxiliary storage for tracking replacements, and most importantly, allow us to compute a given index permutation in constant time, which allows us to get the proposer for a given committee without having to compute all indices.

Props to Claude for the idea and implementation.

Builds on #15813

Median propose cost is 306000.

setOverrideValue(sampledIndices[i], 0);
}
// Use a Feistel network to create a permutation of [0, _indexCount)
uint256 permutedIndex = feistelPermute(_index, _indexCount, _seed);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we should be using the do/while structure here that's done in the batch function instead of calling feistelPermute and doing redundant setup.

}

function testConstantTimeNoDuplicates(uint8 _committeeSize, uint16 _indexCount, uint256 _seed) public {
vm.assume(_committeeSize <= _indexCount);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bound would be better here.

assertEq(sampler.computeSampleIndex(_index, 0, _seed), 0);
}

function testConstantTimeCommitteeMember() public {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test name is a little misleading I think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like what the test is doing though!

function testConstantTimeNoDuplicates(uint8 _committeeSize, uint16 _indexCount, uint256 _seed) public {
vm.assume(_committeeSize <= _indexCount);
vm.assume(_committeeSize > 0 && _committeeSize <= 100); // Reasonable bounds for testing
vm.assume(_indexCount > 0 && _indexCount <= 1000);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the index count should be closer to 20000 if it doesn't make it too slow.

uint256 validatorSetSize = StakingLib.getAttesterCountAtTime(Timestamp.wrap(ts));
uint256 targetCommitteeSize = store.targetCommitteeSize;

if (targetCommitteeSize == 0 || validatorSetSize < targetCommitteeSize) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. This is actually a breaking change. Currently nodes expect getProposer/getCommittee to revert if the current set size is not large enough.

Unless we have a reason to change it, I think we should keep the current behavior.

Copy link
Collaborator

@just-mitch just-mitch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Curious how you prompted claude to this? I have some minor nits in the tests, an optimization, and a bigger one around reverting when the validator set is not large enough.

@spalladino
Copy link
Contributor Author

Curious how you prompted claude to this

I asked it to look into SampleLib, and figure out a way to compute the sampling index of a single committee member without having to recompute all others. It started by keeping the same algorithm, but exiting early when it got to the requested index, so I prompted it to do it in constant time. Then it suggested feistel, and went all in. I was pleasantly surprised.

@spalladino
Copy link
Contributor Author

Closing in favor of sending signers in calldata as discussed

@spalladino spalladino closed this Jul 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants