Skip to content
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

2370 staketable update validator identification keys #2373

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
978cd8f
add stake table tests
alysiahuggins Dec 5, 2024
2bc6d75
remove stake types
alysiahuggins Dec 5, 2024
c8812ef
verify token allowance, balance and reprioritize verification order o…
alysiahuggins Dec 5, 2024
e33bf9b
set the fixed stake amount, added related tests, updated data types
alysiahuggins Dec 5, 2024
7bd7f4b
add more verification checks to the withdraw function
alysiahuggins Dec 5, 2024
2b2411c
updated errror types
alysiahuggins Dec 6, 2024
d7a352d
Merge branch 'main' into 2304-stake-table-registration
alysiahuggins Dec 6, 2024
584e320
added TODO statements in comments to be explicit about outdated funct…
alysiahuggins Dec 6, 2024
59c2109
a validator/node is identified by the msg.sender, blskey and schnorrk…
alysiahuggins Dec 6, 2024
23b0fb0
Merge branch 'main' into 2304-stake-table-registration
alysiahuggins Dec 6, 2024
ec4b02b
Merge branch '2304-stake-table-registration' into 2370-staketable-upd…
alysiahuggins Dec 6, 2024
9777b99
merged from the fixed stake branch and modified test to include the i…
alysiahuggins Dec 6, 2024
b9c7022
not required to get the blsSig for a withdrawal since we have the eth…
alysiahuggins Dec 6, 2024
9303444
add the ability to update consensus keys
alysiahuggins Dec 6, 2024
3a264c1
add the ability to update consensus keys
alysiahuggins Dec 6, 2024
955a69d
merge with main
alysiahuggins Dec 6, 2024
1f171dd
remove vscode settings
alysiahuggins Dec 6, 2024
f237074
add test or happy path of updating consensus keys
alysiahuggins Dec 9, 2024
8800893
added unhappy path tests on based on no key changes and changed the b…
alysiahuggins Dec 9, 2024
cee5a6f
added comment to newly added function on StakeTable and added tests t…
alysiahuggins Dec 9, 2024
f8c0210
change the lookup node and lookup stake functions to use their ethere…
alysiahuggins Dec 9, 2024
5124d9d
updated updateConsensusKeys function, enhance test coverage
alysiahuggins Dec 9, 2024
6a34dae
added todo
alysiahuggins Dec 10, 2024
548b28c
Merge branch 'main' into 2370-staketable-update-validator-identificat…
alysiahuggins Dec 10, 2024
5df564c
updated test to use the new seed so that a new schnorr key could be g…
alysiahuggins Dec 11, 2024
83f1a70
Update contracts/src/StakeTable.sol
alysiahuggins Jan 6, 2025
9d1b2b1
Merge branch 'main' into 2370-staketable-update-validator-identificat…
alysiahuggins Jan 7, 2025
e1b7e25
check for invalid bls and schnorr keys on register
alysiahuggins Jan 7, 2025
178e680
Merge branch 'main' into 2370-staketable-update-validator-identificat…
alysiahuggins Jan 7, 2025
48db5c4
update test comments so that they are clearer
alysiahuggins Jan 7, 2025
5f3a988
updateConsensusKeys reverts when the keys the same as the old ones, n…
alysiahuggins Jan 8, 2025
638ed78
updating consensus keys revert if any VK is a zero point VK
alysiahuggins Jan 9, 2025
0cc8946
clarifies that the bls sig proves
alysiahuggins Jan 9, 2025
2821408
Merge branch 'main' into 2370-staketable-update-validator-identificat…
alysiahuggins Jan 10, 2025
63fa46d
Merge branch 'main' into 2370-staketable-update-validator-identificat…
alysiahuggins Jan 15, 2025
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
205 changes: 153 additions & 52 deletions contracts/src/StakeTable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { AbstractStakeTable } from "./interfaces/AbstractStakeTable.sol";
import { LightClient } from "../src/LightClient.sol";
import { EdOnBN254 } from "./libraries/EdOnBn254.sol";

using EdOnBN254 for EdOnBN254.EdOnBN254Point;

