Skip to content

Commit

Permalink
Fix signer registration via attestation (#33)
Browse files Browse the repository at this point in the history
* Bump nitro-validator
Add register-signer script

* forge fmt

* Fix parsing of pubkey address
  • Loading branch information
mdehoog authored Dec 11, 2024
1 parent 98542e5 commit a1fdac8
Show file tree
Hide file tree
Showing 12 changed files with 5,314 additions and 23 deletions.
46 changes: 32 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,39 @@ guard-%:

define abigen
echo "Generating bindings for $(1)"
jq -r '.bytecode.object' out/$(1).sol/$(1).json > out/$(1).sol/$(1).bin
cp out/$(1).sol/$(1).$(3).json out/$(1).sol/$(1).json 2>/dev/null || true
jq -r '.bytecode.object' out/$(1).sol/$(1).json > out/$(1).sol/$(1).bin
jq -r '.abi' out/$(1).sol/$(1).json > out/$(1).sol/$(1).abi
abigen --abi out/$(1).sol/$(1).abi --bin out/$(1).sol/$(1).bin --pkg bindings --type $(1) --out bindings/$(2).go
endef

define verify
deploy=$(1); \
version=$(2); \
addresses=$$(jq -r '.transactions[] | select(.transactionType=="CREATE" or .transactionType=="CREATE2") | .contractAddress' $$deploy); \
for address in $$addresses; do \
name=$$(jq -r --arg address "$$address" '.transactions[] | select((.transactionType=="CREATE" or .transactionType=="CREATE2") and .contractAddress==$$address) | .contractName' $$deploy); \
arguments=$$(jq -r --arg address "$$address" '.transactions[] | select((.transactionType=="CREATE" or .transactionType=="CREATE2") and .contractAddress==$$address) | .arguments // [] | join(" ")' $$deploy); \
namewithoutversion=$${name%.*.*.*}; \
constructor=$$(jq '.abi[] | select(.type=="constructor")' out/$$namewithoutversion.sol/$$name.json | jq -r '.inputs | map(.type) | join(",")'); \
echo; \
echo "Verifying $$namewithoutversion @ $$address using constructor($$constructor) $$arguments"; \
constructor_args=$$(cast abi-encode "constructor($$constructor)" $$arguments); \
forge verify-contract --compiler-version $$version --watch --verifier-url https://api-sepolia.basescan.org/api --constructor-args $$constructor_args $$address $$namewithoutversion ; \
done
endef

.PHONY: bindings
bindings:
go install github.com/ethereum/go-ethereum/cmd/[email protected]
forge build
mkdir -p bindings
@$(call abigen,"OutputOracle","output_oracle")
@$(call abigen,"Portal","portal")
@$(call abigen,"DeployChain","deploy_chain")
@$(call abigen,"OutputOracle","output_oracle","0.8.15")
@$(call abigen,"Portal","portal","0.8.15")
@$(call abigen,"DeployChain","deploy_chain","0.8.15")
@$(call abigen,"CertManager","cert_manager","0.8.15")
@$(call abigen,"SystemConfigGlobal","system_config_global","0.8.15")
@$(call abigen,"GnosisSafe","gnosis_safe","0.8.15")

.PHONY: deploy-cert-manager
deploy-cert-manager: guard-IMPL_SALT guard-DEPLOY_PRIVATE_KEY guard-RPC_URL
Expand All @@ -27,20 +47,18 @@ deploy: guard-IMPL_SALT guard-DEPLOY_CONFIG_PATH guard-DEPLOY_PRIVATE_KEY guard-
@forge script DeploySystem --sig deploy --rpc-url $(RPC_URL) \
--private-key $(DEPLOY_PRIVATE_KEY) --broadcast

.PHONY: deploy-deploy-chain
deploy-deploy-chain: guard-IMPL_SALT guard-DEPLOY_PRIVATE_KEY guard-RPC_URL
@forge script DeployDeployChain --rpc-url $(RPC_URL) \
--private-key $(DEPLOY_PRIVATE_KEY) --broadcast

.PHONY: testnet
testnet: guard-L1_URL guard-DEPLOY_PRIVATE_KEY
DEPLOY_CHAIN_ADDRESS=$${DEPLOY_CHAIN_ADDRESS:-$$(jq -r ".DeployChain" deployments/84532-deploy.json)} \
go run ./testnet

.PHONY: verify
verify:
deploy=broadcast/DeploySystem.s.sol/84532/run-1733867021.json; \
addresses=$$(jq -r '.transactions[] | select(.transactionType=="CREATE" or .transactionType=="CREATE2") | .contractAddress' $$deploy); \
for address in $$addresses; do \
name=$$(jq -r --arg address "$$address" '.transactions[] | select((.transactionType=="CREATE" or .transactionType=="CREATE2") and .contractAddress==$$address) | .contractName' $$deploy); \
arguments=$$(jq -r --arg address "$$address" '.transactions[] | select((.transactionType=="CREATE" or .transactionType=="CREATE2") and .contractAddress==$$address) | .arguments // [] | join(" ")' $$deploy); \
constructor=$$(jq '.abi[] | select(.type=="constructor")' out/$$name.sol/$$name.json | jq -r '.inputs | map(.type) | join(",")'); \
echo "Verifying $$name @ $$address using constructor($$constructor) $$arguments"; \
constructor_args=$$(cast abi-encode "constructor($$constructor)" $$arguments); \
forge verify-contract --watch --verifier-url https://api-sepolia.basescan.org/api --constructor-args $$constructor_args $$address $$name ; \
done
@$(call verify,"broadcast/DeployCertManager.s.sol/84532/run-1733890597.json","0.8.24")
@$(call verify,"broadcast/DeploySystem.s.sol/84532/run-1733867021.json","0.8.15")
@$(call verify,"broadcast/DeployDeployChain.s.sol/84532/run-1733884066.json","0.8.15")
595 changes: 595 additions & 0 deletions bindings/cert_manager.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bindings/deploy_chain.go

Large diffs are not rendered by default.

3,152 changes: 3,152 additions & 0 deletions bindings/gnosis_safe.go

Large diffs are not rendered by default.

1,295 changes: 1,295 additions & 0 deletions bindings/system_config_global.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion deployments/84532-certmanager.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"CertManager": "0x00a452e7B56052f0beC5EF863F77eDDfd81938C4"
"CertManager": "0xD42fd50A9A8eE3F127A11AEACD4ADAA67Da7FE3B"
}
4 changes: 2 additions & 2 deletions deployments/84532-deploy.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"AddressManager": "0xdcB1Ee2F0F35F8053C1EbD700c030180f7b2C14b",
"AnchorStateRegistryProxy": "0x0000000000000000000000000000000000000001",
"CertManager": "0x00a452e7B56052f0beC5EF863F77eDDfd81938C4",
"CertManager": "0xD42fd50A9A8eE3F127A11AEACD4ADAA67Da7FE3B",
"DelayedWETHProxy": "0x0000000000000000000000000000000000000001",
"DeployChain": "0x8B4dB9468126EA0AA6EC8f1FAEb32173de3A27c7",
"DisputeGameFactoryProxy": "0x0000000000000000000000000000000000000001",
Expand All @@ -27,7 +27,7 @@
"SuperchainConfig": "0xC77dB710C47b6e294D3d544572a10187e8Ef6b2C",
"SuperchainConfigProxy": "0xCf940f9c053092d07EB62DaB59D0AFddF426dE67",
"SystemConfig": "0x8aB8559E6C661eFEB0a44C0f08E180CEe344dABE",
"SystemConfigGlobal": "0x9C9a3B1c8676c1E0A6Ebb9402E7354930Bc52A59",
"SystemConfigGlobal": "0x0933e802B0977fe6287C48a0F601b39244118E04",
"SystemConfigGlobalProxy": "0x53200eC3d6E91E7Ba1fD1087D38430F43501C9Fb",
"SystemConfigProxy": "0x57708f73fF01e8697799B38f47Fbd65bDf9138Bc",
"SystemOwnerSafe": "0xFCD4AfF397A2F9D2a435B64AdA1A70efC59310aD"
Expand Down
2 changes: 1 addition & 1 deletion lib/nitro-validator
181 changes: 181 additions & 0 deletions register-signer/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package main

