Skip to content

Submit Fiat-Shamir #1462

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

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ ignored_error_codes = [
2394,
]

# To clean up, we disabled 3 signature tests. You can regenerate the test fixtures by setting the environment variable
# SIGNATURE_USAGE_COUNT=3 and enabling these tests again.
no_match_test = "testSubmitWith3SignatureCount*"

evm_version = 'Cancun'

[profile.production]
Expand Down
5 changes: 4 additions & 1 deletion contracts/scripts/DeployBeefyClient.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ contract DeployBeefyClient is Script {
uint256 randaoCommitDelay;
uint256 randaoCommitExpiration;
uint256 minimumSignatures;
uint256 fiatShamirRequiredSignatures;
}

function readConfig() internal pure returns (Config memory config) {
Expand All @@ -31,7 +32,8 @@ contract DeployBeefyClient is Script {
}),
randaoCommitDelay: 128,
randaoCommitExpiration: 24,
minimumSignatures: 17
minimumSignatures: 17,
fiatShamirRequiredSignatures: 101
});
}

Expand All @@ -43,6 +45,7 @@ contract DeployBeefyClient is Script {
config.randaoCommitDelay,
config.randaoCommitExpiration,
config.minimumSignatures,
config.fiatShamirRequiredSignatures,
config.startBlock,
config.current,
config.next
Expand Down
9 changes: 8 additions & 1 deletion contracts/scripts/DeployLocal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,15 @@ contract DeployLocal is Script {
uint256 randaoCommitDelay = vm.envUint("RANDAO_COMMIT_DELAY");
uint256 randaoCommitExpiration = vm.envUint("RANDAO_COMMIT_EXP");
uint256 minimumSignatures = vm.envUint("MINIMUM_REQUIRED_SIGNATURES");
uint256 fiatShamirRequiredSignatures = vm.envUint("FIAT_SHAMIR_REQUIRED_SIGNATURES");
BeefyClient beefyClient = new BeefyClient(
randaoCommitDelay, randaoCommitExpiration, minimumSignatures, startBlock, current, next
randaoCommitDelay,
randaoCommitExpiration,
minimumSignatures,
fiatShamirRequiredSignatures,
startBlock,
current,
next
);

uint8 foreignTokenDecimals = uint8(vm.envUint("FOREIGN_TOKEN_DECIMALS"));
Expand Down
142 changes: 140 additions & 2 deletions contracts/src/BeefyClient.sol
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ contract BeefyClient {
*/
uint256 public immutable minNumRequiredSignatures;

uint256 public immutable fiatShamirRequiredSignatures;

/* Errors */
error InvalidBitfield();
error InvalidBitfieldLength();
Expand All @@ -218,6 +220,7 @@ contract BeefyClient {
uint256 _randaoCommitDelay,
uint256 _randaoCommitExpiration,
uint256 _minNumRequiredSignatures,
uint256 _fiatShamirRequiredSignatures,
uint64 _initialBeefyBlock,
ValidatorSet memory _initialValidatorSet,
ValidatorSet memory _nextValidatorSet
Expand All @@ -228,6 +231,7 @@ contract BeefyClient {
randaoCommitDelay = _randaoCommitDelay;
randaoCommitExpiration = _randaoCommitExpiration;
minNumRequiredSignatures = _minNumRequiredSignatures;
fiatShamirRequiredSignatures = _fiatShamirRequiredSignatures;
latestBeefyBlock = _initialBeefyBlock;
currentValidatorSet.id = _initialValidatorSet.id;
currentValidatorSet.length = _initialValidatorSet.length;
Expand Down Expand Up @@ -448,6 +452,96 @@ contract BeefyClient {
);
}

/**
* @dev Helper to create a final bitfield with subsampled validator selections using the Fiat-Shamir approach
* @param commitment contains the full commitment that was used for the commitmentHash
* @param bitfield claiming which validators have signed the commitment
*/
function createFiatShamirFinalBitfield(
Commitment calldata commitment,
uint256[] calldata bitfield
) external view returns (uint256[] memory) {
ValidatorSetState storage vset;
if (commitment.validatorSetID == nextValidatorSet.id) {
vset = nextValidatorSet;
} else if (commitment.validatorSetID == currentValidatorSet.id) {
vset = currentValidatorSet;
} else {
revert InvalidCommitment();
}

bytes32 bitFieldHash = keccak256(abi.encodePacked(bitfield));
bytes32 commitmentHash = keccak256(encodeCommitment(commitment));
bytes32 fiatShamirHash =
sha256(bytes.concat(sha256(bytes.concat(commitmentHash, bitFieldHash, vset.root))));
uint256 requiredSignatures =
Math.min(fiatShamirRequiredSignatures, computeQuorum(vset.length));

Choose a reason for hiding this comment

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

we have 2/3rd +1 honesty assumption on polkadot validators. Hence it is sufficient to check 1/3rd +1 validator signatures to ensure at least 1 honest validator signed the payload. We can get away with min(fiatShamirRequiredSignature, n/3 +1) here. This also sufficient for the random sampling protocol.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

return
Bitfield.subsample(uint256(fiatShamirHash), bitfield, requiredSignatures, vset.length);
}

/**
* @dev Submit a commitment and leaf using the Fiat-Shamir approach
* @param commitment contains the full commitment that was used for the commitmentHash
* @param bitfield claiming which validators have signed the commitment
* @param proofs a struct containing the data needed to verify all validator signatures
* @param leaf an MMR leaf provable using the MMR root in the commitment payload
* @param leafProof an MMR leaf proof
* @param leafProofOrder a bitfield describing the order of each item (left vs right)
*/
function submitFiatShamir(
Commitment calldata commitment,
uint256[] calldata bitfield,
ValidatorProof[] calldata proofs,
MMRLeaf calldata leaf,
bytes32[] calldata leafProof,
uint256 leafProofOrder
) external {
if (commitment.blockNumber <= latestBeefyBlock) {
// ticket is obsolete
revert StaleCommitment();
}

bool is_next_session = false;
ValidatorSetState storage vset;
if (commitment.validatorSetID == nextValidatorSet.id) {
is_next_session = true;
vset = nextValidatorSet;
} else if (commitment.validatorSetID == currentValidatorSet.id) {
vset = currentValidatorSet;
} else {
revert InvalidCommitment();
}

bytes32 commitmentHash = keccak256(encodeCommitment(commitment));

verifyFiatShamirCommitment(commitmentHash, bitfield, vset, proofs);

bytes32 newMMRRoot = ensureProvidesMMRRoot(commitment);

if (is_next_session) {
if (leaf.nextAuthoritySetID != nextValidatorSet.id + 1) {
revert InvalidMMRLeaf();
}
bool leafIsValid = MMRProof.verifyLeafProof(
newMMRRoot, keccak256(encodeMMRLeaf(leaf)), leafProof, leafProofOrder
);
if (!leafIsValid) {
revert InvalidMMRLeafProof();
}
currentValidatorSet = nextValidatorSet;
nextValidatorSet.id = leaf.nextAuthoritySetID;
nextValidatorSet.length = leaf.nextAuthoritySetLen;
nextValidatorSet.root = leaf.nextAuthoritySetRoot;
nextValidatorSet.usageCounters = createUint16Array(leaf.nextAuthoritySetLen);
}

latestMMRRoot = newMMRRoot;
latestBeefyBlock = commitment.blockNumber;

emit NewMMRRoot(newMMRRoot, commitment.blockNumber);
}

/* Internal Functions */

// Creates a unique ticket ID for a new interactive prover-verifier session
Expand Down Expand Up @@ -488,11 +582,11 @@ contract BeefyClient {
}

/**
* @dev Calculates 2/3 majority required for quorum for a given number of validators.
* @dev We have 2/3rd +1 honesty assumption on polkadot validators. Hence it is sufficient to check 1/3rd +1 validator signatures to ensure at least 1 honest validator signed the payload.
* @param numValidators The number of validators in the validator set.
*/
function computeQuorum(uint256 numValidators) internal pure returns (uint256) {
return numValidators - (numValidators - 1) / 3;
return numValidators / 3 + 1;
}

/**
Expand Down Expand Up @@ -539,6 +633,50 @@ contract BeefyClient {
}
}

/**
* @dev Verify commitment with the sampled signatures using the Fiat-Shamir hash
*/
function verifyFiatShamirCommitment(
bytes32 commitmentHash,
uint256[] calldata bitfield,
ValidatorSetState storage vset,
ValidatorProof[] calldata proofs
) internal view {
bytes32 bitFieldHash = keccak256(abi.encodePacked(bitfield));
bytes32 fiatShamirHash =
sha256(bytes.concat(sha256(bytes.concat(commitmentHash, bitFieldHash, vset.root))));
uint256 requiredSignatures =
Math.min(fiatShamirRequiredSignatures, computeQuorum(vset.length));
if (proofs.length != requiredSignatures) {
revert InvalidValidatorProofLength();
}

uint256[] memory finalbitfield =
Bitfield.subsample(uint256(fiatShamirHash), bitfield, requiredSignatures, vset.length);

for (uint256 i = 0; i < proofs.length; i++) {
ValidatorProof calldata proof = proofs[i];

// Check that validator is in bitfield
if (!Bitfield.isSet(finalbitfield, proof.index)) {
revert InvalidValidatorProof();
}

// Check that validator is actually in a validator set
if (!isValidatorInSet(vset, proof.account, proof.index, proof.proof)) {
revert InvalidValidatorProof();
}

// Check that validator signed the commitment
if (ECDSA.recover(commitmentHash, proof.v, proof.r, proof.s) != proof.account) {
revert InvalidSignature();
}

// Ensure no validator can appear more than once in bitfield
Bitfield.unset(finalbitfield, proof.index);
}
}

// Ensure that the commitment provides a new MMR root
function ensureProvidesMMRRoot(Commitment calldata commitment)
internal
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/storage/CoreStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ library CoreStorage {
// Agents
mapping(bytes32 agentID => address) agents;
// Reserve slot to prevent state collision
uint256 private __gap;
uint256 __gap;
// V2
SparseBitmap inboundNonce;
uint64 outboundNonce;
Expand Down
Loading
Loading