diff --git a/README.md b/README.md index b0c5c15..ef7793f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ [Registry.sol](src/Registry.sol) - [x] Reduce `MIN_COLLATERAL` to 0.1 ETH. It needs to be non-zero to incentivize people to slash bad registrations. -- [ ] Optimistically accept an `OperatorCommitment` hash. It can be proven as fraudulent by generating the merkle tree in the fraud proof. +- [ ] Rename `proxyKey` to `commitmentKey`. +- [ ] ~~Optimistically accept an `OperatorCommitment` hash. It can be proven as fraudulent by generating the merkle tree in the fraud proof.~~ - [ ] Make the unregistration delay parameterizable by the proposer but requires it to be at least `TWO_EPOCHS`. - [ ] Spec out the `Registration` message signed by a Validator BLS key. - [ ] Make sure no one can overwrite an `OperatorCommitment` @@ -14,10 +15,11 @@ [BytecodeSlasher.sol](src/BytecodeSlasher.sol) -- [ ] Update the `BytecodeSlasher` interface to include the slashing evidence, signed bytecode, operator commitment, proxy key, and function selector. +- [x] If we want to support 'stateful' slashing contracts we should consider signing `slasherAddress || functionSelector` and invoking that instead of deploying and executing bytecode. +- [ ] Replace BytecodeSlasher concept with a way to call a `Slasher` contract address. +- [ ] Update the `Slasher` interface to include the slashing evidence, signed bytecode, operator commitment, proxy key, and function selector. - [ ] Any additional modifiers needed? -- [ ] Verify the `Delegation` signature inside the `BytecodeSlasher` -- [ ] If we want to support 'stateful' slashing contracts we should consider signing `slasherAddress || functionSelector` and invoking that instead of deploying and executing bytecode. +- [ ] Verify the `Commitment` signature inside the `Slasher` ## Schemas diff --git a/src/Registry.sol b/src/Registry.sol index 879b790..47f638e 100644 --- a/src/Registry.sol +++ b/src/Registry.sol @@ -9,27 +9,28 @@ contract Registry { struct Registration { uint256[2] pubkey; // compressed bls pubkey - uint256[8] signature; // flattened registration signature + uint256[8] signature; // flattened Registration signature } struct Operator { - bytes32 proxyKey; // compressed ecdsa key without prefix + bytes32 commitmentKey; // compressed ecdsa key without prefix address operator; // msg.sender can be a multisig - uint72 collateral; + uint72 collateral; // todo save as GWEI uint32 registeredAt; uint32 unregisteredAt; + // anything else? } struct Leaf { uint256[2] pubkey; // compressed pubkey - bytes32 registrationCommitment; // sha256(signature || proxyKey) + bytes32 registrationCommitment; // sha256(signature || commitmentKey) } mapping(bytes32 operatorCommitment => Operator) public commitments; // Constants - uint256 constant MIN_COLLATERAL = 1 ether; - uint256 constant TWO_EPOCHS = 64; + uint256 constant MIN_COLLATERAL = 0.1 ether; + uint256 constant TWO_EPOCHS = 64; // parameterize when you join // Errors error InsufficientCollateral(); @@ -50,7 +51,7 @@ contract Registry { function register( Registration[] calldata registrations, - bytes32 proxyKey, + bytes32 commitmentKey, uint256 height ) external payable { // check collateral @@ -61,15 +62,15 @@ contract Registry { // operatorCommitment hash = merklize registrations bytes32 operatorCommitment = createCommitment( registrations, - proxyKey, + commitmentKey, height ); // add operatorCommitment to mapping commitments[operatorCommitment] = Operator({ operator: msg.sender, - proxyKey: proxyKey, - collateral: uint72(msg.value), + commitmentKey: commitmentKey, + collateral: uint72(msg.value), // todo save as GWEI registeredAt: uint32(block.number), unregisteredAt: 0 }); @@ -79,7 +80,7 @@ contract Registry { function createCommitment( Registration[] calldata registrations, - bytes32 proxyKey, + bytes32 commitmentKey, uint256 height ) internal view returns (bytes32 operatorCommitment) { uint256 batchSize = 1 << height; // guaranteed pow of 2 @@ -95,7 +96,7 @@ contract Registry { for (uint256 i = 0; i < registrations.length; i++) { // Create registration commitment by hashing signature and metadata bytes32 registrationCommitment = sha256( - abi.encodePacked(registrations[i].signature, proxyKey) + abi.encodePacked(registrations[i].signature, commitmentKey) ); // Create leaf node by hashing pubkey and commitment @@ -122,7 +123,7 @@ contract Registry { bytes32 operatorCommitment, BLS12381.G1Point calldata pubkey, BLS12381.G2Point calldata signature, - bytes32 proxyKey, + bytes32 commitmentKey, bytes32[] calldata proof, uint256 leafIndex ) external { @@ -134,7 +135,7 @@ contract Registry { // reconstruct leaf bytes32 leaf = sha256(abi.encodePacked( pubkeyBytes, - sha256(abi.encodePacked(signatureBytes, proxyKey)) + sha256(abi.encodePacked(signatureBytes, commitmentKey)) )); // verify proof against operatorCommitment @@ -142,7 +143,8 @@ contract Registry { revert FraudProofMerklePathInvalid(); } - // reconstruct message todo + // reconstruct message + // todo what exactly are they signing? bytes memory message = bytes(""); // verify signature @@ -218,211 +220,4 @@ contract Registry { // Return the pairing check that denotes the correctness of the signature return BLS12381.pairing(pubkey, msgG2, BLS12381.negGeneratorG1(), sig); } -} - -// contract Registry2 { -// struct Validator { -// BLS12381.G1Point pubkey; -// BLS12381.G2Point signature; -// address operator; -// uint64 registeredAt; -// } - -// struct Registration { -// BLS12381.G1Point pubkey; -// BLS12381.G2Point signature; -// } - -// // Pack operator info into a struct that fits in one storage slot -// struct OperatorInfo { -// uint64 validatorCount; -// uint64 unregisteredAt; // 0 means not unregistered -// uint128 collateral; // Using uint128 since 1 ether fits easily -// } - -// mapping(address => OperatorInfo) public operatorInfo; -// mapping(address => mapping(uint256 => Validator)) -// public operatorToValidator; - -// // Constants -// uint256 constant MIN_COLLATERAL = 1 ether; -// uint256 constant TWO_EPOCHS = 64; - -// // Errors -// error InsufficientCollateral(uint256 sent); -// error NotUnregistered(); -// error UnregistrationDelayNotMet(); -// error AlreadyUnregistered(); -// error NoCollateralToClaim(); -// error SignatureWasValid(); -// error WrongPubkey(); -// error WrongSignature(); - -// // Events -// event ValidatorRegistered( -// address indexed operator, -// BLS12381.G1Point pubkey, -// BLS12381.G2Point signature, -// uint64 activeAfter -// ); -// event CollateralAdded(address indexed operator, uint256 amount); -// event CollateralRemoved(address indexed operator, uint256 amount); -// event OperatorUnregistered(address indexed operator, uint64 unregisteredAt); -// event ValidatorRegistrationSlashed( -// address indexed operator, -// uint256 index, -// BLS12381.G1Point pubkey -// ); - -// function register(Registration[] calldata validators) external payable { -// OperatorInfo storage info = operatorInfo[msg.sender]; - -// // Ensure operator isn't already unregistering -// if (info.unregisteredAt != 0) { -// // todo but should we allow re-registering? -// revert AlreadyUnregistered(); -// } - -// uint256 newCollateral = info.collateral + msg.value; - -// // Check collateral requirement -// if (newCollateral < MIN_COLLATERAL) { -// revert InsufficientCollateral(newCollateral); -// } - -// // Register each validator -// uint64 timestamp = uint64(block.timestamp); -// uint64 activeAfterTime = timestamp + 1 days; - -// for (uint256 i = 0; i < validators.length; i++) { -// operatorToValidator[msg.sender][ -// info.validatorCount + i -// ] = Validator({ -// pubkey: validators[i].pubkey, -// signatureHash: _hashSignature(validators[i].signature), -// registeredAt: timestamp, -// activeAfter: activeAfterTime -// }); - -// emit ValidatorRegistered( -// msg.sender, -// validators[i].pubkey, -// validators[i].signature, -// activeAfterTime -// ); -// } - -// // Update operator info -// info.validatorCount += uint64(validators.length); -// info.collateral = uint128(newCollateral); - -// emit CollateralAdded(msg.sender, msg.value); -// } - -// function unregister() external { -// OperatorInfo storage info = operatorInfo[msg.sender]; - -// // Check that they haven't already unregistered -// if (info.unregisteredAt != 0) { -// revert AlreadyUnregistered(); -// } - -// // Set unregistration timestamp -// info.unregisteredAt = uint64(block.number); - -// emit OperatorUnregistered(msg.sender, info.unregisteredAt); -// } - -// function claimCollateral() external { -// OperatorInfo storage info = operatorInfo[msg.sender]; - -// // Check that they've unregistered -// if (info.unregisteredAt == 0) { -// revert NotUnregistered(); -// } - -// // Check that enough time has passed -// if (block.number < info.unregisteredAt + TWO_EPOCHS) { -// revert UnregistrationDelayNotMet(); -// } - -// // Check there's collateral to claim -// if (info.collateral == 0) { -// revert NoCollateralToClaim(); -// } - -// // Store amount to return -// uint256 amountToReturn = info.collateral; - -// // Clear operator info -// delete operatorInfo[msg.sender]; - -// // TODO safe transfer for rentrancy -// (bool success, ) = msg.sender.call{value: amountToReturn}(""); -// require(success, "Transfer failed"); - -// emit CollateralRemoved(msg.sender, amountToReturn); -// } - -// function slashRegistration( -// address operator, -// uint256 index, -// BLS12381.G1Point calldata pubkey, -// BLS12381.G2Point calldata signature -// ) external { -// Validator memory validator = operatorToValidator[msg.sender][index]; - -// // Verify the supplied calldata matches what has been committed -// if (_hashBLSPubKey(validator.pubkey) != _hashBLSPubKey(pubkey)) { -// revert WrongPubkey(); -// } - -// if (validator.signatureHash != _hashSignature(signature)) { -// revert WrongSignature(); -// } - -// // Verify the signature -// bytes memory message = abi.encodePacked(operator); -// if (_verifySignature(message, signature, pubkey)) { -// revert SignatureWasValid(); -// } - -// // todo slash the validator collateral -// // todo do we pay the msg.sender? - -// emit ValidatorRegistrationSlashed(operator, index, pubkey); -// } - -// /** -// * @notice Returns `true` if the BLS signature on the message matches against the public key -// * @param message The message bytes -// * @param sig The BLS signature -// * @param pubkey The BLS public key of the expected signer -// */ -// function _verifySignature( -// bytes memory message, -// BLS12381.G2Point memory sig, -// BLS12381.G1Point memory pubkey -// ) internal view returns (bool) { -// bytes memory domainSeparator = bytes("Proposer Commitment Registry"); -// // Hash the message bytes into a G2 point -// BLS12381.G2Point memory msgG2 = message.hashToCurveG2(domainSeparator); - -// // Return the pairing check that denotes the correctness of the signature -// return BLS12381.pairing(pubkey, msgG2, BLS12381.negGeneratorG1(), sig); -// } - -// function _hashBLSPubKey( -// BLS12381.G1Point memory pubkey -// ) internal pure returns (bytes32) { -// uint256[2] memory compressedPubKey = pubkey.compress(); -// return keccak256(abi.encodePacked(compressedPubKey)); -// } - -// function _hashSignature( -// BLS12381.G2Point memory signature -// ) internal pure returns (bytes32) { -// uint256[8] memory flattenedSignature = signature.flatten(); -// return keccak256(abi.encodePacked(flattenedSignature)); -// } -// } +} \ No newline at end of file