import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"os"
"time"

"github.com/base-org/op-enclave/bindings"
"github.com/base-org/op-enclave/op-withdrawer/withdrawals"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/hf/nitrite"
)

type deployment struct {
SystemConfigGlobalProxy common.Address `json:"SystemConfigGlobalProxy"`
}

func main() {
var attestationHex string
var rpcUrl string
var privateKeyHex string
var deploymentFile string
flag.StringVar(&attestationHex, "attestation", "", "attestation hex")
flag.StringVar(&rpcUrl, "rpc", "https://sepolia.base.org", "rpc url")
flag.StringVar(&privateKeyHex, "private-key", "", "private key")
flag.StringVar(&deploymentFile, "deployment", "deployments/84532-deploy.json", "deployment file")
flag.Parse()

if attestationHex == "" || privateKeyHex == "" {
flag.Usage()
os.Exit(1)
}

attestation, err := hexutil.Decode(attestationHex)
if err != nil {
panic(err)
}
privateKey, err := hexutil.Decode(privateKeyHex)
if err != nil {
panic(err)
}

res, err := nitrite.Verify(attestation, nitrite.VerifyOptions{})
if err != nil {
panic(err)
}

ctx := context.Background()
client, err := ethclient.DialContext(ctx, rpcUrl)
if err != nil {
panic(err)
}

deploy, err := os.ReadFile(deploymentFile)
if err != nil {
panic(err)
}
var d deployment
err = json.Unmarshal(deploy, &d)
if err != nil {
panic(err)
}

if bytes.Equal(common.Address{}.Bytes(), d.SystemConfigGlobalProxy.Bytes()) {
panic("SystemConfigGlobalProxy address not found in deployment file")
}

key, err := crypto.ToECDSA(privateKey)
if err != nil {
panic(err)
}

chainId, err := client.ChainID(ctx)
if err != nil {
panic(err)
}
signer := types.LatestSignerForChainID(chainId)
auth := &bind.TransactOpts{
From: crypto.PubkeyToAddress(key.PublicKey),
Signer: func(_ common.Address, tx *types.Transaction) (*types.Transaction, error) {
return types.SignTx(tx, signer, key)
},
}

systemConfigGlobal, err := bindings.NewSystemConfigGlobal(d.SystemConfigGlobalProxy, client)
if err != nil {
panic(err)
}

pub, err := crypto.UnmarshalPubkey(res.Document.PublicKey)
if err != nil {
panic(err)
}
signerAddr := crypto.PubkeyToAddress(*pub)
validSigner, err := systemConfigGlobal.ValidSigners(&bind.CallOpts{}, signerAddr)
if err != nil {
panic(err)
}
fmt.Printf("Public key: %s\n", hexutil.Encode(res.Document.PublicKey))
fmt.Printf("Signer: %s\n", signerAddr.String())
if validSigner {
fmt.Printf("Signer already registered: %s\n", signerAddr.String())
return
}

certManagerAddr, err := systemConfigGlobal.CertManager(&bind.CallOpts{})
if err != nil {
panic(err)
}
certManager, err := bindings.NewCertManager(certManagerAddr, client)
if err != nil {
panic(err)
}

verifyCert := func(cert []byte, ca bool, parentCertHash common.Hash) common.Hash {
certHash := crypto.Keccak256Hash(cert)
verified, err := certManager.Verified(&bind.CallOpts{}, certHash)
if err != nil {
panic(err)
}
if len(verified) == 0 {
tx, err := certManager.VerifyCert(auth, cert, ca, parentCertHash)
if err != nil {
panic(err)
}
receipt, err := withdrawals.WaitForReceipt(ctx, client, tx.Hash(), 2*time.Second)
if err != nil {
panic(err)
}
fmt.Printf("Verified cert: %s, tx: %s\n", certHash.String(), receipt.TxHash.String())
} else {
fmt.Printf("Cert already verified: %s\n", certHash.String())
}
return certHash
}

parentCertHash := crypto.Keccak256Hash(res.Document.CABundle[0])
for i := 0; i < len(res.Document.CABundle); i++ {
cert := res.Document.CABundle[i]
parentCertHash = verifyCert(cert, true, parentCertHash)
}
verifyCert(res.Document.Certificate, false, parentCertHash)

pcr0Hash := crypto.Keccak256Hash(res.Document.PCRs[0])
valid, err := systemConfigGlobal.ValidPCR0s(&bind.CallOpts{}, pcr0Hash)
if err != nil {
panic(err)
}
if !valid {
tx, err := systemConfigGlobal.RegisterPCR0(auth, res.Document.PCRs[0])
if err != nil {
panic(err)
}
receipt, err := withdrawals.WaitForReceipt(ctx, client, tx.Hash(), 2*time.Second)
if err != nil {
panic(err)
}
fmt.Printf("Registered PCR0, tx: %s\n", receipt.TxHash.String())
} else {
fmt.Printf("PCR0 already registered: %s\n", pcr0Hash.String())
}

tx, err := systemConfigGlobal.RegisterSigner(auth, res.COSESign1, res.Signature)
if err != nil {
panic(err)
}
receipt, err := withdrawals.WaitForReceipt(ctx, client, tx.Hash(), 2*time.Second)
if err != nil {
panic(err)
}
fmt.Printf("Registered signer, tx: %s\n", receipt.TxHash.String())
}
47 changes: 47 additions & 0 deletions script/UpgradeSystemConfigGlobal.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {Script} from "forge-std/Script.sol";
import {console2 as console} from "forge-std/console2.sol";
import {DeployChain} from "../src/DeployChain.sol";
import {Artifacts} from "@eth-optimism-bedrock/scripts/Artifacts.s.sol";
import {ProxyAdmin} from "@eth-optimism-bedrock/src/universal/ProxyAdmin.sol";
import {SystemConfigGlobal} from "../src/SystemConfigGlobal.sol";
import {ICertManager} from "@nitro-validator/ICertManager.sol";
import {IGnosisSafe, Enum} from "@eth-optimism-bedrock/scripts/interfaces/IGnosisSafe.sol";

