Skip to content

Commit

Permalink
improve docs
Browse files Browse the repository at this point in the history
  • Loading branch information
vgeddes committed Nov 5, 2023
1 parent 67707cd commit a4968e1
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 137 deletions.
166 changes: 77 additions & 89 deletions contracts/src/BeefyClient.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,11 @@ import {ScaleCodec} from "./utils/ScaleCodec.sol";
*
* High-level documentation at https://docs.snowbridge.network/architecture/verification/polkadot
*
* To submit new commitments signed by the current validator set, relayers must call
* the following methods sequentially:
* 1. submitInitial
* 2. commitPrevRandao
* 3. createFinalBitfield (this is just a call, not a transaction, to generate the validator subsampling)
* 4. submitFinal (with signature proofs specified by (3))
*
* If the a commitment is signed by the next validator set, relayers must call
* the following methods sequentially:
* 1. submitInitialWithHandover
* 2. commitPrevRandao
* 3. createFinalBitfield (this is just a call, not a transaction, to generate the validator subsampling)
* 4. submitFinalWithHandover (with signature proofs specified by (3))
* To submit new commitments, relayers must call the following methods sequentially:
* 1. submitInitial: Setup the session for the interactive submission
* 2. commitPrevRandao: Commit to a random seed for generating a validator subsampling
* 3. createFinalBitfield: Generate the validator subsampling
* 4. submitFinal: Complete submission after providing the request validator signatures
*
*/
contract BeefyClient {
Expand All @@ -50,120 +42,122 @@ contract BeefyClient {
* @dev The Commitment, with its payload, is the core thing we are trying to verify with
* this contract. It contains an MMR root that commits to the polkadot history, including
* past blocks and parachain blocks and can be used to verify both polkadot and parachain blocks.
* @param blockNumber relay chain block number
* @param validatorSetID id of the validator set that signed the commitment
* @param payload the payload of the new commitment in beefy justifications (in
* our case, this is a new MMR root for all past polkadot blocks)
*/
struct Commitment {
// Relay chain block number
uint32 blockNumber;
// ID of the validator set that signed the commitment
uint64 validatorSetID;
// The payload of the new commitment in beefy justifications (in
// our case, this is a new MMR root for all past polkadot blocks)
PayloadItem[] payload;
}

/**
* @dev Each PayloadItem is a piece of data signed by validators at a particular block.
* This includes the relay chain's MMR root.
* @param payloadID an ID that references a description of the data in the payload item.
* Known payload ids can be found [upstream](https://github.com/paritytech/substrate/blob/fe1f8ba1c4f23931ae89c1ada35efb3d908b50f5/primitives/consensus/beefy/src/payload.rs#L27).
* @param data the contents of the payload item.
*/
struct PayloadItem {
// An ID that references a description of the data in the payload item.
// Known payload ids can be found [upstream](https://github.com/paritytech/substrate/blob/fe1f8ba1c4f23931ae89c1ada35efb3d908b50f5/primitives/consensus/beefy/src/payload.rs#L27).
bytes2 payloadID;
// The contents of the payload item
bytes data;
}

/**
* @dev The ValidatorProof is a proof used to verify a commitment signature
* @param v the parity bit to specify the intended solution
* @param r the x component on the secp256k1 curve
* @param s the challenge solution
* @param index index of the validator address in the merkle tree
* @param account validator address
* @param proof merkle proof for the validator
*/
struct ValidatorProof {
// The parity bit to specify the intended solution
uint8 v;
// The x component on the secp256k1 curve
bytes32 r;
// The challenge solution
bytes32 s;
// Leaf index of the validator address in the merkle tree
uint256 index;
// Validator address
address account;
// Merkle proof for the validator
bytes32[] proof;
}

/**
* @dev A ticket tracks working state for the interactive submission of new commitments
* @param sender the sender of the initial transaction
* @param bitfield a bitfield signalling which validators they claim have signed
* @param blockNumber the block number for this commitment
* @param validatorSetLen the length of the validator set for this commitment
* @param numRequiredSignatures the number of signatures required
* @param bitfield a bitfield signalling which validators they claim have signed
*/
struct Ticket {
// The block number this ticket was issued
uint64 blockNumber;
// Length of the validator set that signed the commitment
uint32 validatorSetLen;
// The number of signatures required
uint32 numRequiredSignatures;
// The PREVRANDAO seed selected for this ticket session
uint256 prevRandao;
// Hash of a bitfield claiming which validators have signed
bytes32 bitfieldHash;
}

/**
* @dev The MMRLeaf is the structure of each leaf in each MMR that each commitment's payload commits to.
* @param version version of the leaf type
* @param parentNumber parent number of the block this leaf describes
* @param parentHash parent hash of the block this leaf describes
* @param parachainHeadsRoot merkle root of all parachain headers in this block
* @param nextAuthoritySetID validator set id that will be part of consensus for the next block
* @param nextAuthoritySetLen length of that validator set
* @param nextAuthoritySetRoot merkle root of all public keys in that validator set
*/
/// @dev The MMRLeaf describes the leaf structure of the MMR
struct MMRLeaf {
// Version of the leaf type
uint8 version;
// Parent number of the block this leaf describes
uint32 parentNumber;
// Parent hash of the block this leaf describes
bytes32 parentHash;
// Validator set id that will be part of consensus for the next block
uint64 nextAuthoritySetID;
// Length of that validator set
uint32 nextAuthoritySetLen;
// Merkle root of all public keys in that validator set
bytes32 nextAuthoritySetRoot;
// Merkle root of all parachain headers in this block
bytes32 parachainHeadsRoot;
}

/**
* @dev The ValidatorSet describes a BEEFY validator set
* @param id identifier for the set
* @param length number of validators in the set
* @param root Merkle root of BEEFY validator addresses
*/
struct ValidatorSet {
// Identifier for the set
uint128 id;
// Number of validators in the set
uint128 length;
// Merkle root of BEEFY validator addresses
bytes32 root;
}

/**
* @dev The ValidatorSet describes a BEEFY validator set
* @param id identifier for the set
* @param length number of validators in the set
* @param root Merkle root of BEEFY validator addresses
* @dev The ValidatorSetState describes a BEEFY validator set along with signature usage counters
*/
struct ValidatorSetState {
// Identifier for the set
uint128 id;
// Number of validators in the set
uint128 length;
// Merkle root of BEEFY validator addresses
bytes32 root;
// Number of times a validator signature has been used
Uint16Array.Array usageCounters;
}

/* State */

// The latest verified MMRRoot and corresponding BlockNumber from the Polkadot relay chain
/// @dev The latest verified MMR root
bytes32 public latestMMRRoot;

/// @dev The block number in the relay chain in which the latest MMR root was emitted
uint64 public latestBeefyBlock;

/// @dev State of the current validator set
ValidatorSetState public currentValidatorSet;

/// @dev State of the next validator set
ValidatorSetState public nextValidatorSet;

// Currently pending tickets for commitment submission
mapping(bytes32 => Ticket) public tickets;
/// @dev Pending tickets for commitment submission
mapping(bytes32 ticketID => Ticket) public tickets;

/* Constants */

Expand Down Expand Up @@ -205,7 +199,7 @@ contract BeefyClient {
error InvalidSignature();
error InvalidTicket();
error InvalidValidatorProof();
error NoMMRRootInCommitment();
error CommitmentNotRelevant();
error NotEnoughClaims();
error PrevRandaoAlreadyCaptured();
error PrevRandaoNotCaptured();
Expand Down Expand Up @@ -338,7 +332,30 @@ contract BeefyClient {
bytes32[] calldata leafProof,
uint256 leafProofOrder
) external {
(bytes32 commitmentHash, bytes32 ticketID) = validate(commitment, bitfield);
bytes32 commitmentHash = keccak256(encodeCommitment(commitment));
bytes32 ticketID = createTicketID(msg.sender, commitmentHash);
Ticket storage ticket = tickets[ticketID];

if (ticket.blockNumber == 0) {
// submitInitial hasn't been called yet
revert InvalidTicket();
}

if (ticket.prevRandao == 0) {
// commitPrevRandao hasn't been called yet
revert PrevRandaoNotCaptured();
}

if (commitment.blockNumber <= latestBeefyBlock) {
// ticket is obsolete
revert StaleCommitment();
}

if (ticket.bitfieldHash != keccak256(abi.encodePacked(bitfield))) {
// The provided claims bitfield isn't the same one that was
// passed to submitInitial
revert InvalidBitfield();
}

bool is_next_session = false;
ValidatorSetState storage vset;
Expand Down Expand Up @@ -438,13 +455,13 @@ contract BeefyClient {

/**
* @dev Calculates the number of required signatures for `submitFinal`.
* For more details on the calculation, read the following:
* 1. https://docs.snowbridge.network/architecture/verification/polkadot#signature-sampling
* 2. https://hackmd.io/9OedC7icR5m-in_moUZ_WQ
* @param validatorSetLen The length of the validator set
* @param signatureUsageCount A counter of the number of times the validator signature was previously used in a call to `submitInitial` within the session.
* @param minRequiredSignatures The minimum amount of signatures to verify
*/
// For more details on the calculation, read the following:
// 1. https://docs.snowbridge.network/architecture/verification/polkadot#signature-sampling
// 2. https://hackmd.io/9OedC7icR5m-in_moUZ_WQ
function computeNumRequiredSignatures(
uint256 validatorSetLen,
uint256 signatureUsageCount,
Expand Down Expand Up @@ -524,6 +541,7 @@ contract BeefyClient {
}
}

// Ensure that the commitment provides a new MMR root
function ensureProvidesMMRRoot(Commitment calldata commitment) internal pure returns (bytes32) {
for (uint256 i = 0; i < commitment.payload.length; i++) {
if (commitment.payload[i].payloadID == MMR_ROOT_ID) {
Expand All @@ -534,8 +552,7 @@ contract BeefyClient {
}
}
}

revert NoMMRRootInCommitment();
revert CommitmentNotRelevant();
}

function encodeCommitment(Commitment calldata commitment) internal pure returns (bytes memory) {
Expand Down Expand Up @@ -588,33 +605,4 @@ contract BeefyClient {
bytes32 hashedLeaf = keccak256(abi.encodePacked(account));
return SubstrateMerkleProof.verify(vset.root, hashedLeaf, index, vset.length, proof);
}

// Basic checks for commitment
function validate(Commitment calldata commitment, uint256[] calldata bitfield)
internal
view
returns (bytes32, bytes32)
{
bytes32 commitmentHash = keccak256(encodeCommitment(commitment));
bytes32 ticketID = createTicketID(msg.sender, commitmentHash);
Ticket storage ticket = tickets[ticketID];

if (ticket.blockNumber == 0) {
// Zero value ticket: submitInitial hasn't run for this commitment
revert InvalidTicket();
}

if (ticket.prevRandao == 0) {
revert PrevRandaoNotCaptured();
}

if (commitment.blockNumber <= latestBeefyBlock) {
revert StaleCommitment();
}

if (ticket.bitfieldHash != keccak256(abi.encodePacked(bitfield))) {
revert InvalidBitfield();
}
return (commitmentHash, ticketID);
}
}
16 changes: 8 additions & 8 deletions contracts/src/Verification.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ library Verification {
/// @dev Merkle proof for parachain header finalized by BEEFY
/// Reference: https://github.com/paritytech/polkadot/blob/09b61286da11921a3dda0a8e4015ceb9ef9cffca/runtime/rococo/src/lib.rs#L1312
struct HeadProof {
/// @dev The leaf index of the parachain being proven
// The leaf index of the parachain being proven
uint256 pos;
/// @dev The number of leaves in the merkle tree
// The number of leaves in the merkle tree
uint256 width;
/// @dev The proof items
// The proof items
bytes32[] proof;
}

Expand Down Expand Up @@ -54,15 +54,15 @@ library Verification {

/// @dev A chain of proofs
struct Proof {
/// @dev The parachain header containing the message commitment as a digest item
// The parachain header containing the message commitment as a digest item
ParachainHeader header;
/// @dev The proof used to generate a merkle root of parachain heads
// The proof used to generate a merkle root of parachain heads
HeadProof headProof;
/// @dev The MMR leaf to be proven
// The MMR leaf to be proven
MMRLeafPartial leafPartial;
/// @dev The MMR leaf prove
// The MMR leaf prove
bytes32[] leafProof;
/// @dev The order in which proof items should be combined
// The order in which proof items should be combined
uint256 leafProofOrder;
}

Expand Down
Loading

0 comments on commit a4968e1

Please sign in to comment.