Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
2 changes: 0 additions & 2 deletions .github/workflows/registry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ jobs:

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: v1.0.0

- name: Show Forge version
run: |
Expand Down
4 changes: 3 additions & 1 deletion config/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
"fraudProofWindow": 7200,
"unregistrationDelay": 7200,
"slashWindow": 7200,
"optInDelay": 7200
"optInDelay": 7200,
"signingDomain": "0x6d6d6f43719103511efa4f1362ff2a50996cccf329cc84cb410c5e5c7d351d03",
"chainId": "0xb08b080000000000000000000000000000000000000000000000000000000000"
}
51 changes: 50 additions & 1 deletion docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,53 @@
- [ ] Audit 3

## Overview
Readers are recommended to refer to the [Constraints API documentation](https://github.com/eth-fabric/constraints-specs/blob/main/specs/proposer.md) for details on how to interact with the URC.
Readers are recommended to refer to the [Constraints API documentation](https://github.com/eth-fabric/constraints-specs/blob/main/specs/proposer.md) for details on how to interact with the URC.

## Signing
The URC will verify two types of BLS signatures:
- `IRegistry.SignedRegistration.signature`
- `ISlasher.SignedDelegation.signature`

The messages are expected to be formatted as follows:
- `bytes32 messageHash = keccak256(abi.encode(IRegistry.MessageType.Registration, owner));` where `owner` is an `address`
- `bytes32 messageHash = keccak256(abi.encode(IRegistry.MessageType.Delegation, delegation));` where `delegation` is an `ISlasher.Delegation`

The URC complies with the [Signing API defined in Commit-Boost](https://github.com/Commit-Boost/commit-boost-client/blob/2dfe96b8d45d9c2bb37f71d56130a066dec16ec8/crates/common/src/types.rs#L296).
```
/// Structure for signatures used in Beacon chain operations
#[derive(Default, Debug, TreeHash)]
pub struct SigningData {
pub object_root: B256,
pub signing_domain: B256,
}

/// Structure for signatures used for proposer commitments in Commit Boost.
/// The signing root of this struct must be used as the object_root of a
/// SigningData for signatures.
#[derive(Default, Debug, TreeHash)]
pub struct PropCommitSigningInfo {
pub data: B256,
pub module_signing_id: B256,
pub nonce: u64, // As per https://eips.ethereum.org/EIPS/eip-2681
pub chain_id: U256,
}
```

The final signature is over the `signingRoot` which is the hash tree root of the `SigningData` struct, containing the `PropCommitSigningInfo` and `signing_domain`.

This can be visualized as the following, where each intermediate hash uses SHA256. Specifically, `subTreeRoot` is the hash tree root of `PropCommitSigningInfo`:
```
signingRoot
/ \
subTreeRoot signingDomain
/ \
* *
/ \ / \
messageHash signingId nonce chainId
```

Notes:
- `messageHash` is equivalent to `PropCommitSigningInfo.data`
- the `nonce` and `chain_id` are encoded as little-endian `bytes32`.
- the `nonce` and `signingId` aren't tracked by the URC but is mixed in with the signature according to the Commit-Boost signing spec.
- the `signingDomain` is the precalculated `compute_domain()` value from Commit-Boost for the specific chain the URC is deployed on.
2 changes: 0 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ auto_detect_solc = false
optimizer = true
optimizer_runs = 200
evm_version = "prague" # for testing bls precompiles
seed = "0x1337"
solc = "0.8.25"
# via_ir = true

[fmt]
line_length = 120
Expand Down
139 changes: 102 additions & 37 deletions script/BaseScript.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ import "../src/lib/MerkleTree.sol";
import "../src/ISlasher.sol";
import { BLS } from "solady/utils/ext/ithaca/BLS.sol";
import { BLSUtils } from "../src/lib/BLSUtils.sol";
import { ECDSAUtils } from "../src/lib/ECDSAUtils.sol";

contract BaseScript is Script {
bytes public constant REGISTRATION_DOMAIN_SEPARATOR = "0x00555243"; // "URC" in little endian
bytes public constant DELEGATION_DOMAIN_SEPARATOR = "0x0044656c"; // "Del" in little endian

function _getDefaultJson(string memory _outfile, string memory _default)
internal
returns (string memory jsonFile, string memory jsonObj)
Expand All @@ -38,37 +36,52 @@ contract BaseScript is Script {
}

/// @dev NOT MEANT FOR PRODUCTION USE
function _signTestRegistration(uint256 privateKey, address _owner)
internal
view
returns (IRegistry.SignedRegistration memory signedRegistration)
{
function _signTestRegistration(
uint256 privateKey,
address _owner,
bytes32 signingDomain,
bytes32 signingId,
uint64 nonce,
bytes32 chainId
) internal view returns (IRegistry.SignedRegistration memory signedRegistration) {
BLS.G1Point memory pubkey = BLSUtils.toPublicKey(privateKey);
bytes memory message = abi.encode(_owner);
BLS.G2Point memory signature = BLSUtils.sign(message, privateKey, REGISTRATION_DOMAIN_SEPARATOR);
signedRegistration = IRegistry.SignedRegistration({ pubkey: pubkey, signature: signature });
bytes32 messageHash = keccak256(abi.encode(IRegistry.MessageType.Registration, _owner));
BLS.G2Point memory signature = BLSUtils.sign(privateKey, messageHash, signingDomain, signingId, nonce, chainId);
signedRegistration = IRegistry.SignedRegistration({ pubkey: pubkey, signature: signature, nonce: nonce });
}

/// @dev NOT MEANT FOR PRODUCTION USE
function _nRegistrations(uint256 _n, uint256 privateKeyStart, address _owner)
internal
view
returns (IRegistry.SignedRegistration[] memory signedRegistrations)
{
function _nRegistrations(
uint256 _n,
uint256 privateKeyStart,
address _owner,
bytes32 signingDomain,
bytes32 signingId,
bytes32 chainId
) internal view returns (IRegistry.SignedRegistration[] memory signedRegistrations) {
signedRegistrations = new IRegistry.SignedRegistration[](_n);
for (uint256 i = 0; i < _n; i++) {
signedRegistrations[i] = _signTestRegistration(privateKeyStart + i, _owner);
uint64 nonce = uint64(privateKeyStart + i);
signedRegistrations[i] =
_signTestRegistration(privateKeyStart + i, _owner, signingDomain, signingId, nonce, chainId);
}
}

/// @dev NOT MEANT FOR PRODUCTION USE
function _signTestDelegation(uint256 privateKey, ISlasher.Delegation memory delegation)
internal
view
returns (ISlasher.SignedDelegation memory signedDelegation)
{
BLS.G2Point memory signature = BLSUtils.sign(abi.encode(delegation), privateKey, DELEGATION_DOMAIN_SEPARATOR);
return ISlasher.SignedDelegation({ delegation: delegation, signature: signature });
function _signTestDelegation(
uint256 privateKey,
ISlasher.Delegation memory delegation,
bytes32 signingDomain,
bytes32 signingId,
uint64 nonce,
bytes32 chainId
) internal view returns (ISlasher.SignedDelegation memory signedDelegation) {
bytes32 messageHash = keccak256(abi.encode(IRegistry.MessageType.Delegation, delegation));
BLS.G2Point memory signature = BLSUtils.sign(privateKey, messageHash, signingDomain, signingId, nonce, chainId);
return
ISlasher.SignedDelegation({
delegation: delegation, signature: signature, nonce: nonce, signingId: signingId
});
}

/// @dev NOT MEANT FOR PRODUCTION USE
Expand All @@ -77,11 +90,15 @@ contract BaseScript is Script {
uint256 _proposerPrivateKey,
uint256 _delegatePrivateKeyStart,
address _committer,
uint256 _slot
uint256 _slot,
bytes32 _signingDomain,
bytes32 _signingId,
bytes32 _chainId
) internal view returns (ISlasher.SignedDelegation[] memory signedDelegations) {
BLS.G1Point memory proposer = BLSUtils.toPublicKey(_proposerPrivateKey);
signedDelegations = new ISlasher.SignedDelegation[](_n);
for (uint256 i = 0; i < _n; i++) {
uint64 _nonce = uint64(_delegatePrivateKeyStart + i);
BLS.G1Point memory delegate = BLSUtils.toPublicKey(_delegatePrivateKeyStart + i);
signedDelegations[i] = _signTestDelegation(
_proposerPrivateKey, // fixed proposer private key
Expand All @@ -91,22 +108,57 @@ contract BaseScript is Script {
committer: _committer,
slot: uint64(_slot),
metadata: ""
})
}),
_signingDomain,
_signingId,
_nonce,
_chainId
);
}
}

/// @dev NOT MEANT FOR PRODUCTION USE
function _signTestCommitment(uint256 privateKey, address slasher, uint256 commitmentType, bytes memory payload)
internal
pure
returns (ISlasher.SignedCommitment memory signedCommitment)
{
ISlasher.Commitment memory commitment =
ISlasher.Commitment({ commitmentType: uint64(commitmentType), payload: payload, slasher: slasher });
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, keccak256(abi.encode(commitment)));
bytes memory signature = abi.encodePacked(r, s, v);
return ISlasher.SignedCommitment({ commitment: commitment, signature: signature });
function _signTestCommitment(
uint256 privateKey,
address slasher,
uint256 commitmentType,
bytes memory payload,
bytes32 signingId,
uint64 nonce,
bytes32 chainId,
bytes32 signingDomain
) internal view returns (ISlasher.SignedCommitment memory signedCommitment) {
// Create CommitmentRequest and compute requestHash
ISlasher.CommitmentRequest memory request =
ISlasher.CommitmentRequest({ commitmentType: uint64(commitmentType), payload: payload, slasher: slasher });
bytes32 requestHash = keccak256(abi.encode(request));

// Create Commitment with requestHash
ISlasher.Commitment memory commitment = ISlasher.Commitment({
commitmentType: uint64(commitmentType), payload: payload, requestHash: requestHash, slasher: slasher
});

// Sign using ECDSA
bytes32 messageHash = keccak256(abi.encode(commitment));
bytes memory signature = _signECDSA(privateKey, messageHash, signingDomain, signingId, nonce, chainId);

return
ISlasher.SignedCommitment({
commitment: commitment, nonce: nonce, signingId: signingId, signature: signature
});
}

function _signECDSA(
uint256 privateKey,
bytes32 messageHash,
bytes32 signingDomain,
bytes32 signingId,
uint64 nonce,
bytes32 chainId
) internal view returns (bytes memory signature) {
bytes32 signingRoot = ECDSAUtils.computeSigningRoot(messageHash, signingDomain, signingId, nonce, chainId);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, signingRoot);
signature = abi.encodePacked(r, s, v);
}

function _writeSignedRegistrations(
Expand Down Expand Up @@ -166,6 +218,10 @@ contract BaseScript is Script {
vm.writeJson(vm.toString(abi.encode(proof.registration)), _jsonFile, ".registration");
vm.sleep(250);

// Write the signingId to the json file
vm.writeJson(vm.toString(proof.signingId), _jsonFile, ".signingId");
vm.sleep(250);

// Write the registrationRoot to the json file
vm.writeJson(vm.toString(proof.registrationRoot), _jsonFile, ".registrationRoot");
vm.sleep(250);
Expand All @@ -184,6 +240,8 @@ contract BaseScript is Script {
proof.registration = abi.decode(vm.parseJsonBytes(json, ".registration"), (IRegistry.SignedRegistration));

proof.registrationRoot = vm.parseJsonBytes32(json, ".registrationRoot");

proof.signingId = vm.parseJsonBytes32(json, ".signingId");
}

function _writeDelegation(ISlasher.SignedDelegation memory delegation, string memory outfile) internal {
Expand Down Expand Up @@ -285,12 +343,19 @@ contract BaseScript is Script {
return pubkeyPretty;
}

function _prettyPrintPubKey(IRegistry.SignedRegistration memory registration) internal {
function _prettyPrintPubKey(IRegistry.SignedRegistration memory registration) internal pure {
// duplicate to prevent weird foundry memory issues
BLS.G1Point memory pubkeyCopy = BLS.G1Point(
registration.pubkey.x_a, registration.pubkey.x_b, registration.pubkey.y_a, registration.pubkey.y_b
);
bytes memory pubkeyPretty = _prettyPubKey(abi.encode(BLSUtils.compress(pubkeyCopy)));
console.log("Pubkey: ", vm.toString(pubkeyPretty));
}

function _defaultSigningParams() internal returns (bytes32 signingDomain, bytes32 chainId) {
string memory configPath = "config/registry.json";
string memory configJson = vm.readFile(configPath);
signingDomain = vm.parseJsonBytes32(configJson, ".signingDomain");
chainId = vm.parseJsonBytes32(configJson, ".chainId");
}
}
4 changes: 3 additions & 1 deletion script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ contract DeployScript is Script {
fraudProofWindow: uint32(vm.parseJsonUint(configJson, ".fraudProofWindow")),
unregistrationDelay: uint32(vm.parseJsonUint(configJson, ".unregistrationDelay")),
slashWindow: uint32(vm.parseJsonUint(configJson, ".slashWindow")),
optInDelay: uint32(vm.parseJsonUint(configJson, ".optInDelay"))
optInDelay: uint32(vm.parseJsonUint(configJson, ".optInDelay")),
signingDomain: vm.parseJsonBytes32(configJson, ".signingDomain"),
chainId: vm.parseJsonBytes32(configJson, ".chainId")
});

// Deploy the Registry contract
Expand Down
16 changes: 10 additions & 6 deletions script/Getters.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,12 @@ contract GettersScript is BaseScript {
}

// forge script script/Getters.s.sol:GettersScript --sig "getSlasherCommitment(address,bytes32,address,string)" $REGISTRY_ADDRESS $REGISTRATION_ROOT $SLASHER $SLASHER_COMMITMENT_FILE --rpc-url $RPC_URL
function getSlasherCommitment(address _registry, bytes32 _registrationRoot, address _slasher, string memory outfile)
public
returns (IRegistry.SlasherCommitment memory slasherCommitment)
{
function getSlasherCommitment(
address _registry,
bytes32 _registrationRoot,
address _slasher,
string memory outfile
) public returns (IRegistry.SlasherCommitment memory slasherCommitment) {
// Get reference to the registry
IRegistry registry = IRegistry(_registry);

Expand All @@ -91,10 +93,11 @@ contract GettersScript is BaseScript {
console.log("SlasherCommitment written to", jsonFile);
}

// forge script script/Getters.s.sol:GettersScript --sig "getRegistrationProof(address,bytes,string,string)" $REGISTRY_ADDRESS $PUBKEY $SIGNED_REGISTRATIONS_FILE $REGISTRATION_PROOF_FILE --account $FOUNDRY_WALLET --rpc-url $RPC_URL
// forge script script/Getters.s.sol:GettersScript --sig "getRegistrationProof(address,bytes,bytes32,string,string)" $REGISTRY_ADDRESS $PUBKEY $SIGNING_ID $SIGNED_REGISTRATIONS_FILE $REGISTRATION_PROOF_FILE --account $FOUNDRY_WALLET --rpc-url $RPC_URL
function getRegistrationProof(
address _registry,
bytes memory pubkey,
bytes32 signingId,
string memory signedRegistrationsFile,
string memory outfile
) public returns (IRegistry.RegistrationProof memory) {
Expand Down Expand Up @@ -131,7 +134,8 @@ contract GettersScript is BaseScript {
IRegistry registry = IRegistry(_registry);

// Call URC.getRegistrationProof
IRegistry.RegistrationProof memory proof = registry.getRegistrationProof(registrations, owner, leafIndex);
IRegistry.RegistrationProof memory proof =
registry.getRegistrationProof(registrations, owner, leafIndex, signingId);

// Check that the registration proof is valid
registry.verifyMerkleProof(proof);
Expand Down
8 changes: 4 additions & 4 deletions script/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ forge script script/Deploy.s.sol:DeployScript --sig "deploy()" --rpc-url $RPC_UR

Running this script will generate `N` `SignedRegistration` messages signed by deterministic BLS private keys. The `SignedRegistrations` will be saved to `script/output/{$SIGNED_REGISTRATIONS_FILE}` following the template file [SignedRegistrations.json](./output/SignedRegistrations.json).
```bash
forge script script/Register.s.sol:RegisterScript --sig "nDummyRegistrations(uint256,address,string)" $N $OWNER $SIGNED_REGISTRATIONS_FILE
forge script script/Register.s.sol:RegisterScript --sig "nDummyRegistrations(uint256,address,bytes32,string)" $N $OWNER $SIGNING_ID $SIGNED_REGISTRATIONS_FILE
```
### Registering to the URC
Running this script will call `register()` using the supplied `SignedRegistrations` located in `script/output/{$SIGNED_REGISTRATIONS_FILE}` following the template file [SignedRegistrations.json](./output/SignedRegistrations.json). `$COLLATERAL` wei will be transferred from the caller's account to the URC.
```bash
forge script script/Register.s.sol:RegisterScript --sig "register(address,uint256,string)" $REGISTRY_ADDRESS $COLLATERAL $SIGNED_REGISTRATIONS_FILE --account $FOUNDRY_WALLET --rpc-url $RPC_URL --broadcast
forge script script/Register.s.sol:RegisterScript --sig "register(address,uint256,bytes32,string)" $REGISTRY_ADDRESS $COLLATERAL $SIGNING_ID $SIGNED_REGISTRATIONS_FILE --account $FOUNDRY_WALLET --rpc-url $RPC_URL --broadcast
```

### Unregistering from the URC
Expand Down Expand Up @@ -70,7 +70,7 @@ forge script script/Collateral.s.sol:CollateralScript --sig "claimSlashedCollate
Running this script will generate and register an intentionally invalid BLS registration. The registration will be saved to `script/output/{$SIGNED_REGISTRATIONS_FILE}` following the template file [SignedRegistrations.json](./output/SignedRegistrations.json).

```bash
forge script script/Slashing.s.sol:SlashingScript --sig "registerBadRegistration(address,address,string)" $REGISTRY_ADDRESS $OWNER $SIGNED_REGISTRATIONS_FILE --account $FOUNDRY_WALLET --rpc-url $RPC_URL --broadcast
forge script script/Slashing.s.sol:SlashingScript --sig "registerBadRegistration(address,address,bytes32,string)" $REGISTRY_ADDRESS $OWNER $SIGNING_ID $SIGNED_REGISTRATIONS_FILE --account $FOUNDRY_WALLET --rpc-url $RPC_URL --broadcast
```

### Slashing a registration
Expand Down Expand Up @@ -110,7 +110,7 @@ forge script script/Getters.s.sol:GettersScript --sig "getOperatorData(address,b
Given a compressed 48-byte-hex-encoded BLS `$PUBKEY` and a `SignedRegistrations` file located in `script/output/{$SIGNED_REGISTRATIONS_FILE}`, the script will write the `RegistrationProof` for the specified pubkey to `script/output/{$REGISTRATION_PROOF_FILE}` following the template file [`RegistrationProof.json`](./output/RegistrationProof.json).

```bash
forge script script/Getters.s.sol:GettersScript --sig "getRegistrationProof(address,bytes,string,string)" $REGISTRY_ADDRESS $PUBKEY $SIGNED_REGISTRATIONS_FILE $REGISTRATION_PROOF_FILE --account $FOUNDRY_WALLET --rpc-url $RPC_URL
forge script script/Getters.s.sol:GettersScript --sig "getRegistrationProof(address,bytes,bytes32,string,string)" $REGISTRY_ADDRESS $PUBKEY $SIGNING_ID $SIGNED_REGISTRATIONS_FILE $REGISTRATION_PROOF_FILE --account $FOUNDRY_WALLET --rpc-url $RPC_URL
```

### getSlasherCommitment
Expand Down
Loading