Skip to content

Commit e3734b3

Browse files
committed
SubmitFiatShamir
1 parent d60151a commit e3734b3

File tree

1 file changed

+106
-0
lines changed

1 file changed

+106
-0
lines changed

contracts/src/BeefyClient.sol

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ contract BeefyClient {
195195
*/
196196
uint256 public immutable minNumRequiredSignatures;
197197

198+
uint256 constant fiatShamirRequiredSignatures = 101;
199+
198200
/* Errors */
199201
error InvalidBitfield();
200202
error InvalidBitfieldLength();
@@ -399,6 +401,71 @@ contract BeefyClient {
399401
emit NewMMRRoot(newMMRRoot, commitment.blockNumber);
400402
}
401403

404+
/**
405+
* @dev Submit a commitment and leaf using the Fiat-Shamir approach
406+
* @param commitment contains the full commitment that was used for the commitmentHash
407+
* @param bitfield claiming which validators have signed the commitment
408+
* @param proofs a struct containing the data needed to verify all validator signatures
409+
* @param leaf an MMR leaf provable using the MMR root in the commitment payload
410+
* @param leafProof an MMR leaf proof
411+
* @param leafProofOrder a bitfield describing the order of each item (left vs right)
412+
*/
413+
function submitFiatShamir(
414+
Commitment calldata commitment,
415+
uint256[] calldata bitfield,
416+
ValidatorProof[] calldata proofs,
417+
MMRLeaf calldata leaf,
418+
bytes32[] calldata leafProof,
419+
uint256 leafProofOrder
420+
) external {
421+
if (commitment.blockNumber <= latestBeefyBlock) {
422+
// ticket is obsolete
423+
revert StaleCommitment();
424+
}
425+
if (proofs.length != fiatShamirRequiredSignatures) {
426+
revert InvalidValidatorProofLength();
427+
}
428+
429+
bool is_next_session = false;
430+
ValidatorSetState storage vset;
431+
if (commitment.validatorSetID == nextValidatorSet.id) {
432+
is_next_session = true;
433+
vset = nextValidatorSet;
434+
} else if (commitment.validatorSetID == currentValidatorSet.id) {
435+
vset = currentValidatorSet;
436+
} else {
437+
revert InvalidCommitment();
438+
}
439+
440+
bytes32 commitmentHash = keccak256(encodeCommitment(commitment));
441+
442+
verifyFiatShamirCommitment(commitmentHash, bitfield, vset, proofs);
443+
444+
bytes32 newMMRRoot = ensureProvidesMMRRoot(commitment);
445+
446+
if (is_next_session) {
447+
if (leaf.nextAuthoritySetID != nextValidatorSet.id + 1) {
448+
revert InvalidMMRLeaf();
449+
}
450+
bool leafIsValid = MMRProof.verifyLeafProof(
451+
newMMRRoot, keccak256(encodeMMRLeaf(leaf)), leafProof, leafProofOrder
452+
);
453+
if (!leafIsValid) {
454+
revert InvalidMMRLeafProof();
455+
}
456+
currentValidatorSet = nextValidatorSet;
457+
nextValidatorSet.id = leaf.nextAuthoritySetID;
458+
nextValidatorSet.length = leaf.nextAuthoritySetLen;
459+
nextValidatorSet.root = leaf.nextAuthoritySetRoot;
460+
nextValidatorSet.usageCounters = createUint16Array(leaf.nextAuthoritySetLen);
461+
}
462+
463+
latestMMRRoot = newMMRRoot;
464+
latestBeefyBlock = commitment.blockNumber;
465+
466+
emit NewMMRRoot(newMMRRoot, commitment.blockNumber);
467+
}
468+
402469
/**
403470
* @dev Verify that the supplied MMR leaf is included in the latest verified MMR root.
404471
* @param leafHash contains the merkle leaf to be verified
@@ -539,6 +606,45 @@ contract BeefyClient {
539606
}
540607
}
541608

609+
/**
610+
* @dev Verify commitment with the sampled signatures using the Fiat-Shamir hash
611+
*/
612+
function verifyFiatShamirCommitment(
613+
bytes32 commitmentHash,
614+
uint256[] calldata bitfield,
615+
ValidatorSetState storage vset,
616+
ValidatorProof[] calldata proofs
617+
) internal view {
618+
bytes32 bitFieldHash = keccak256(abi.encodePacked(bitfield));
619+
bytes32 fiatShamirHash = keccak256(bytes.concat(commitmentHash, bitFieldHash, vset.root));
620+
621+
uint256[] memory finalbitfield = Bitfield.subsample(
622+
uint256(fiatShamirHash), bitfield, fiatShamirRequiredSignatures, vset.length
623+
);
624+
625+
for (uint256 i = 0; i < proofs.length; i++) {
626+
ValidatorProof calldata proof = proofs[i];
627+
628+
// Check that validator is in bitfield
629+
if (!Bitfield.isSet(finalbitfield, proof.index)) {
630+
revert InvalidValidatorProof();
631+
}
632+
633+
// Check that validator is actually in a validator set
634+
if (!isValidatorInSet(vset, proof.account, proof.index, proof.proof)) {
635+
revert InvalidValidatorProof();
636+
}
637+
638+
// Check that validator signed the commitment
639+
if (ECDSA.recover(commitmentHash, proof.v, proof.r, proof.s) != proof.account) {
640+
revert InvalidSignature();
641+
}
642+
643+
// Ensure no validator can appear more than once in bitfield
644+
Bitfield.unset(finalbitfield, proof.index);
645+
}
646+
}
647+
542648
// Ensure that the commitment provides a new MMR root
543649
function ensureProvidesMMRRoot(Commitment calldata commitment)
544650
internal

0 commit comments

Comments
 (0)