contract UpgradeSystemConfigGlobal is Script, Artifacts {
function run() public {
_loadAddresses(deploymentOutfile);

bytes memory signature = abi.encodePacked(uint256(uint160(msg.sender)), bytes32(0), uint8(1));

console.log("Deploying SystemConfigGlobal implementation");
vm.startBroadcast();

address addr_ = address(new SystemConfigGlobal{salt: _implSalt()}(ICertManager(mustGetAddress("CertManager"))));
bytes memory data =
abi.encodeCall(ProxyAdmin.upgrade, (payable(mustGetAddress("SystemConfigGlobalProxy")), address(addr_)));
IGnosisSafe(mustGetAddress("SystemOwnerSafe")).execTransaction({
to: mustGetAddress("ProxyAdmin"),
value: 0,
data: data,
operation: Enum.Operation.Call,
safeTxGas: 0,
baseGas: 0,
gasPrice: 0,
gasToken: address(0),
refundReceiver: payable(address(0)),
signatures: signature
});

vm.stopBroadcast();

delete _namedDeployments["SystemConfigGlobal"];
save("SystemConfigGlobal", addr_);
}

function _implSalt() internal view returns (bytes32 _env) {
_env = keccak256(bytes(vm.envOr("IMPL_SALT", string("ethers phoenix"))));
}
}
9 changes: 6 additions & 3 deletions src/SystemConfigGlobal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ pragma solidity ^0.8.0;
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {ISemver} from "@eth-optimism-bedrock/src/universal/interfaces/ISemver.sol";
import {NitroValidator} from "@nitro-validator/NitroValidator.sol";
import {CborDecode} from "@nitro-validator/CborDecode.sol";
import {LibBytes} from "@nitro-validator/LibBytes.sol";
import {LibCborElement, CborElement, CborDecode} from "@nitro-validator/CborDecode.sol";
import {ICertManager} from "@nitro-validator/ICertManager.sol";

