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

feat: cert verification #42

Merged
merged 8 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .github/workflows/actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,6 @@ jobs:
- name: Run holesky tests
env:
SIGNER_PRIVATE_KEY: ${{ secrets.SIGNER_PRIVATE_KEY }}
ETHEREUM_RPC: ${{ secrets.ETHEREUM_RPC }}
run: |
SIGNER_PRIVATE_KEY=$SIGNER_PRIVATE_KEY make holesky-test
SIGNER_PRIVATE_KEY=$SIGNER_PRIVATE_KEY ETHEREUM_RPC=$ETHEREUM_RPC make holesky-test
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,28 @@ Additional CLI args are provided for targeting an EigenDA network backend:
* `--eigenda-cache-path`: Directory path to dump cached SRS tables
* `--eigenda-max-blob-length`: The maximum blob length that this EigenDA sidecar proxy should expect to be written or read from EigenDA. This configuration setting is used to determine how many SRS points should be loaded into memory for generating/verifying KZG commitments returned by the EigenDA disperser. Valid byte units are either base-2 or base-10 byte amounts (not bits), e.g. `30 MiB`, `4Kb`, `30MB`. The maximum blob size is a little more than `1GB`.


### Certificate verification
For additional security, there is a cert verification feature which verifies the blob metadata read from the disperser to ensure that:
1. The respective batch hash can be computed locally and matches the one persisted on-chain in the `ServiceManager` contract
2. The blob inclusion proof can be merkalized to generate the proper batch root
3. All quorum params are adequately defined and expressed when compared to their on-chain counterparts

