-
Notifications
You must be signed in to change notification settings - Fork 29
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: Batch hash verification #39
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Never understood why people abbreviate 'config' to remove 3 letters. Feel free to ignore. |
||
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. | ||
|
@@ -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.", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thoughts on putting an example RPC url in the description and a default value for a public Holesky RPC? |
||
EnvVars: prefixEnvVars("ETH_RPC"), | ||
}, | ||
&cli.StringFlag{ | ||
Name: SvcManagerAddrFlagName, | ||
Usage: "Deployed EigenDA service manager address.", | ||
EnvVars: prefixEnvVars("SERVICE_MANAGER_ADDR"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A default value for the Holesky address might be nice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm realizing it might be good to check into the repo certain "configuration profiles" representing good defaults for testnet and mainnet. Maybe networks/mainnet.env and networks/holesky.env. To make it the invocation of ./bin/eigenda-proxy solid state (a one liner) we would need to add a special command line flag |
||
}, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: 'EigenDA' in any user-facing outputs |
||
client, err := clients.NewEigenDAClient(log, daCfg.ClientConfig) | ||
if err != nil { | ||
return nil, err | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Observation, not urgent: The fact that there's a decent amount of code duplication between the EigenDA store and memory store suggests to me that we could do some refactoring. |
||
if err != nil { | ||
return nil, err | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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[:]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: "golang.org/x/exp/slices" gives you slices.Equal(). Again, can ignore. https://chatgpt.com/share/79d6326a-d0e3-4192-829b-ee3064dccbc9 |
||
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should update the readme with a table of these addresses across Holesky and Mainnet, or even better link to the canonical EigenDA contract readme where those addresses are written down.