contract SystemConfigGlobal is OwnableUpgradeable, ISemver, NitroValidator {
using LibBytes for bytes;
using CborDecode for bytes;
using LibCborElement for CborElement;

uint256 public constant MAX_AGE = 60 minutes;

Expand Down Expand Up @@ -56,8 +59,8 @@ contract SystemConfigGlobal is OwnableUpgradeable, ISemver, NitroValidator {

require(ptrs.timestamp + MAX_AGE > block.timestamp, "attestation too old");

bytes memory publicKey = attestationTbs.slice(ptrs.publicKey);
address enclaveAddress = address(uint160(uint256(keccak256(publicKey))));
bytes32 publicKeyHash = attestationTbs.keccak(ptrs.publicKey.start() + 1, ptrs.publicKey.length() - 1);
address enclaveAddress = address(uint160(uint256(publicKeyHash)));
validSigners[enclaveAddress] = true;
}

Expand Down
2 changes: 1 addition & 1 deletion test/SystemConfigGlobal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ contract SystemConfigGlobalTest is Test {
(bytes memory attestationTbs, bytes memory signature) = systemConfigGlobal.decodeAttestationTbs(attestation);
systemConfigGlobal.registerSigner(attestationTbs, signature);

address expectedSigner = 0xe04d808785d2BBdE18E9D0C01c05FB8CE0711f2d;
address expectedSigner = 0x874a4c5675cd4850dB08bD9A1e3184ED239087e4;
assertTrue(systemConfigGlobal.validSigners(expectedSigner));
}
}

0 comments on commit a1fdac8

Please sign in to comment.