@@ -195,6 +195,8 @@ contract BeefyClient {
195
195
*/
196
196
uint256 public immutable minNumRequiredSignatures;
197
197
198
+ uint256 constant fiatShamirRequiredSignatures = 101 ;
199
+
198
200
/* Errors */
199
201
error InvalidBitfield ();
200
202
error InvalidBitfieldLength ();
@@ -399,6 +401,71 @@ contract BeefyClient {
399
401
emit NewMMRRoot (newMMRRoot, commitment.blockNumber);
400
402
}
401
403
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
+
402
469
/**
403
470
* @dev Verify that the supplied MMR leaf is included in the latest verified MMR root.
404
471
* @param leafHash contains the merkle leaf to be verified
@@ -539,6 +606,45 @@ contract BeefyClient {
539
606
}
540
607
}
541
608
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
+
542
648
// Ensure that the commitment provides a new MMR root
543
649
function ensureProvidesMMRRoot (Commitment calldata commitment )
544
650
internal
0 commit comments