/// @title Implementation of the Stake Table interface
contract StakeTable is AbstractStakeTable {
/// Error to notify restaking is not implemented yet.
Expand Down Expand Up @@ -53,8 +55,17 @@ contract StakeTable is AbstractStakeTable {
// Error raised when the staker does not register with the correct stakeAmount
error InsufficientStakeAmount(uint256);

// Error raised when the staker does not provide a new schnorrVK
error InvalidSchnorrVK();

// Error raised when the staker does not provide a new blsVK
error InvalidBlsVK();

// Error raised when zero point keys are provided
error NoKeyChange();

/// Mapping from a hash of a BLS key to a node struct defined in the abstract contract.
mapping(bytes32 keyHash => Node node) public nodes;
mapping(address account => Node node) public nodes;

/// Total stake locked;
uint256 public totalStake;
Expand Down Expand Up @@ -103,33 +114,45 @@ contract StakeTable is AbstractStakeTable {
return keccak256(abi.encode(blsVK.x0, blsVK.x1, blsVK.y0, blsVK.y1));
}

/// @dev Compares two BLS keys for equality
/// @param a First BLS key
/// @param b Second BLS key
/// @return True if the keys are equal, false otherwise
function _isEqualBlsKey(BN254.G2Point memory a, BN254.G2Point memory b)
public
pure
returns (bool)
{
return BN254.BaseField.unwrap(a.x0) == BN254.BaseField.unwrap(b.x0)
&& BN254.BaseField.unwrap(a.x1) == BN254.BaseField.unwrap(b.x1)
&& BN254.BaseField.unwrap(a.y0) == BN254.BaseField.unwrap(b.y0)
&& BN254.BaseField.unwrap(a.y1) == BN254.BaseField.unwrap(b.y1);
}

/// TODO handle this logic more appropriately when epochs are re-introduced
/// @dev Fetches the current epoch from the light client contract.
/// @return current epoch (computed from the current block)
function currentEpoch() public pure returns (uint64) {
return 0;
}

/// @notice Look up the balance of `blsVK`
/// @param blsVK BLS public key controlled by the user.
/// @notice Look up the balance of `account`
/// @param account account controlled by the user.
/// @return Current balance owned by the user.
/// TODO modify this according to the current spec
function lookupStake(BN254.G2Point memory blsVK) external view override returns (uint256) {
Node memory node = this.lookupNode(blsVK);
function lookupStake(address account) external view override returns (uint256) {
Node memory node = this.lookupNode(account);
return node.balance;
}

/// @notice Look up the full `Node` state associated with `blsVK`
/// @dev The lookup is achieved by hashing first the four field elements of blsVK using
/// keccak256.
/// @return Node indexed by blsVK
/// TODO modify this according to the current spec
function lookupNode(BN254.G2Point memory blsVK) external view override returns (Node memory) {
return nodes[_hashBlsKey(blsVK)];
/// @notice Look up the full `Node` state associated with `account`
/// @return Node indexed by account
function lookupNode(address account) external view override returns (Node memory) {
return nodes[account];
}

/// @notice Get the next available epoch and queue size in that epoch
/// TODO modify this according to the current spec
/// TODO modify this according to the current spec
function nextRegistrationEpoch() external view override returns (uint64, uint64) {
uint64 epoch;
uint64 queueSize;
Expand All @@ -152,19 +175,22 @@ contract StakeTable is AbstractStakeTable {
// @param queueSize current size of the registration queue (after insertion of new element in
// the queue)
/// TODO modify this according to the current spec
/// TODO modify this according to the current spec
function appendRegistrationQueue(uint64 epoch, uint64 queueSize) private {
firstAvailableRegistrationEpoch = epoch;
_numPendingRegistrations = queueSize + 1;
}

/// @notice Get the number of pending registration requests in the waiting queue
/// TODO modify this according to the current spec
/// TODO modify this according to the current spec
function numPendingRegistrations() external view override returns (uint64) {
return _numPendingRegistrations;
}

/// @notice Get the next available epoch for exit and queue size in that epoch
/// TODO modify this according to the current spec
/// TODO modify this according to the current spec
function nextExitEpoch() external view override returns (uint64, uint64) {
uint64 epoch;
uint64 queueSize;
Expand All @@ -186,13 +212,15 @@ contract StakeTable is AbstractStakeTable {
// @param epoch next available exit epoch
// @param queueSize current size of the exit queue (after insertion of new element in the queue)
/// TODO modify this according to the current spec
/// TODO modify this according to the current spec
function appendExitQueue(uint64 epoch, uint64 queueSize) private {
firstAvailableExitEpoch = epoch;
_numPendingExits = queueSize + 1;
}

/// @notice Get the number of pending exit requests in the waiting queue
/// TODO modify this according to the current spec
/// TODO modify this according to the current spec
function numPendingExits() external view override returns (uint64) {
return _numPendingExits;
}
Expand All @@ -212,6 +240,7 @@ contract StakeTable is AbstractStakeTable {
/// @param node node which is assigned an exit escrow period.
/// @return Number of epochs post exit after which funds can be withdrawn.
/// TODO modify this according to the current spec
/// TODO modify this according to the current spec
function exitEscrowPeriod(Node memory node) public pure returns (uint64) {
if (node.balance > 100) {
return 10;
Expand Down Expand Up @@ -254,8 +283,7 @@ contract StakeTable is AbstractStakeTable {
revert InsufficientStakeAmount(amount);
}

bytes32 key = _hashBlsKey(blsVK);
Node memory node = nodes[key];
Node memory node = nodes[msg.sender];

// Verify that the node is not already registered.
if (node.account != address(0x0)) {
Expand All @@ -274,10 +302,30 @@ contract StakeTable is AbstractStakeTable {
revert InsufficientBalance(balance);
}

// Verify that blsVK is not the zero point
if (
_isEqualBlsKey(
blsVK,
BN254.G2Point(
BN254.BaseField.wrap(0),
BN254.BaseField.wrap(0),
BN254.BaseField.wrap(0),
BN254.BaseField.wrap(0)
)
)
) {
revert InvalidBlsVK();
}

// Verify that the validator can sign for that blsVK
bytes memory message = abi.encode(msg.sender);
BLSSig.verifyBlsSig(message, blsSig, blsVK);

// Verify that the schnorrVK is non-zero
if (schnorrVK.isEqual(EdOnBN254.EdOnBN254Point(0, 0))) {
revert InvalidSchnorrVK();
}

// Find the earliest epoch at which this node can register. Usually, this will be
// currentEpoch() + 1 (the start of the next full epoch), but in periods of high churn the
// queue may fill up and it may be later. If the queue is so full that the wait time exceeds
Expand All @@ -299,28 +347,28 @@ contract StakeTable is AbstractStakeTable {
// Create an entry for the node.
node.account = msg.sender;
node.balance = fixedStakeAmount;
node.blsVK = blsVK;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do I understand correctly that we check that the keys are not zero when if consensus keys are updated but we don't check for that during registration? Is that intentional?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess we don't need to check the blsVk because we verify a signature but what about the schnorrVk?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes you're right, there should be checks on the vk(s) here too!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added checks on zero point keys e1b7e25

node.schnorrVK = schnorrVK;
node.registerEpoch = registerEpoch;

nodes[key] = node;
nodes[msg.sender] = node;

emit Registered(key, registerEpoch, fixedStakeAmount);
emit Registered(msg.sender, registerEpoch, fixedStakeAmount);
}

/// @notice Deposit more stakes to registered keys
/// @dev TODO this implementation will be revisited later. See
/// https://github.com/EspressoSystems/espresso-sequencer/issues/806
/// @dev TODO modify this according to the current spec
/// @param blsVK The BLS verification key
/// @param amount The amount to deposit
/// @return (newBalance, effectiveEpoch) the new balance effective at a future epoch
function deposit(BN254.G2Point memory blsVK, uint256 amount)
external
override
returns (uint256, uint64)
{
bytes32 key = _hashBlsKey(blsVK);
Node memory node = nodes[key];
function deposit(uint256 amount) external override returns (uint256, uint64) {
Node memory node = nodes[msg.sender];

// if the node is not registered, revert
if (node.account == address(0)) {
revert NodeNotRegistered();
}

// The deposit must come from the node's registered account.
if (node.account != msg.sender) {
Expand All @@ -338,23 +386,27 @@ contract StakeTable is AbstractStakeTable {
revert ExitRequestInProgress();
}

nodes[key].balance += amount;
nodes[msg.sender].balance += amount;
SafeTransferLib.safeTransferFrom(ERC20(tokenAddress), msg.sender, address(this), amount);

emit Deposit(_hashBlsKey(blsVK), uint256(amount));
emit Deposit(msg.sender, uint256(amount));

uint64 effectiveEpoch = _currentEpoch + 1;

return (nodes[key].balance, effectiveEpoch);
return (nodes[msg.sender].balance, effectiveEpoch);
}

/// @notice Request to exit from the stake table, not immediately withdrawable!
///
/// @dev TODO modify this according to the current spec
/// @param blsVK The BLS verification key to exit
function requestExit(BN254.G2Point memory blsVK) external override {
bytes32 key = _hashBlsKey(blsVK);
Node memory node = nodes[key];
function requestExit() external override {
Node memory node = nodes[msg.sender];

// TODO test this behaviour when re-implementing the logic for handling epochs
// if the node is not registered, revert
alysiahuggins marked this conversation as resolved.
Show resolved Hide resolved
if (node.account == address(0)) {
revert NodeNotRegistered();
}

// The exit request must come from the node's withdrawal account.
if (node.account != msg.sender) {
Expand All @@ -374,60 +426,109 @@ contract StakeTable is AbstractStakeTable {

// Prepare the node to exit.
(uint64 exitEpoch, uint64 queueSize) = this.nextExitEpoch();
nodes[key].exitEpoch = exitEpoch;
nodes[msg.sender].exitEpoch = exitEpoch;

appendExitQueue(exitEpoch, queueSize);

emit Exit(key, exitEpoch);
emit Exit(msg.sender, exitEpoch);
}

/// @notice Withdraw from the staking pool. Transfers occur! Only successfully exited keys can
/// withdraw past their `exitEpoch`.
///
/// @param blsVK The BLS verification key to withdraw
/// @param blsSig The BLS signature that authenticates the ethereum account this function is
/// called from the caller
/// @return The total amount withdrawn, equal to `Node.balance` associated with `blsVK`
/// TODO: This function should be tested
/// TODO modify this according to the current spec

function withdrawFunds(BN254.G2Point memory blsVK, BN254.G1Point memory blsSig)
external
override
returns (uint256)
{
bytes32 key = _hashBlsKey(blsVK);
Node memory node = nodes[key];
function withdrawFunds() external override returns (uint256) {
Node memory node = nodes[msg.sender];

// Verify that the node is already registered.
if (node.account == address(0)) {
revert NodeNotRegistered();
}

// The exit request must come from the node's withdrawal account.
if (node.account != msg.sender) {
revert Unauthenticated();
}

// Verify that the balance is greater than zero
uint256 balance = node.balance;
if (balance == 0) {
revert InsufficientStakeBalance(0);
}

// Verify that the validator can sign for that blsVK
bytes memory message = abi.encode(msg.sender);
BLSSig.verifyBlsSig(message, blsSig, blsVK);

// Verify that the exit escrow period is over.
if (currentEpoch() < node.exitEpoch + exitEscrowPeriod(node)) {
revert PrematureWithdrawal();
}

// Delete the node from the stake table.
delete nodes[key];
delete nodes[msg.sender];

// Transfer the balance to the node's account.
SafeTransferLib.safeTransfer(ERC20(tokenAddress), node.account, balance);

return balance;
}

/// @notice Update the consensus keys for a validator
/// @dev This function is used to update the consensus keys for a validator
/// @dev This function can only be called by the validator itself when it's not in the exit
/// queue
/// @dev The validator will need to give up either its old BLS key and/or old Schnorr key
/// @dev The validator will need to provide a BLS signature to prove that the account owns the
/// new BLS key
/// @param newBlsVK The new BLS verification key
/// @param newSchnorrVK The new Schnorr verification key
/// @param newBlsSig The BLS signature that the account owns the new BLS key
function updateConsensusKeys(
philippecamacho marked this conversation as resolved.
Show resolved Hide resolved
BN254.G2Point memory newBlsVK,
EdOnBN254.EdOnBN254Point memory newSchnorrVK,
BN254.G1Point memory newBlsSig
) external override {
Node memory node = nodes[msg.sender];

// Verify that the node is already registered.
if (node.account == address(0)) revert NodeNotRegistered();

// Verify that the node is not in the exit queue
if (node.exitEpoch != 0) revert ExitRequestInProgress();

// Verify that the keys are not the same as the old ones
if (_isEqualBlsKey(newBlsVK, node.blsVK) && newSchnorrVK.isEqual(node.schnorrVK)) {
revert NoKeyChange();
}

// Zero-point constants for verification
BN254.G2Point memory zeroBlsKey = BN254.G2Point(
BN254.BaseField.wrap(0),
BN254.BaseField.wrap(0),
BN254.BaseField.wrap(0),
BN254.BaseField.wrap(0)
);
EdOnBN254.EdOnBN254Point memory zeroSchnorrKey = EdOnBN254.EdOnBN254Point(0, 0);

if (_isEqualBlsKey(newBlsVK, zeroBlsKey)) revert InvalidBlsVK();

if (newSchnorrVK.isEqual(zeroSchnorrKey)) revert InvalidSchnorrVK();

// Verify that the validator can sign for that newBlsVK, otherwise it inner reverts with
// BLSSigVerificationFailed
bytes memory message = abi.encode(msg.sender);
BLSSig.verifyBlsSig(message, newBlsSig, newBlsVK);

// Update the node's bls key
node.blsVK = newBlsVK;

// Update the node's schnorr key if the newSchnorrVK is not a zero point Schnorr key
node.schnorrVK = newSchnorrVK;

// Update the node in the stake table
nodes[msg.sender] = node;

// Emit the event
emit UpdatedConsensusKeys(msg.sender, node.blsVK, node.schnorrVK);
}

/// @notice Minimum stake amount
/// @return Minimum stake amount
/// TODO: This value should be a variable modifiable by admin
Expand Down
Loading
Loading