To target this feature, the following CLI args should be provided:
* `--eigenda-svc-manager-addr`: The deployed EigenDA service manager address. The list can be found [here](https://github.com/Layr-Labs/eigenlayer-middleware/?tab=readme-ov-file#current-mainnet-deployment).
epociask marked this conversation as resolved.
Show resolved Hide resolved
* `--eigenda-eth-rpc` : JSON RPC node endpoint for the Ethereum network used for finalizing DA blobs. See available list [here](https://docs.eigenlayer.xyz/eigenda/networks/).


### In-Memory Storage

An ephemeral memory store backend can be used for faster feedback testing when performing rollup integrations. The following cli args can be used to target the feature:

* `--memstore.enabled`: Boolean feature flag
* `--memstore.expiration`: Duration for which a blob will exist

## Metrics
To the see list of available metrics, run `./bin/eigenda-proxy doc metrics`

## Running Locally

1. Compile binary: `make eigenda-proxy`
Expand Down
7 changes: 3 additions & 4 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,22 @@ import (
"github.com/joho/godotenv"
"github.com/urfave/cli/v2"

"github.com/Layr-Labs/eigenda-proxy/metrics"
"github.com/Layr-Labs/eigenda-proxy/server"
"github.com/ethereum-optimism/optimism/op-node/metrics"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/metrics/doc"
"github.com/ethereum-optimism/optimism/op-service/opio"
)

var Version = "v0.0.1"
var Version = "v1.1.0"

func main() {
oplog.SetupDefaults()

app := cli.NewApp()
app.Flags = cliapp.ProtectFlags(server.Flags)
app.Version = opservice.FormatVersion(Version, "", "", "")
app.Version = Version
app.Name = "eigenda-proxy"
app.Usage = "EigenDA Proxy Sidecar Service"
app.Description = "Service for more trustless and secure interactions with EigenDA"
Expand Down
59 changes: 58 additions & 1 deletion common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package common

import (
"fmt"
"math/big"
"strconv"
"strings"

Expand All @@ -12,7 +13,63 @@ var (
ErrInvalidDomainType = fmt.Errorf("invalid domain type")
)

type Certificate = disperser.BlobInfo
// G1Point struct to represent G1Point in Solidity
type G1Point struct {
X *big.Int
Y *big.Int
}

// QuorumBlobParam struct to represent QuorumBlobParam in Solidity
type QuorumBlobParam struct {
QuorumNumber uint8
AdversaryThresholdPercentage uint8
ConfirmationThresholdPercentage uint8
ChunkLength uint32
}

// BlobHeader struct to represent BlobHeader in Solidity
type BlobHeader struct {
Commitment G1Point
DataLength uint32
QuorumBlobParams []QuorumBlobParam
}

type Certificate disperser.BlobInfo

func (c *Certificate) BlobIndex() uint32 {
return c.BlobVerificationProof.BlobIndex
}

func (c *Certificate) BatchHeaderRoot() []byte {
return c.BlobVerificationProof.BatchMetadata.BatchHeader.BatchRoot
}

func (c *Certificate) ReadBlobHeader() BlobHeader {
// parse quorum params

qps := make([]QuorumBlobParam, len(c.BlobHeader.BlobQuorumParams))
for i, qp := range c.BlobHeader.BlobQuorumParams {
qps[i] = QuorumBlobParam{
QuorumNumber: uint8(qp.QuorumNumber),
AdversaryThresholdPercentage: uint8(qp.AdversaryThresholdPercentage),
ConfirmationThresholdPercentage: uint8(qp.ConfirmationThresholdPercentage),
ChunkLength: qp.ChunkLength,
}
}

return BlobHeader{
Commitment: G1Point{
X: new(big.Int).SetBytes(c.BlobHeader.Commitment.X),
Y: new(big.Int).SetBytes(c.BlobHeader.Commitment.Y),
},
DataLength: c.BlobHeader.DataLength,
QuorumBlobParams: qps,
}
}

func (c *Certificate) Proof() *disperser.BlobVerificationProof {
return c.BlobVerificationProof
}

// DomainType is a enumeration type for the different data domains for which a
// blob can exist between
Expand Down
6 changes: 0 additions & 6 deletions e2e/optimism_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,6 @@ func TestOptimism(gt *testing.T) {

// verify
op_stack.sequencer.ActL2PipelineFull(t)

// expire the challenge window so these blocks can no longer be challenged
op_stack.ActL1Blocks(t, op_stack.plasmaCfg.ChallengeWindow)

// advance derivation and finalize plasma via the L1 signal
op_stack.sequencer.ActL2PipelineFull(t)
op_stack.ActL1Finalized(t)

// assert that EigenDA proxy's was written and read from
Expand Down
2 changes: 1 addition & 1 deletion e2e/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestPlasmaClient(t *testing.T) {

// 1 - write arbitrary data to EigenDA

var testPreimage = []byte("inter-subjective and not objective!")
var testPreimage = []byte("feel the rain on your skin!")

t.Log("Setting input data on proxy server...")
commit, err := daClient.SetInput(ts.Ctx, testPreimage)
Expand Down
9 changes: 9 additions & 0 deletions e2e/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

const (
privateKey = "SIGNER_PRIVATE_KEY"
ethRPC = "ETHEREUM_RPC"
transport = "http"
svcName = "eigenda_proxy"
host = "127.0.0.1"
Expand All @@ -43,6 +44,12 @@ func CreateTestSuite(t *testing.T, useMemory bool) (TestSuite, func()) {
t.Fatal("SIGNER_PRIVATE_KEY environment variable not set")
}

// load node url from environment
ethRPC := os.Getenv(ethRPC)
if ethRPC == "" && !useMemory {
t.Fatal("ETHEREUM_RPC environment variable is not set")
}

log := oplog.NewLogger(os.Stdout, oplog.CLIConfig{
Level: log.LevelDebug,
Format: oplog.FormatLogFmt,
Expand All @@ -57,6 +64,8 @@ func CreateTestSuite(t *testing.T, useMemory bool) (TestSuite, func()) {
DisableTLS: false,
SignerPrivateKeyHex: pk,
},
EthRPC: ethRPC,
SvcManagerAddr: "0xD4A7E1Bd8015057293f0D0A557088c286942e84b", // incompatible with non holeskly networks
CacheDir: "../operator-setup/resources/SRSTables",
G1Path: "../operator-setup/resources/g1_abbr.point",
G2Path: "../test/resources/kzg/g2.point", // do we need this?
Expand Down
39 changes: 37 additions & 2 deletions eigenda/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/Layr-Labs/eigenda-proxy/common"
"github.com/Layr-Labs/eigenda-proxy/verify"
"github.com/Layr-Labs/eigenda/api/clients"
"github.com/Layr-Labs/eigenda/api/clients/codecs"
"github.com/Layr-Labs/eigenda/encoding/kzg"
Expand All @@ -16,6 +17,8 @@ import (

const (
RPCFlagName = "eigenda-rpc"
EthRPCFlagName = "eigenda-eth-rpc"
SvcManagerAddrFlagName = "eigenda-svc-manager-addr"
StatusQueryRetryIntervalFlagName = "eigenda-status-query-retry-interval"
StatusQueryTimeoutFlagName = "eigenda-status-query-timeout"
DisableTlsFlagName = "eigenda-disable-tls"
Expand Down Expand Up @@ -44,6 +47,10 @@ type Config struct {
// The blob encoding version to use when writing blobs from the high level interface.
PutBlobEncodingVersion codecs.BlobEncodingVersion

// ETH vars
EthRPC string
SvcManagerAddr string

// KZG vars
CacheDir string

Expand Down Expand Up @@ -73,21 +80,37 @@ func (c *Config) GetMaxBlobLength() (uint64, error) {
return c.maxBlobLengthBytes, nil
}

func (c *Config) KzgConfig() *kzg.KzgConfig {
func (c *Config) VerificationCfg() *verify.Config {
numBytes, err := c.GetMaxBlobLength()
if err != nil {
panic(fmt.Errorf("Check() was not called on config object, err is not nil: %w", err))
}

numPointsNeeded := uint64(math.Ceil(float64(numBytes) / BytesPerSymbol))
return &kzg.KzgConfig{

kzgCfg := &kzg.KzgConfig{
G1Path: c.G1Path,
G2PowerOf2Path: c.G2PowerOfTauPath,
CacheDir: c.CacheDir,
SRSOrder: numPointsNeeded,
SRSNumberToLoad: numPointsNeeded,
NumWorker: uint64(runtime.GOMAXPROCS(0)),
}

if c.EthRPC == "" || c.SvcManagerAddr == "" {
return &verify.Config{
Verify: false,
KzgConfig: kzgCfg,
}
}

return &verify.Config{
Verify: true,
RPCURL: c.EthRPC,
SvcManagerAddr: c.SvcManagerAddr,
KzgConfig: kzgCfg,
}

}

// NewConfig parses the Config from the provided flags or environment variables.
Expand All @@ -109,6 +132,8 @@ func ReadConfig(ctx *cli.Context) Config {
G2PowerOfTauPath: ctx.String(G2TauFlagName),
CacheDir: ctx.String(CachePathFlagName),
MaxBlobLength: ctx.String(MaxBlobLengthFlagName),
SvcManagerAddr: ctx.String(SvcManagerAddrFlagName),
EthRPC: ctx.String(EthRPCFlagName),
}
return cfg
}
Expand Down Expand Up @@ -199,5 +224,15 @@ func CLIFlags(envPrefix string) []cli.Flag {
Usage: "Directory path to SRS tables",
EnvVars: prefixEnvVars("TARGET_CACHE_PATH"),
},
&cli.StringFlag{
Name: EthRPCFlagName,
Usage: "JSON RPC node endpoint for the Ethereum network used for finalizing DA blobs.",
EnvVars: prefixEnvVars("ETH_RPC"),
},
&cli.StringFlag{
Name: SvcManagerAddrFlagName,
Usage: "Deployed EigenDA service manager address.",
EnvVars: prefixEnvVars("SERVICE_MANAGER_ADDR"),
},
}
}
11 changes: 9 additions & 2 deletions server/load_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@ import (
)

func LoadStore(cfg CLIConfig, ctx context.Context, log log.Logger) (store.Store, error) {
log.Info("Using eigenda backend")
daCfg := cfg.EigenDAConfig
vCfg := daCfg.VerificationCfg()

verifier, err := verify.NewVerifier(daCfg.KzgConfig())
verifier, err := verify.NewVerifier(vCfg, log)
if err != nil {
return nil, err
}

if vCfg.Verify {
log.Info("Certificate verification with Ethereum enabled")
} else {
log.Warn("Verification disabled")
}

maxBlobLength, err := daCfg.GetMaxBlobLength()
if err != nil {
return nil, err
Expand All @@ -28,6 +34,7 @@ func LoadStore(cfg CLIConfig, ctx context.Context, log log.Logger) (store.Store,
return store.NewMemStore(ctx, &cfg.MemStoreCfg, verifier, log, maxBlobLength)
}

log.Info("Using eigenda backend")
client, err := clients.NewEigenDAClient(log, daCfg.ClientConfig)
if err != nil {
return nil, err
Expand Down
13 changes: 9 additions & 4 deletions store/eigenda.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,15 @@ func (e EigenDAStore) Get(ctx context.Context, key []byte, domain common.DomainT
// reencode blob for verification
encodedBlob, err := e.client.GetCodec().EncodeBlob(decodedBlob)
if err != nil {
return nil, fmt.Errorf("EigenDA client failed to reencode blob: %w", err)
return nil, fmt.Errorf("EigenDA client failed to re-encode blob: %w", err)
}

err = e.verifier.Verify(cert.BlobHeader.Commitment, encodedBlob)
err = e.verifier.VerifyCommitment(cert.BlobHeader.Commitment, encodedBlob)
if err != nil {
return nil, err
}

err = e.verifier.VerifyCert(&cert)
if err != nil {
return nil, err
}
Expand All @@ -73,9 +78,9 @@ func (e EigenDAStore) Put(ctx context.Context, value []byte) (comm []byte, err e

encodedBlob, err := e.client.GetCodec().EncodeBlob(value)
if err != nil {
return nil, fmt.Errorf("EigenDA client failed to reencode blob: %w", err)
return nil, fmt.Errorf("EigenDA client failed to re-encode blob: %w", err)
}
err = e.verifier.Verify(cert.BlobHeader.Commitment, encodedBlob)
err = e.verifier.VerifyCommitment(cert.BlobHeader.Commitment, encodedBlob)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion store/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (e *MemStore) Get(ctx context.Context, commit []byte, domain eigendacommon.
}

// Don't need to do this really since it's a mock store
err = e.verifier.Verify(cert.BlobHeader.Commitment, encodedBlob)
err = e.verifier.VerifyCommitment(cert.BlobHeader.Commitment, encodedBlob)
if err != nil {
return nil, err
}
Expand Down
16 changes: 14 additions & 2 deletions store/memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ func TestGetSet(t *testing.T) {
SRSNumberToLoad: 3000,
NumWorker: uint64(runtime.GOMAXPROCS(0)),
}
verifier, err := verify.NewVerifier(kzgConfig)

cfg := &verify.Config{
Verify: false,
KzgConfig: kzgConfig,
}

verifier, err := verify.NewVerifier(cfg, nil)
assert.NoError(t, err)

ms, err := NewMemStore(
Expand Down Expand Up @@ -68,7 +74,13 @@ func TestExpiration(t *testing.T) {
SRSNumberToLoad: 3000,
NumWorker: uint64(runtime.GOMAXPROCS(0)),
}
verifier, err := verify.NewVerifier(kzgConfig)

cfg := &verify.Config{
Verify: false,
KzgConfig: kzgConfig,
}

verifier, err := verify.NewVerifier(cfg, nil)
assert.NoError(t, err)

ms, err := NewMemStore(
Expand Down
Loading
Loading