From ba05698af4ff3c829370ec3b720ee39eec344531 Mon Sep 17 00:00:00 2001 From: Ethen Pociask Date: Tue, 18 Jun 2024 04:53:57 -0400 Subject: [PATCH 1/8] feat: Batch hash verification --- .github/workflows/actions.yml | 1 + e2e/setup.go | 9 ++++ eigenda/config.go | 39 +++++++++++++- server/load_store.go | 11 +++- store/eigenda.go | 13 +++-- store/memory.go | 2 +- store/memory_test.go | 16 +++++- verify/cert.go | 72 ++++++++++++++++++++++++++ verify/hasher.go | 97 +++++++++++++++++++++++++++++++++++ verify/hasher_test.go | 59 +++++++++++++++++++++ verify/verifier.go | 61 ++++++++++++++++++++-- verify/verify_test.go | 13 +++-- 12 files changed, 373 insertions(+), 20 deletions(-) create mode 100644 verify/cert.go create mode 100644 verify/hasher.go create mode 100644 verify/hasher_test.go diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 5fa4d668..15a43e04 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -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 \ No newline at end of file diff --git a/e2e/setup.go b/e2e/setup.go index 7e234e67..173df40c 100644 --- a/e2e/setup.go +++ b/e2e/setup.go @@ -22,6 +22,7 @@ import ( const ( privateKey = "SIGNER_PRIVATE_KEY" + ethRPC = "ETHEREUM_RPC" transport = "http" svcName = "eigenda_proxy" host = "127.0.0.1" @@ -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, @@ -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? diff --git a/eigenda/config.go b/eigenda/config.go index 4a3e8de5..c7c2ff0e 100644 --- a/eigenda/config.go +++ b/eigenda/config.go @@ -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" @@ -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" @@ -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 @@ -73,14 +80,15 @@ 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, @@ -88,6 +96,21 @@ func (c *Config) KzgConfig() *kzg.KzgConfig { 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. @@ -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 } @@ -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"), + }, } } diff --git a/server/load_store.go b/server/load_store.go index 1ac7bc8c..0947822e 100644 --- a/server/load_store.go +++ b/server/load_store.go @@ -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 @@ -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 diff --git a/store/eigenda.go b/store/eigenda.go index 8b4071e7..dd96ce07 100644 --- a/store/eigenda.go +++ b/store/eigenda.go @@ -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 } @@ -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 } diff --git a/store/memory.go b/store/memory.go index 9d80edae..9535c83b 100644 --- a/store/memory.go +++ b/store/memory.go @@ -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 } diff --git a/store/memory_test.go b/store/memory_test.go index f3ce1783..3af5fb7f 100644 --- a/store/memory_test.go +++ b/store/memory_test.go @@ -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( @@ -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( diff --git a/verify/cert.go b/verify/cert.go new file mode 100644 index 00000000..37e40a1e --- /dev/null +++ b/verify/cert.go @@ -0,0 +1,72 @@ +package verify + +import ( + "fmt" + + proxy_common "github.com/Layr-Labs/eigenda-proxy/common" + binding "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" +) + +// CertVerifier verifies the DA certificate against on-chain EigenDA contracts +// to ensure disperser returned fields haven't been tampered with +type CertVerifier struct { + manager *binding.ContractEigenDAServiceManagerCaller +} + +func NewCertVerifier(cfg *Config, l log.Logger) (*CertVerifier, error) { + client, err := ethclient.Dial(cfg.RPCURL) + if err != nil { + return nil, fmt.Errorf("failed to dial ETH RPC node: %s", err.Error()) + } + + // construct binding + m, err := binding.NewContractEigenDAServiceManagerCaller(common.HexToAddress(cfg.SvcManagerAddr), client) + if err != nil { + return nil, err + } + + return &CertVerifier{ + manager: m, + }, nil +} + +func (cv *CertVerifier) VerifyBatch(header *binding.IEigenDAServiceManagerBatchHeader, + id uint32, recordHash [32]byte, blockNum uint32) error { + // 1 - Verify batch hash + + // 1.a - ensure that a batch hash can be looked up for a batch ID + expectedHash, err := cv.manager.BatchIdToBatchMetadataHash(nil, id) + if err != nil { + return err + } + + // 1.b - ensure that hash generated from local cert matches one stored on-chain + + actualHash, err := HashBatchMetadata(header, recordHash, blockNum) + + if err != nil { + return err + } + + equal := proxy_common.EqualSlices(expectedHash[:], actualHash[:]) + if !equal { + return fmt.Errorf("batch hash mismatch, expected: %x, got: %x", expectedHash, actualHash) + } + + return nil +} + +// 2 - (TODO) merkle proof verification + +func (cv *CertVerifier) VerifyMerkleProof(inclusionProof []byte, rootHash []byte, leafHash []byte, index uint64) error { + return nil +} + +// 3 - (TODO) verify blob security params +func (cv *CertVerifier) Verify(inclusionProof []byte, rootHash []byte, leafHash []byte, index uint64) error { + return nil +} diff --git a/verify/hasher.go b/verify/hasher.go new file mode 100644 index 00000000..840d2be4 --- /dev/null +++ b/verify/hasher.go @@ -0,0 +1,97 @@ +package verify + +import ( + "encoding/binary" + + binding "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" + "github.com/ethereum/go-ethereum/accounts/abi" + geth_common "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// HashBatchMetadata regenerates a batch data hash +// replicates: https://github.com/Layr-Labs/eigenda-utils/blob/c4cbc9ec078aeca3e4a04bd278e2fb136bf3e6de/src/libraries/EigenDAHasher.sol#L46-L54 +func HashBatchMetadata(bh *binding.IEigenDAServiceManagerBatchHeader, sigHash [32]byte, blockNum uint32) (geth_common.Hash, error) { + batchHeaderType, err := abi.NewType("tuple", "", []abi.ArgumentMarshaling{ + { + Name: "blobHeadersRoot", + Type: "bytes32", + }, + { + Name: "quorumNumbers", + Type: "bytes", + }, + { + Name: "signedStakeForQuorums", + Type: "bytes", + }, + { + Name: "referenceBlockNumber", + Type: "uint32", + }, + }) + + if err != nil { + return [32]byte{}, err + } + + arguments := abi.Arguments{ + { + Type: batchHeaderType, + }, + } + + s := struct { + BlobHeadersRoot [32]byte + QuorumNumbers []byte + SignedStakeForQuorums []byte + ReferenceBlockNumber uint32 + }{ + BlobHeadersRoot: bh.BlobHeadersRoot, + QuorumNumbers: bh.QuorumNumbers, + SignedStakeForQuorums: bh.SignedStakeForQuorums, + ReferenceBlockNumber: bh.ReferenceBlockNumber, + } + + bytes, err := arguments.Pack(s) + if err != nil { + return [32]byte{}, nil + } + + headerHash := crypto.Keccak256Hash(bytes) + return HashBatchHashedMetadata(headerHash, sigHash, blockNum) +} + +// HashBatchHashedMetadata hashes the given metadata into the commitment that will be stored in the contract +// replicates: https://github.com/Layr-Labs/eigenda-utils/blob/c4cbc9ec078aeca3e4a04bd278e2fb136bf3e6de/src/libraries/EigenDAHasher.sol#L19-L25 +func HashBatchHashedMetadata(batchHeaderHash [32]byte, signatoryRecordHash [32]byte, blockNumber uint32) (geth_common.Hash, error) { + + // since the solidity function uses abi.encodePacked, we need to consolidate the byte space that + // blockNum occupies to only 4 bytes versus 28 or 256 bits when encoded to abi buffer + a := make([]byte, 4) + binary.BigEndian.PutUint32(a, blockNumber) + + bytes32Type, err := abi.NewType("bytes32", "bytes32", nil) + if err != nil { + return geth_common.BytesToHash([]byte{}), err + } + + arguments := abi.Arguments{ + { + Type: bytes32Type, + }, + { + Type: bytes32Type, + }, + } + + bytes, err := arguments.Pack(batchHeaderHash, signatoryRecordHash) + if err != nil { + return [32]byte{}, err + } + + bytes = append(bytes, a...) + headerHash := crypto.Keccak256Hash(bytes) + + return headerHash, nil +} diff --git a/verify/hasher_test.go b/verify/hasher_test.go new file mode 100644 index 00000000..3c06936e --- /dev/null +++ b/verify/hasher_test.go @@ -0,0 +1,59 @@ +package verify + +import ( + "testing" + + binding "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" +) + +func TestHashBatchHashedMetadata(t *testing.T) { + batchHeaderHash := crypto.Keccak256Hash([]byte("batchHeader")) + sigRecordHash := crypto.Keccak256Hash([]byte("signatoryRecord")) + + // 1 - Test using uint32 MAX + var blockNum uint32 = 4294967295 + + expected := "0x687b60d8b30b6aaddf6413728fb66fb7a7554601c2cc8e17a37fa94ad0818500" + actual, err := HashBatchHashedMetadata(batchHeaderHash, sigRecordHash, blockNum) + require.NoError(t, err) + + require.Equal(t, expected, actual.String()) + + // 2 - Test using uint32 value + blockNum = 4294967294 + + expected = "0x94d77be4d3d180d32d61ec8037e687b71e7996feded39b72a6dc3f9ff6406b30" + actual, err = HashBatchHashedMetadata(batchHeaderHash, sigRecordHash, blockNum) + require.NoError(t, err) + + require.Equal(t, expected, actual.String()) + + // 3 - Testing using uint32 0 value + blockNum = 0 + + expected = "0x482dfb1545a792b6d118a045033143d0cc28b0e5a4b2e1924decf27e4fc8c250" + actual, err = HashBatchHashedMetadata(batchHeaderHash, sigRecordHash, blockNum) + require.NoError(t, err) + + require.Equal(t, expected, actual.String()) +} + +func TestHashBatchMetadata(t *testing.T) { + testHash := crypto.Keccak256Hash([]byte("batchHeader")) + + header := &binding.IEigenDAServiceManagerBatchHeader{ + BlobHeadersRoot: testHash, + QuorumNumbers: testHash.Bytes(), + SignedStakeForQuorums: testHash.Bytes(), + ReferenceBlockNumber: 1, + } + + expected := "0x746f8a453586621d12e41d097eab089b1f25beca44c434281d68d4be0484b7e8" + + actual, err := HashBatchMetadata(header, testHash, 1) + require.NoError(t, err) + require.Equal(t, actual.String(), expected) + +} diff --git a/verify/verifier.go b/verify/verifier.go index 298db2b4..86a60138 100644 --- a/verify/verifier.go +++ b/verify/verifier.go @@ -7,27 +7,78 @@ import ( "github.com/Layr-Labs/eigenda/encoding" "github.com/consensys/gnark-crypto/ecc/bn254" "github.com/consensys/gnark-crypto/ecc/bn254/fp" + "github.com/ethereum/go-ethereum/log" + + proxy_common "github.com/Layr-Labs/eigenda-proxy/common" + + binding "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" "github.com/Layr-Labs/eigenda/encoding/kzg" "github.com/Layr-Labs/eigenda/encoding/kzg/prover" "github.com/Layr-Labs/eigenda/encoding/rs" ) +type Config struct { + Verify bool + RPCURL string + SvcManagerAddr string + KzgConfig *kzg.KzgConfig +} + type Verifier struct { - prover *prover.Prover + verifyCert bool + prover *prover.Prover + cv *CertVerifier } -func NewVerifier(cfg *kzg.KzgConfig) (*Verifier, error) { - prover, err := prover.NewProver(cfg, false) // don't load G2 points +func NewVerifier(cfg *Config, l log.Logger) (*Verifier, error) { + var cv *CertVerifier + var err error + + if cfg.Verify { + cv, err = NewCertVerifier(cfg, l) + if err != nil { + return nil, err + } + } + + prover, err := prover.NewProver(cfg.KzgConfig, false) // don't load G2 points if err != nil { return nil, err } return &Verifier{ - prover: prover, + verifyCert: cfg.Verify, + prover: prover, + cv: cv, }, nil } +func (v *Verifier) VerifyCert(cert *proxy_common.Certificate) error { + if !v.verifyCert { + return nil + } + + // 1 - verify batch + + header := binding.IEigenDAServiceManagerBatchHeader{ + BlobHeadersRoot: [32]byte(cert.GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetBatchRoot()), + QuorumNumbers: cert.GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetQuorumNumbers(), + ReferenceBlockNumber: cert.GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetReferenceBlockNumber(), + SignedStakeForQuorums: cert.GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetQuorumSignedPercentages(), + } + + err := v.cv.VerifyBatch(&header, cert.BlobVerificationProof.BatchId, [32]byte(cert.BlobVerificationProof.BatchMetadata.SignatoryRecordHash), cert.BlobVerificationProof.BatchMetadata.GetConfirmationBlockNumber()) + if err != nil { + return err + } + + // 2 - TODO: verify merkle proof + + // 3 - TODO: verify security params + return nil +} + func (v *Verifier) Commit(blob []byte) (*bn254.G1Affine, error) { // ChunkLength and TotalChunks aren't relevant for computing data // commitment which is why they're currently set arbitrarily @@ -54,7 +105,7 @@ func (v *Verifier) Commit(blob []byte) (*bn254.G1Affine, error) { // Verify regenerates a commitment from the blob and asserts equivalence // to the commitment in the certificate // TODO: Optimize implementation by opening a point on the commitment instead -func (v *Verifier) Verify(expectedCommit *common.G1Commitment, blob []byte) error { +func (v *Verifier) VerifyCommitment(expectedCommit *common.G1Commitment, blob []byte) error { actualCommit, err := v.Commit(blob) if err != nil { return err diff --git a/verify/verify_test.go b/verify/verify_test.go index 19ec5c8b..0e7ccdb3 100644 --- a/verify/verify_test.go +++ b/verify/verify_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestVerification(t *testing.T) { +func TestCommitmentVerification(t *testing.T) { t.Parallel() var data = []byte("inter-subjective and not objective!") @@ -36,19 +36,24 @@ func TestVerification(t *testing.T) { NumWorker: uint64(runtime.GOMAXPROCS(0)), } - v, err := NewVerifier(kzgConfig) + cfg := &Config{ + Verify: false, + KzgConfig: kzgConfig, + } + + v, err := NewVerifier(cfg, nil) assert.NoError(t, err) // Happy path verification codec := codecs.NewIFFTCodec(codecs.NewDefaultBlobCodec()) blob, err := codec.EncodeBlob(data) assert.NoError(t, err) - err = v.Verify(c, blob) + err = v.VerifyCommitment(c, blob) assert.NoError(t, err) // failure with wrong data fakeData, err := codec.EncodeBlob([]byte("I am an imposter!!")) assert.NoError(t, err) - err = v.Verify(c, fakeData) + err = v.VerifyCommitment(c, fakeData) assert.Error(t, err) } From 2221b6bb834d2b0c25f537e9410710c622572945 Mon Sep 17 00:00:00 2001 From: Ethen Pociask Date: Tue, 18 Jun 2024 05:44:40 -0400 Subject: [PATCH 2/8] feat: Batch hash verification - update actions --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 15a43e04..2c26cdad 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -81,4 +81,4 @@ jobs: SIGNER_PRIVATE_KEY: ${{ secrets.SIGNER_PRIVATE_KEY }} ETHEREUM_RPC: ${{ secrets.ETHEREUM_RPC }} run: | - SIGNER_PRIVATE_KEY=$SIGNER_PRIVATE_KEY make holesky-test \ No newline at end of file + SIGNER_PRIVATE_KEY=$SIGNER_PRIVATE_KEY ETHEREUM_RPC=$ETHEREUM_RPC make holesky-test \ No newline at end of file From 81740a4ef9fa85d3d22adb0902850ebf1ffab5c7 Mon Sep 17 00:00:00 2001 From: Ethen Pociask Date: Tue, 18 Jun 2024 20:41:26 -0400 Subject: [PATCH 3/8] feat: DA Cert Verification --- common/common.go | 59 +++++++++++++++++++++++++++- srs_cache/dimE512.coset1 | Bin 0 -> 32769 bytes verify/cert.go | 20 ++++++++-- verify/hasher.go | 48 ++++++++++++++++++++++ verify/hasher_test.go | 76 +++++++++++++++++++++++++++++++++++ verify/merkle.go | 33 ++++++++++++++++ verify/merkle_test.go | 41 +++++++++++++++++++ verify/verifier.go | 83 +++++++++++++++++++++++++++++++++++---- 8 files changed, 348 insertions(+), 12 deletions(-) create mode 100644 srs_cache/dimE512.coset1 create mode 100644 verify/merkle.go create mode 100644 verify/merkle_test.go diff --git a/common/common.go b/common/common.go index df8cb067..a1e185d1 100644 --- a/common/common.go +++ b/common/common.go @@ -2,6 +2,7 @@ package common import ( "fmt" + "math/big" "strconv" "strings" @@ -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 diff --git a/srs_cache/dimE512.coset1 b/srs_cache/dimE512.coset1 new file mode 100644 index 0000000000000000000000000000000000000000..7fcccf6f9a9de1aeb875a9a542879dede0d76e45 GIT binary patch literal 32769 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

s}G8 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 YL2n5VT4gt+!*1%n<6Fz?!X>Y13d2WYs{jB1 literal 0 HcmV?d00001 diff --git a/verify/cert.go b/verify/cert.go index 37e40a1e..35314c36 100644 --- a/verify/cert.go +++ b/verify/cert.go @@ -60,13 +60,27 @@ func (cv *CertVerifier) VerifyBatch(header *binding.IEigenDAServiceManagerBatchH return nil } -// 2 - (TODO) merkle proof verification +// VerifyMerkleProof +func (cv *CertVerifier) VerifyMerkleProof(inclusionProof []byte, root []byte, blobIndex uint32, blobHeader proxy_common.BlobHeader) error { + leafHash, err := HashEncodeBlobHeader(blobHeader) + if err != nil { + return err + } + + generatedRoot, err := ProcessInclusionProof(inclusionProof, leafHash, uint64(blobIndex)) + if err != nil { + return err + } + + equal := proxy_common.EqualSlices(root, generatedRoot.Bytes()) + if !equal { + return fmt.Errorf("root hash mismatch, expected: %x, got: %x", root, generatedRoot) + } -func (cv *CertVerifier) VerifyMerkleProof(inclusionProof []byte, rootHash []byte, leafHash []byte, index uint64) error { return nil } // 3 - (TODO) verify blob security params -func (cv *CertVerifier) Verify(inclusionProof []byte, rootHash []byte, leafHash []byte, index uint64) error { +func (cv *CertVerifier) VerifyBlobParams(inclusionProof []byte, rootHash []byte, leafHash []byte, index uint64) error { return nil } diff --git a/verify/hasher.go b/verify/hasher.go index 840d2be4..ca2353c6 100644 --- a/verify/hasher.go +++ b/verify/hasher.go @@ -3,6 +3,7 @@ package verify import ( "encoding/binary" + common "github.com/Layr-Labs/eigenda-proxy/common" binding "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" "github.com/ethereum/go-ethereum/accounts/abi" geth_common "github.com/ethereum/go-ethereum/common" @@ -95,3 +96,50 @@ func HashBatchHashedMetadata(batchHeaderHash [32]byte, signatoryRecordHash [32]b return headerHash, nil } + +// HashBlobHeader function to hash BlobHeader +func HashBlobHeader(blobHeader common.BlobHeader) (geth_common.Hash, error) { + + blobHeaderType, err := abi.NewType("tuple", "", []abi.ArgumentMarshaling{ + {Name: "commitment", Type: "tuple", Components: []abi.ArgumentMarshaling{ + {Name: "X", Type: "uint256"}, + {Name: "Y", Type: "uint256"}, + }}, + {Name: "dataLength", Type: "uint32"}, + {Name: "quorumBlobParams", Type: "tuple[]", Components: []abi.ArgumentMarshaling{ + {Name: "quorumNumber", Type: "uint8"}, + {Name: "adversaryThresholdPercentage", Type: "uint8"}, + {Name: "confirmationThresholdPercentage", Type: "uint8"}, + {Name: "chunkLength", Type: "uint32"}, + }}, + }) + if err != nil { + return geth_common.Hash{}, err + } + + // Create ABI arguments + arguments := abi.Arguments{ + {Type: blobHeaderType}, + } + + // Pack the BlobHeader + bytes, err := arguments.Pack(blobHeader) + if err != nil { + return geth_common.Hash{}, err + } + // Hash the packed bytes using Keccak256 + hash := crypto.Keccak256Hash(bytes) + return hash, nil +} + +// Function to hash and encode header +func HashEncodeBlobHeader(header common.BlobHeader) (geth_common.Hash, error) { + // Hash the BlobHeader + blobHash, err := HashBlobHeader(header) + if err != nil { + return geth_common.Hash{}, err + } + + finalHash := crypto.Keccak256Hash(blobHash.Bytes()) + return finalHash, nil +} diff --git a/verify/hasher_test.go b/verify/hasher_test.go index 3c06936e..db4d09e6 100644 --- a/verify/hasher_test.go +++ b/verify/hasher_test.go @@ -1,8 +1,12 @@ package verify import ( + "math/big" "testing" + "github.com/Layr-Labs/eigenda-proxy/common" + eigenda_common "github.com/Layr-Labs/eigenda/api/grpc/common" + "github.com/Layr-Labs/eigenda/api/grpc/disperser" binding "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" @@ -57,3 +61,75 @@ func TestHashBatchMetadata(t *testing.T) { require.Equal(t, actual.String(), expected) } + +func TestHashBlobHeader(t *testing.T) { + expected := "0xba4675a31c9bf6b2f7abfdcedd34b74645cb7332b35db39bff00ae8516a67393" + + // [[1,1],2,[[2,4,5,6]]] + header := &disperser.BlobHeader{ + Commitment: &eigenda_common.G1Commitment{ + X: big.NewInt(1).Bytes(), + Y: big.NewInt(1).Bytes(), + }, + DataLength: 2, + BlobQuorumParams: []*disperser.BlobQuorumParam{ + { + QuorumNumber: 2, + AdversaryThresholdPercentage: 4, + ConfirmationThresholdPercentage: 5, + ChunkLength: 6, + }, + { + QuorumNumber: 2, + AdversaryThresholdPercentage: 4, + ConfirmationThresholdPercentage: 5, + ChunkLength: 6, + }, + }, + } + + cert := &common.Certificate{ + BlobHeader: header, + } + + actual, err := HashBlobHeader(cert.ReadBlobHeader()) + + require.NoError(t, err) + require.Equal(t, expected, actual.String()) +} + +func TestHashEncodeBlobHeader(t *testing.T) { + expected := "0xf15f43fa44bae9b74cd2f88f8f838e09ff7ab5d50f2170f07b98479eb7da98ba" + + // [[1,1],2,[[2,4,5,6]]] + header := &disperser.BlobHeader{ + Commitment: &eigenda_common.G1Commitment{ + X: big.NewInt(1).Bytes(), + Y: big.NewInt(1).Bytes(), + }, + DataLength: 2, + BlobQuorumParams: []*disperser.BlobQuorumParam{ + { + QuorumNumber: 2, + AdversaryThresholdPercentage: 4, + ConfirmationThresholdPercentage: 5, + ChunkLength: 6, + }, + { + QuorumNumber: 2, + AdversaryThresholdPercentage: 4, + ConfirmationThresholdPercentage: 5, + ChunkLength: 6, + }, + }, + } + + cert := &common.Certificate{ + BlobHeader: header, + } + + actual, err := HashEncodeBlobHeader(cert.ReadBlobHeader()) + + require.NoError(t, err) + require.Equal(t, expected, actual.String()) +} diff --git a/verify/merkle.go b/verify/merkle.go new file mode 100644 index 00000000..3d8bc9b7 --- /dev/null +++ b/verify/merkle.go @@ -0,0 +1,33 @@ +package verify + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// ProcessInclusionProof processes the Merkle proof +func ProcessInclusionProof(proof []byte, leaf common.Hash, index uint64) (common.Hash, error) { + if len(proof) == 0 || len(proof)%32 != 0 { + return common.Hash{}, errors.New("proof length should be a multiple of 32 bytes or 256 bits") + } + + computedHash := leaf + for i := 0; i < len(proof); i += 32 { + var proofElement common.Hash + copy(proofElement[:], proof[i:i+32]) + + var combined []byte + if index%2 == 0 { // right + combined = append(computedHash.Bytes(), proofElement.Bytes()...) + } else { // left + combined = append(proofElement.Bytes(), computedHash.Bytes()...) + } + + computedHash = crypto.Keccak256Hash(combined) + index = index / 2 + } + + return computedHash, nil +} diff --git a/verify/merkle_test.go b/verify/merkle_test.go new file mode 100644 index 00000000..2ac5e002 --- /dev/null +++ b/verify/merkle_test.go @@ -0,0 +1,41 @@ +package verify + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/require" +) + +func TestProcessInclusionProofPass(t *testing.T) { + proof, err := hexutil.Decode("0xc455c1ea0e725d7ea3e5f29e9f48be8fc2787bb0a914d5a86710ba302c166ac4f626d76f67f1055bb960a514fb8923af2078fd84085d712655b58a19612e8cd15c3e4ac1cef57acde3438dbcf63f47c9fefe1221344c4d5c1a4943dd0d1803091ca81a270909dc0e146841441c9bd0e08e69ce6168181a3e4060ffacf3627480bec6abdd8d7bb92b49d33f180c42f49e041752aaded9c403db3a17b85e48a11e9ea9a08763f7f383dab6d25236f1b77c12b4c49c5cdbcbea32554a604e3f1d2f466851cb43fe73617b3d01e665e4c019bf930f92dea7394c25ed6a1e200d051fb0c30a2193c459f1cfef00bf1ba6656510d16725a4d1dc031cb759dbc90bab427b0f60ddc6764681924dda848824605a4f08b7f526fe6bd4572458c94e83fbf2150f2eeb28d3011ec921996dc3e69efa52d5fcf3182b20b56b5857a926aa66605808079b4d52c0c0cfe06923fa92e65eeca2c3e6126108e8c1babf5ac522f4d7") + require.NoError(t, err) + + leaf := common.HexToHash("0xf6106e6ae4631e68abe0fa898cedbe97dbae6c7efb1b088c5aa2e8b91190ff96") + index := uint64(580) + + expectedRoot, err := hexutil.Decode("0x7390b8023db8248123dcaeca57fa6c9340bef639e204f2278fc7ec3d46ad071b") + require.NoError(t, err) + + actualRoot, err := ProcessInclusionProof(proof, leaf, index) + require.NoError(t, err) + + require.Equal(t, expectedRoot, actualRoot.Bytes()) +} + +func TestProcessInclusionProofFail(t *testing.T) { + proof, err := hexutil.Decode("0xc455c1ea0e725d7ea3e5f29e9f48be8fc2787bb0a914d5a86710ba302c166ac4f626d76f67f1055bb960a514fb8923af2078fd84085d712655b58a19612e8cd15c3e4ac1cef57acde3438dbcf63f47c9fefe1221344c4d5c1a4943dd0d1803091ca81a270909dc0e146841441c9bd0e08e69ce6168181a3e4060ffacf3627480bec6abdd8d7bb92b49d33f180c42f49e041752aaded9c403db3a17b85e48a11e9ea9a08763f7f383dab6d25236f1b77c12b4c49c5cdbcbea32554a604e3f1d2f466851cb43fe73617b3d01e665e4c019bf930f92dea7394c25ed6a1e200d051fb0c30a2193c459f1cfef00bf1ba6656510d16725a4d1dc031cb759dbc90bab427b0f60ddc6764681924dda848824605a4f08b7f526fe6bd4572458c94e83fbf2150f2eeb28d3011ec921996dc3e69efa52d5fcf3182b20b56b5857a926aa66605808079b4d52c0c0cfe06923fa92e65eeca2c3e6126108e8c1babf5ac522f4d7") + require.NoError(t, err) + + leaf := common.HexToHash("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") + index := uint64(580) + + expectedRoot, err := hexutil.Decode("0x7390b8023db8248123dcaeca57fa6c9340bef639e204f2278fc7ec3d46ad071b") + require.NoError(t, err) + + actualRoot, err := ProcessInclusionProof(proof, leaf, index) + require.NoError(t, err) + + require.NotEqual(t, expectedRoot, actualRoot.Bytes()) +} diff --git a/verify/verifier.go b/verify/verifier.go index 86a60138..9ae2681f 100644 --- a/verify/verifier.go +++ b/verify/verifier.go @@ -60,22 +60,30 @@ func (v *Verifier) VerifyCert(cert *proxy_common.Certificate) error { } // 1 - verify batch - header := binding.IEigenDAServiceManagerBatchHeader{ - BlobHeadersRoot: [32]byte(cert.GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetBatchRoot()), - QuorumNumbers: cert.GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetQuorumNumbers(), - ReferenceBlockNumber: cert.GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetReferenceBlockNumber(), - SignedStakeForQuorums: cert.GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetQuorumSignedPercentages(), + BlobHeadersRoot: [32]byte(cert.Proof().GetBatchMetadata().GetBatchHeader().GetBatchRoot()), + QuorumNumbers: cert.Proof().GetBatchMetadata().GetBatchHeader().GetQuorumNumbers(), + ReferenceBlockNumber: cert.Proof().GetBatchMetadata().GetBatchHeader().GetReferenceBlockNumber(), + SignedStakeForQuorums: cert.Proof().GetBatchMetadata().GetBatchHeader().GetQuorumSignedPercentages(), + } + + err := v.cv.VerifyBatch(&header, cert.Proof().GetBatchId(), [32]byte(cert.Proof().BatchMetadata.GetSignatoryRecordHash()), cert.Proof().BatchMetadata.GetConfirmationBlockNumber()) + if err != nil { + return err } - err := v.cv.VerifyBatch(&header, cert.BlobVerificationProof.BatchId, [32]byte(cert.BlobVerificationProof.BatchMetadata.SignatoryRecordHash), cert.BlobVerificationProof.BatchMetadata.GetConfirmationBlockNumber()) + // 2 - verify merkle proof + err = v.cv.VerifyMerkleProof(cert.Proof().GetInclusionProof(), cert.BatchHeaderRoot(), cert.Proof().GetBlobIndex(), cert.ReadBlobHeader()) if err != nil { return err } - // 2 - TODO: verify merkle proof + // 3 - verify security parameters + err = v.VerifySecurityParams(cert.ReadBlobHeader(), header) + if err != nil { + return err + } - // 3 - TODO: verify security params return nil } @@ -126,3 +134,62 @@ func (v *Verifier) VerifyCommitment(expectedCommit *common.G1Commitment, blob [] return nil } + +// VerifySecurityParams +func (v *Verifier) VerifySecurityParams(blobHeader proxy_common.BlobHeader, batchHeader binding.IEigenDAServiceManagerBatchHeader) error { + + confirmedQuorums := make(map[uint8]bool) + + // require that the security param in each blob is met + for i := 0; i < len(blobHeader.QuorumBlobParams); i++ { + if batchHeader.QuorumNumbers[i] != blobHeader.QuorumBlobParams[i].QuorumNumber { + return fmt.Errorf("quorum number mismatch, expected: %d, got: %d", batchHeader.QuorumNumbers[i], blobHeader.QuorumBlobParams[i].QuorumNumber) + } + + if blobHeader.QuorumBlobParams[i].AdversaryThresholdPercentage > blobHeader.QuorumBlobParams[i].ConfirmationThresholdPercentage { + return fmt.Errorf("adversary threshold percentage must be greater than or equal to confirmation threshold percentage") + } + + quorumAdversaryThreshold, err := v.getQuorumAdversaryThreshold(blobHeader.QuorumBlobParams[i].QuorumNumber) + if err != nil { + log.Warn("failed to get quorum adversary threshold", "err", err) + } + + if quorumAdversaryThreshold > 0 && blobHeader.QuorumBlobParams[i].AdversaryThresholdPercentage < quorumAdversaryThreshold { + return fmt.Errorf("adversary threshold percentage must be greater than or equal to quorum adversary threshold percentage") + } + + if batchHeader.SignedStakeForQuorums[i] < blobHeader.QuorumBlobParams[i].ConfirmationThresholdPercentage { + return fmt.Errorf("signed stake for quorum must be greater than or equal to confirmation threshold percentage") + } + + confirmedQuorums[blobHeader.QuorumBlobParams[i].QuorumNumber] = true + } + + requiredQuorums, err := v.cv.manager.QuorumNumbersRequired(nil) + if err != nil { + log.Warn("failed to get required quorum numbers", "err", err) + } + + // ensure that required quorums are present in the confirmed ones + for _, quorum := range requiredQuorums { + if !confirmedQuorums[quorum] { + return fmt.Errorf("quorum %d is required but not present in confirmed quorums", quorum) + } + } + + return nil +} + +func (v *Verifier) getQuorumAdversaryThreshold(quorumNum uint8) (uint8, error) { + percentages, err := v.cv.manager.QuorumAdversaryThresholdPercentages(nil) + if err != nil { + return 0, err + } + + if len(percentages) > int(quorumNum) { + return percentages[quorumNum], nil + } + + return 0, nil +} From 0cba7868a47a48de2aee7febe469e4c14e6f2584 Mon Sep 17 00:00:00 2001 From: Ethen Pociask Date: Tue, 18 Jun 2024 20:41:40 -0400 Subject: [PATCH 4/8] feat: DA Cert Verification - rm dead file --- srs_cache/dimE512.coset1 | Bin 32769 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 srs_cache/dimE512.coset1 diff --git a/srs_cache/dimE512.coset1 b/srs_cache/dimE512.coset1 deleted file mode 100644 index 7fcccf6f9a9de1aeb875a9a542879dede0d76e45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32769 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

s}G8 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 YL2n5VT4gt+!*1%n<6Fz?!X>Y13d2WYs{jB1 From 2752bae273c970ba4f11e81a40eccb8aab7dbfd2 Mon Sep 17 00:00:00 2001 From: Ethen Pociask Date: Tue, 18 Jun 2024 23:59:30 -0400 Subject: [PATCH 5/8] feat: DA Cert Verification - update env ingestion for holesky test --- e2e/setup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/setup.go b/e2e/setup.go index 173df40c..e381d65d 100644 --- a/e2e/setup.go +++ b/e2e/setup.go @@ -46,7 +46,7 @@ func CreateTestSuite(t *testing.T, useMemory bool) (TestSuite, func()) { // load node url from environment ethRPC := os.Getenv(ethRPC) - if ethRPC != "" && !useMemory { + if ethRPC == "" && !useMemory { t.Fatal("ETHEREUM_RPC environment variable is not set") } From 1eb4fd6d7b92b6b48fcd7e71f2245a359b3ec82b Mon Sep 17 00:00:00 2001 From: Ethen Pociask Date: Wed, 19 Jun 2024 00:18:29 -0400 Subject: [PATCH 6/8] feat: DA Cert Verification - update docs --- README.md | 15 +++++++++++++++ cmd/server/main.go | 7 +++---- verify/verifier.go | 6 ++++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4a4b73aa..260c4f09 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,18 @@ 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). +* `--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: @@ -35,6 +47,9 @@ An ephemeral memory store backend can be used for faster feedback testing when p * `--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` diff --git a/cmd/server/main.go b/cmd/server/main.go index 7bfb7a34..c24b00b6 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -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" diff --git a/verify/verifier.go b/verify/verifier.go index 9ae2681f..651d5eb7 100644 --- a/verify/verifier.go +++ b/verify/verifier.go @@ -72,7 +72,7 @@ func (v *Verifier) VerifyCert(cert *proxy_common.Certificate) error { return err } - // 2 - verify merkle proof + // 2 - verify merkle inclusion proof err = v.cv.VerifyMerkleProof(cert.Proof().GetInclusionProof(), cert.BatchHeaderRoot(), cert.Proof().GetBlobIndex(), cert.ReadBlobHeader()) if err != nil { return err @@ -135,7 +135,7 @@ func (v *Verifier) VerifyCommitment(expectedCommit *common.G1Commitment, blob [] return nil } -// VerifySecurityParams +// VerifySecurityParams ensures that returned security parameters are valid func (v *Verifier) VerifySecurityParams(blobHeader proxy_common.BlobHeader, batchHeader binding.IEigenDAServiceManagerBatchHeader) error { confirmedQuorums := make(map[uint8]bool) @@ -181,6 +181,8 @@ func (v *Verifier) VerifySecurityParams(blobHeader proxy_common.BlobHeader, batc return nil } +// getQuorumAdversaryThreshold reads the adversarial threshold percentage for a given quorum number +// returns 0 if DNE func (v *Verifier) getQuorumAdversaryThreshold(quorumNum uint8) (uint8, error) { percentages, err := v.cv.manager.QuorumAdversaryThresholdPercentages(nil) if err != nil { From 0db1735e0007915113f7d62172d76e8dba59385f Mon Sep 17 00:00:00 2001 From: Ethen Pociask Date: Wed, 19 Jun 2024 18:09:01 -0400 Subject: [PATCH 7/8] feat: DA Cert Verification - fix e2e test bug --- e2e/optimism_test.go | 6 ------ e2e/server_test.go | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/e2e/optimism_test.go b/e2e/optimism_test.go index 6ac74619..155d1a07 100644 --- a/e2e/optimism_test.go +++ b/e2e/optimism_test.go @@ -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 diff --git a/e2e/server_test.go b/e2e/server_test.go index 2bf43445..6001674a 100644 --- a/e2e/server_test.go +++ b/e2e/server_test.go @@ -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("afk is onto something!") t.Log("Setting input data on proxy server...") commit, err := daClient.SetInput(ts.Ctx, testPreimage) From 50e45eaba9ec4cd6e8724a8b3ac98ff924c6a6e0 Mon Sep 17 00:00:00 2001 From: Ethen Pociask Date: Wed, 19 Jun 2024 18:09:25 -0400 Subject: [PATCH 8/8] feat: DA Cert Verification - fix e2e test bug --- e2e/server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/server_test.go b/e2e/server_test.go index 6001674a..6d6e9e3c 100644 --- a/e2e/server_test.go +++ b/e2e/server_test.go @@ -44,7 +44,7 @@ func TestPlasmaClient(t *testing.T) { // 1 - write arbitrary data to EigenDA - var testPreimage = []byte("afk is onto something!") + var testPreimage = []byte("feel the rain on your skin!") t.Log("Setting input data on proxy server...") commit, err := daClient.SetInput(ts.Ctx, testPreimage)