diff --git a/go.mod b/go.mod index ca3ce306a..e0aaa787b 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/Layr-Labs/eigenda/api v0.6.1 github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 + github.com/consensys/gnark-crypto v0.10.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 github.com/docker/docker v20.10.24+incompatible github.com/docker/go-connections v0.4.0 @@ -75,7 +76,6 @@ require ( github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect github.com/cockroachdb/redact v1.1.3 // indirect github.com/consensys/bavard v0.1.13 // indirect - github.com/consensys/gnark-crypto v0.10.0 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect diff --git a/op-batcher/Dockerfile b/op-batcher/Dockerfile index 96ed2e1dc..1c34a2d46 100644 --- a/op-batcher/Dockerfile +++ b/op-batcher/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.19.9-alpine3.16 as builder +FROM --platform=$BUILDPLATFORM golang:1.19.9-alpine3.16 AS builder ARG VERSION=v0.0.0 @@ -13,7 +13,6 @@ COPY ./op-signer /app/op-signer COPY ./datalayr /app/datalayr COPY ./go.mod /app/go.mod COPY ./go.sum /app/go.sum -COPY ./.git /app/.git COPY ./bss-core /app/bss-core COPY ./op-batcher/docker.go.work /go/go.work @@ -31,5 +30,7 @@ RUN make op-batcher VERSION="$VERSION" GOOS=$TARGETOS GOARCH=$TARGETARCH FROM alpine:3.16 COPY --from=builder /app/op-batcher/bin/op-batcher /usr/local/bin +COPY --from=builder /app/op-batcher/resources /resources + ENTRYPOINT ["op-batcher"] diff --git a/op-batcher/batcher/config.go b/op-batcher/batcher/config.go index 13d5e7886..8dbc0d511 100644 --- a/op-batcher/batcher/config.go +++ b/op-batcher/batcher/config.go @@ -69,6 +69,8 @@ type Config struct { EigenDA eigenda.Config // Upgrade Da from MantleDA to EigenDA DaUpgradeChainConfig *upgrade.UpgradeChainConfig + //skip eigenda and submit to ethereum blob transaction + SkipEigenDaRpc bool } // Check ensures that the [Config] is valid. @@ -93,7 +95,6 @@ func (c *Config) Check() error { return ErrGraphPollingDurationZero } } - return nil } @@ -165,6 +166,7 @@ type CLIConfig struct { EigenLogConfig logging.Config EigenDAConfig eigenda.CLIConfig + SkipEigenDaRpc bool } func (c CLIConfig) Check() error { @@ -211,6 +213,7 @@ func NewConfig(ctx *cli.Context) CLIConfig { RollupMaxSize: ctx.GlobalUint64(flags.RollUpMaxSizeFlag.Name), MantleDaNodes: ctx.GlobalInt(flags.MantleDaNodeFlag.Name), Stopped: ctx.GlobalBool(flags.StoppedFlag.Name), + SkipEigenDaRpc: ctx.GlobalBool(flags.SkipEigenDaRpcFlag.Name), TxMgrConfig: txmgr.ReadCLIConfig(ctx), RPCConfig: rpc.ReadCLIConfig(ctx), LogConfig: oplog.ReadCLIConfig(ctx), diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index 3627b8c90..c0e99cbfd 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -98,6 +98,7 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metri GraphPollingDuration: cfg.GraphPollingDuration, RollupMaxSize: cfg.RollupMaxSize, MantleDaNodes: cfg.MantleDaNodes, + SkipEigenDaRpc: cfg.SkipEigenDaRpc, Rollup: rcfg, Channel: ChannelConfig{ SeqWindowSize: rcfg.SeqWindowSize, @@ -111,6 +112,19 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger, m metrics.Metri RPC: cfg.EigenDAConfig.RPC, StatusQueryTimeout: cfg.EigenDAConfig.StatusQueryTimeout, StatusQueryRetryInterval: cfg.EigenDAConfig.StatusQueryRetryInterval, + RPCTimeout: cfg.EigenDAConfig.DARPCTimeout, + EnableHsm: cfg.EigenDAConfig.EnableHsm, + HsmCreden: cfg.EigenDAConfig.HsmCreden, + HsmPubkey: cfg.EigenDAConfig.HsmPubkey, + HsmAPIName: cfg.EigenDAConfig.HsmAPIName, + PrivateKey: cfg.EigenDAConfig.PrivateKey, + EthRPC: cfg.EigenDAConfig.EthRPC, + SvcManagerAddr: cfg.EigenDAConfig.SvcManagerAddr, + EthConfirmationDepth: cfg.EigenDAConfig.EthConfirmationDepth, + CacheDir: cfg.EigenDAConfig.CacheDir, + G1Path: cfg.EigenDAConfig.G1Path, + MaxBlobLength: cfg.EigenDAConfig.MaxBlobLength, + G2PowerOfTauPath: cfg.EigenDAConfig.G2PowerOfTauPath, }, } @@ -170,14 +184,16 @@ func NewBatchSubmitter(ctx context.Context, cfg Config, l log.Logger, m metrics. cfg.metr = m + eigenDA, err := eigenda.NewEigenDAClient(cfg.EigenDA, cfg.log, m) + if err != nil { + return nil, fmt.Errorf("error creating EigenDA client: %w", err) + } + return &BatchSubmitter{ - Config: cfg, - txMgr: cfg.TxManager, - state: NewChannelManager(l, m, cfg.Channel, cfg.DaUpgradeChainConfig), - eigenDA: &eigenda.EigenDA{ - Config: cfg.EigenDA, - Log: l, - }, + Config: cfg, + txMgr: cfg.TxManager, + state: NewChannelManager(l, m, cfg.Channel, cfg.DaUpgradeChainConfig), + eigenDA: eigenDA, }, nil } diff --git a/op-batcher/batcher/driver_da.go b/op-batcher/batcher/driver_da.go index 196ef5597..7ff76986d 100644 --- a/op-batcher/batcher/driver_da.go +++ b/op-batcher/batcher/driver_da.go @@ -3,6 +3,7 @@ package batcher import ( "context" "errors" + "fmt" "io" "math/big" "sort" @@ -35,6 +36,7 @@ const ( DaLoopRetryNum = 10 EigenRPCRetryNum = 3 BytesPerCoefficient = 31 + MaxblobNum = 3 // max number of blobs, the bigger the more possible of timeout ) var ErrInitDataStore = errors.New("init data store transaction failed") @@ -211,12 +213,9 @@ func (l *BatchSubmitter) loopEigenDa() (bool, error) { } var err error - var candidate *txmgr.TxCandidate - var receipt *types.Receipt - var data, wrappedData []byte - eigendaSuccess := false + var wrappedData []byte - data, err = l.txAggregatorForEigenDa() + daData, err := l.txAggregatorForEigenDa() if err != nil { l.log.Error("loopEigenDa txAggregatorForEigenDa err", "err", err) return false, err @@ -228,60 +227,76 @@ func (l *BatchSubmitter) loopEigenDa() (bool, error) { return false, err } - for loopRetry := 0; loopRetry < DaLoopRetryNum; loopRetry++ { - err = func() error { - l.metr.RecordRollupRetry(int32(loopRetry)) - if !eigendaSuccess { - //try 3 times - for retry := 0; retry < EigenRPCRetryNum; retry++ { - l.metr.RecordDaRetry(int32(retry)) - wrappedData, err = l.disperseEigenDaData(data) - if err != nil { - l.log.Warn("loopEigenDa disperseEigenDaData err,need to try again", "retry time", retry, "err", err) - time.Sleep(5 * time.Second) - continue - } - - eigendaSuccess = true - break - } + l.log.Info("disperseEigenDaData", "skip da rpc", l.Config.SkipEigenDaRpc) + + eigendaSuccess := false + if !l.Config.SkipEigenDaRpc { + timeoutTime := time.Now().Add(l.EigenDA.StatusQueryTimeout) + for retry := 0; retry < EigenRPCRetryNum; retry++ { + l.metr.RecordDaRetry(int32(retry)) + wrappedData, err = l.disperseEigenDaData(daData) + if err == nil && len(wrappedData) > 0 { + eigendaSuccess = true + break } - if eigendaSuccess { - candidate = l.calldataTxCandidate(wrappedData) - } else { - if candidate, err = l.blobTxCandidate(data); err != nil { - l.log.Warn("failed to create blob tx candidate", "err", err) - return err - } - l.metr.RecordEigenDAFailback() + if time.Now().After(timeoutTime) { + l.log.Warn("loopEigenDa disperseEigenDaData timeout", "retry time", retry, "err", err) + break } - receipt, err = l.txMgr.Send(l.killCtx, *candidate) - if err != nil { + l.log.Warn("loopEigenDa disperseEigenDaData err,need to try again", "retry time", retry, "err", err) + time.Sleep(5 * time.Second) + } + } + + var candidates []*txmgr.TxCandidate + if eigendaSuccess { + candidate := l.calldataTxCandidate(wrappedData) + candidates = append(candidates, candidate) + } else { + if blobCandidates, err := l.blobTxCandidates(daData); err != nil { + l.log.Warn("failed to create blob tx candidate", "err", err) + return false, err + } else { + candidates = append(candidates, blobCandidates...) + l.metr.RecordEigenDAFailback(len(blobCandidates)) + } + } + + var lastReceipt *types.Receipt + var successTxs []ecommon.Hash + for loopRetry := 0; loopRetry < DaLoopRetryNum; loopRetry++ { + l.metr.RecordRollupRetry(int32(loopRetry)) + failedIdx := 0 + for idx, tx := range candidates { + lastReceipt, err = l.txMgr.Send(l.killCtx, *tx) + if err != nil || lastReceipt.Status == types.ReceiptStatusFailed { l.log.Warn("failed to send tx candidate", "err", err) - return err + break } + successTxs = append(successTxs, lastReceipt.TxHash) + failedIdx = idx + 1 + } + candidates = candidates[failedIdx:] - return nil - }() - if err != nil { + if len(candidates) > 0 { l.log.Warn("failed to rollup", "err", err, "retry time", loopRetry) } else { + l.log.Info("rollup success", "success txs", successTxs) break } } - if err != nil { + if len(candidates) > 0 { + err = fmt.Errorf("failed to rollup %d tx candidates", len(candidates)) l.log.Error("failed to rollup", "err", err) + l.metr.RecordBatchTxConfirmDataFailed() return false, err } - err = l.handleConfirmDataStoreReceipt(receipt) - if err != nil { - l.log.Error("failed to handleConfirmDataStoreReceipt", "err", err) - return false, err - } + l.metr.RecordBatchTxConfirmDataSuccess() + l.recordConfirmedEigenDATx(lastReceipt) //create a new channel now for reducing the disperseEigenDaData latency time if err = l.state.ensurePendingChannel(currentL1.ID()); err != nil { @@ -309,26 +324,74 @@ func minInt(a, b int) int { return b } -func (l *BatchSubmitter) blobTxCandidate(data []byte) (*txmgr.TxCandidate, error) { +func (l *BatchSubmitter) blobTxCandidates(data [][]byte) ([]*txmgr.TxCandidate, error) { l.log.Info("building Blob transaction candidate", "size", len(data)) - blobs := []*se.Blob{} - for idx := 0; idx < len(data); idx += se.MaxBlobDataSize { - blobData := data[idx : idx+minInt(len(data)-idx, se.MaxBlobDataSize)] - var blob se.Blob - if err := blob.FromData(blobData); err != nil { + candidates := []*txmgr.TxCandidate{} + dataInTx := [][]byte{} + encodeData := []byte{} + + for _, frameData := range data { + dataInTx = append(dataInTx, frameData) + nextEncodeData, err := rlp.EncodeToBytes(dataInTx) + if err != nil { + l.log.Error("op-batcher unable to encode txn", "err", err) return nil, err } - blobs = append(blobs, &blob) + + if len(nextEncodeData) > se.MaxBlobDataSize*MaxblobNum { + blobs := []*se.Blob{} + for idx := 0; idx < len(encodeData); idx += se.MaxBlobDataSize { + blobData := encodeData[idx : idx+minInt(len(encodeData)-idx, se.MaxBlobDataSize)] + var blob se.Blob + if err := blob.FromData(blobData); err != nil { + return nil, err + } + blobs = append(blobs, &blob) + } + candidates = append(candidates, &txmgr.TxCandidate{ + To: &l.Rollup.BatchInboxAddress, + Blobs: blobs, + }) + dataInTx = [][]byte{frameData} + encodeData, err = rlp.EncodeToBytes(dataInTx) + if err != nil { + l.log.Error("op-batcher unable to encode txn", "err", err) + return nil, err + } + } else { + dataInTx = append(dataInTx, frameData) + encodeData = nextEncodeData + } + } - return &txmgr.TxCandidate{ - To: &l.Rollup.BatchInboxAddress, - Blobs: blobs, - }, nil + if len(dataInTx) > 0 { + blobs := []*se.Blob{} + for idx := 0; idx < len(encodeData); idx += se.MaxBlobDataSize { + blobData := encodeData[idx : idx+minInt(len(encodeData)-idx, se.MaxBlobDataSize)] + var blob se.Blob + if err := blob.FromData(blobData); err != nil { + return nil, err + } + blobs = append(blobs, &blob) + } + candidates = append(candidates, &txmgr.TxCandidate{ + To: &l.Rollup.BatchInboxAddress, + Blobs: blobs, + }) + } + + return candidates, nil } -func (l *BatchSubmitter) disperseEigenDaData(data []byte) ([]byte, error) { - blobInfo, requestId, err := l.eigenDA.DisperseBlob(l.shutdownCtx, data) +func (l *BatchSubmitter) disperseEigenDaData(data [][]byte) ([]byte, error) { + encodeData, err := rlp.EncodeToBytes(data) + if err != nil { + l.log.Error("op-batcher unable to encode txn", "err", err) + return nil, err + } + + blobInfo, requestId, err := l.eigenDA.DisperseBlob(l.shutdownCtx, encodeData) if err != nil { l.log.Error("Unable to publish batch frameset to EigenDA", "err", err) return nil, err @@ -346,7 +409,7 @@ func (l *BatchSubmitter) disperseEigenDaData(data []byte) ([]byte, error) { BlobIndex: blobInfo.BlobVerificationProof.BlobIndex, ReferenceBlockNumber: blobInfo.BlobVerificationProof.BatchMetadata.BatchHeader.ReferenceBlockNumber, QuorumIds: quorumIDs, - BlobLength: uint32(len(data)), + BlobLength: uint32(len(encodeData)), RequestId: requestId, }, }, @@ -422,7 +485,7 @@ func (l *BatchSubmitter) txAggregator() ([]byte, error) { return transactionByte, nil } -func (l *BatchSubmitter) txAggregatorForEigenDa() ([]byte, error) { +func (l *BatchSubmitter) txAggregatorForEigenDa() ([][]byte, error) { var txsData [][]byte var transactionByte []byte sortTxIds := make([]txID, 0, len(l.state.daPendingTxData)) @@ -450,7 +513,13 @@ func (l *BatchSubmitter) txAggregatorForEigenDa() ([]byte, error) { l.state.daUnConfirmedTxID = append(l.state.daUnConfirmedTxID, v) l.log.Info("added frame to daUnConfirmedTxID", "id", v.String()) } - return transactionByte, nil + + if len(txsData) == 0 { + l.log.Error("txsData is empty") + return nil, fmt.Errorf("txsData is empty") + } + + return txsData, nil } func (l *BatchSubmitter) disperseStoreData(txsData []byte) error { diff --git a/op-batcher/batcher/driver_da_test.go b/op-batcher/batcher/driver_da_test.go index 37b1b6b0a..8845aac86 100644 --- a/op-batcher/batcher/driver_da_test.go +++ b/op-batcher/batcher/driver_da_test.go @@ -5,12 +5,14 @@ import ( "context" "encoding/base64" "encoding/binary" + "encoding/hex" "encoding/json" "errors" "fmt" "io" "math/big" "testing" + "time" "github.com/Layr-Labs/datalayr/common/graphView" "github.com/Layr-Labs/datalayr/common/logging" @@ -18,11 +20,13 @@ import ( "github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/testlog" + "github.com/ethereum-optimism/optimism/op-service/eigenda" "github.com/ethereum-optimism/optimism/op-service/proto/gen/op_service/v1" "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -304,7 +308,7 @@ func TestBatchSubmitter_Send(t *testing.T) { var candidate *txmgr.TxCandidate var err error da := [358834]byte{} - if candidate, err = l.blobTxCandidate(da[:]); err != nil { + if l.blobTxCandidates([][]byte{da[:]}); err != nil { l.log.Error("failed to create blob tx candidate", "err", err) return } @@ -661,8 +665,50 @@ func TestCallEigenDA2(t *testing.T) { } func TestDecode(t *testing.T) { - _, _ = base64.StdEncoding.DecodeString("fItdiqlPZyEjwyqJvLwlvuzkjYQ7qcnjI6DXooHht5U=") - //fmt.Println(hexutil.Encode(data)) + data, _ := base64.StdEncoding.DecodeString("MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEQVp/Zdt0r7Clidd6dG0wQj7SGD4738oXZHrnygN6XoI1eD2gAUa6/tisBUMKwl/ysrmJf1CAjCdWO0Kst3YFcw===") + fmt.Println(len(data[24:])) + fmt.Println(hex.EncodeToString(data[23:])) + fmt.Println(hex.EncodeToString(crypto.Keccak256(data[24:])[12:])) //data, _ = base64.StdEncoding.DecodeString("Yzg2NjQ4ZmM3YjY2ZWI5Mjk0NDk4ODhlNDY0YjE0Y2Q0NTEzOTMxM2Q5MjRlOGYxMThkNWMwZmVmYzQxMWZiOS0zMTM3MzEzNzM1MzczODM3MzgzMDMwMzgzNzM2MzEzMjM3MzgzMjJmMzAyZjMzMzMyZjMxMmYzMzMzMmZlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1") - //fmt.Println(string(data)) + fmt.Println(string(data)) +} + +func TestCrypto(t *testing.T) { + privateKeyHex := "0xff4476671982ec7fea451f53cb8dbcc64bdd7851087077a6de73b0cb8f757124" //0xFfC192B454c330e68D20f63C58a50c56290b07d7 + privateKeyBytes := common.FromHex(privateKeyHex) + privateKey, err := crypto.ToECDSA(privateKeyBytes) + fmt.Println(privateKey, err) + pubkey := crypto.FromECDSAPub(&privateKey.PublicKey) + account := crypto.Keccak256(pubkey[1:])[12:] + fmt.Println("ecdsa pub", len(pubkey), hexutil.Encode(account)) + rawpubkey, err := base64.StdEncoding.DecodeString("MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEQVp/Zdt0r7Clidd6dG0wQj7SGD4738oXZHrnygN6XoI1eD2gAUa6/tisBUMKwl/ysrmJf1CAjCdWO0Kst3YFcw==") + pk, _ := crypto.UnmarshalPubkey(rawpubkey[23:]) + pubkey = crypto.FromECDSAPub(pk) + account = crypto.Keccak256(pubkey[1:])[12:] + fmt.Println("ecdsa pub", err, len(pubkey), hexutil.Encode(account), crypto.PubkeyToAddress(*pk)) +} + +func TestDisperseBlobAuthenticated(t *testing.T) { + + hsmCreden := `{}` + + hsmCreden = hex.EncodeToString([]byte(hsmCreden)) + //signer, _ := eigenda.NewLocalBlobSigner("0xff4476671982ec7fea451f53cb8dbcc64bdd7851087077a6de73b0cb8f757124") + //signer, _ := eigenda.NewHsmBlobSigner(hsmCreden, "projects/mantle-381302/locations/global/keyRings/qa/cryptoKeys/proposer-qa/cryptoKeyVersions/1", "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEQVp/Zdt0r7Clidd6dG0wQj7SGD4738oXZHrnygN6XoI1eD2gAUa6/tisBUMKwl/ysrmJf1CAjCdWO0Kst3YFcw==") + //fmt.Println("pubkey", signer.GetAccountID()) + da, _ := eigenda.NewEigenDAClient( + eigenda.Config{ + RPC: "disperser-holesky.eigenda.xyz:443", + StatusQueryTimeout: time.Minute * 10, + StatusQueryRetryInterval: time.Second * 5, + RPCTimeout: time.Minute, + }, log.New(context.Background()), + ) + + data, _, err := da.DisperseBlobAuthenticated(context.Background(), common.Hex2Bytes("D4A7E1Bd8015057293f0D0A557088c286942e84b")) + if err != nil { + t.Errorf("RetrieveBlob err:%v", err) + return + } + fmt.Printf("RetrieveBlob %d:%d\n", data.BlobVerificationProof.BatchId, data.BlobVerificationProof.BlobIndex) } diff --git a/op-batcher/flags/flags.go b/op-batcher/flags/flags.go index 3b873edb6..affa705f9 100644 --- a/op-batcher/flags/flags.go +++ b/op-batcher/flags/flags.go @@ -69,6 +69,7 @@ var ( Value: 31600, // ktz for order is 3000 EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "ROLLUP_MAX_SIZE"), } + MantleDaNodeFlag = cli.IntFlag{ Name: "mantle-da-node", Usage: "The number of nodes in MantleDA", @@ -112,6 +113,11 @@ var ( Usage: "Initialize the batcher in a stopped state. The batcher can be started using the admin_startBatcher RPC", EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "STOPPED"), } + SkipEigenDaRpcFlag = cli.BoolFlag{ + Name: "skip-eigenda-da-rpc", + Usage: "skip eigenDA rpc and submit da data to ethereum blob transaction when mantle_da_switch is open", + EnvVar: opservice.PrefixEnvVar(EnvVarPrefix, "SKIP_EIGENDA_DA_RPC"), + } // Legacy Flags SequencerHDPathFlag = txmgr.SequencerHDPathFlag ) @@ -129,6 +135,7 @@ var optionalFlags = []cli.Flag{ MaxChannelDurationFlag, MaxL1TxSizeBytesFlag, StoppedFlag, + SkipEigenDaRpcFlag, SequencerHDPathFlag, DisperserTimeoutFlag, DisperserSocketFlag, diff --git a/op-batcher/metrics/metrics.go b/op-batcher/metrics/metrics.go index 73241f18f..98e4c237f 100644 --- a/op-batcher/metrics/metrics.go +++ b/op-batcher/metrics/metrics.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-service/eigenda" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" txmetrics "github.com/ethereum-optimism/optimism/op-service/txmgr/metrics" ) @@ -26,6 +27,7 @@ type Metricer interface { // Record Tx metrics txmetrics.TxMetricer + eigenda.Metrics RecordLatestL1Block(l1ref eth.L1BlockRef) RecordL2BlocksLoaded(l2ref eth.L2BlockRef) @@ -62,7 +64,9 @@ type Metricer interface { Document() []opmetrics.DocumentedMetric - RecordEigenDAFailback() + RecordEigenDAFailback(txs int) + + RecordInterval(method string) func(error) } type Metrics struct { @@ -103,6 +107,11 @@ type Metrics struct { batcherTxEvs opmetrics.EventVec batcherTxOverMaxLimitEvent opmetrics.Event eigenDAFailbackCount prometheus.Counter + + eigendaRpcSubmit *prometheus.CounterVec + eigendaRpcSuccess *prometheus.CounterVec + eigendaRpcFailed *prometheus.CounterVec + eigendaRpcDuration *prometheus.SummaryVec } var _ Metricer = (*Metrics)(nil) @@ -239,6 +248,26 @@ func NewMetrics(procName string) *Metrics { Name: "eigen_da_failback_count", Help: "Number of times eigen da failback.", }), + eigendaRpcSubmit: factory.NewCounterVec(prometheus.CounterOpts{ + Namespace: ns, + Name: "eigenda_rpc_submit", + Help: "Number of eigenda rpc submit", + }, []string{"method"}), + eigendaRpcSuccess: factory.NewCounterVec(prometheus.CounterOpts{ + Namespace: ns, + Name: "eigenda_rpc_success", + Help: "Number of eigenda rpc success", + }, []string{"method"}), + eigendaRpcFailed: factory.NewCounterVec(prometheus.CounterOpts{ + Namespace: ns, + Name: "eigenda_rpc_failed", + Help: "Number of eigenda rpc failed", + }, []string{"method"}), + eigendaRpcDuration: factory.NewSummaryVec(prometheus.SummaryOpts{ + Namespace: ns, + Name: "eigenda_rpc_duration", + Help: "Eigenda rpc duration", + }, []string{"method"}), } } @@ -416,8 +445,21 @@ func (m *Metrics) RecordTxOverMaxLimit() { m.batcherTxOverMaxLimitEvent.Record() } -func (m *Metrics) RecordEigenDAFailback() { - m.eigenDAFailbackCount.Add(1) +func (m *Metrics) RecordEigenDAFailback(txs int) { + m.eigenDAFailbackCount.Add(float64(txs)) +} + +func (m *Metrics) RecordInterval(method string) func(error) { + m.eigendaRpcSubmit.WithLabelValues(method).Inc() + timer := prometheus.NewTimer(m.eigendaRpcDuration.WithLabelValues(method)) + return func(err error) { + timer.ObserveDuration() + if err != nil { + m.eigendaRpcFailed.WithLabelValues(method).Inc() + } else { + m.eigendaRpcSuccess.WithLabelValues(method).Inc() + } + } } // estimateBatchSize estimates the size of the batch diff --git a/op-batcher/metrics/noop.go b/op-batcher/metrics/noop.go index 346edb692..6aa97dc60 100644 --- a/op-batcher/metrics/noop.go +++ b/op-batcher/metrics/noop.go @@ -56,4 +56,8 @@ func (*noopMetrics) RecordDaNonSignerPubkeys(num int) { } -func (*noopMetrics) RecordEigenDAFailback() {} +func (*noopMetrics) RecordEigenDAFailback(txs int) {} + +func (*noopMetrics) RecordInterval(method string) func(error) { + return func(error) {} +} diff --git a/op-batcher/resources/g1.point b/op-batcher/resources/g1.point new file mode 100644 index 000000000..483074e40 Binary files /dev/null and b/op-batcher/resources/g1.point differ diff --git a/op-batcher/resources/g2.point.powerOf2 b/op-batcher/resources/g2.point.powerOf2 new file mode 100644 index 000000000..58e349b6e Binary files /dev/null and b/op-batcher/resources/g2.point.powerOf2 differ diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index 4e56649c6..2afd3b0f6 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -260,6 +260,12 @@ var ( Value: "http://da:26658", EnvVar: prefixEnvVar("DA_RPC"), } + DARPCTimeout = cli.DurationFlag{ + Name: "da-rpc-timeout", + Usage: "Data Availability RPC Timeout", + Value: 5 * time.Second, + EnvVar: prefixEnvVar("DA_RPC_TIMEOUT"), + } /* Optional Flags */ BeaconAddr = cli.StringFlag{ Name: "l1.beacon", @@ -337,6 +343,7 @@ var optionalFlags = []cli.Flag{ MantleDAIndexerEnableFlag, RPCEnableAdmin, DARPC, + DARPCTimeout, } // Flags contains the list of configuration options available to the binary. diff --git a/op-node/metrics/metrics.go b/op-node/metrics/metrics.go index 932d69552..7dc15380a 100644 --- a/op-node/metrics/metrics.go +++ b/op-node/metrics/metrics.go @@ -146,6 +146,11 @@ type Metrics struct { registry *prometheus.Registry factory metrics.Factory + + eigendaRpcSubmit *prometheus.CounterVec + eigendaRpcSuccess *prometheus.CounterVec + eigendaRpcFailed *prometheus.CounterVec + eigendaRpcDuration *prometheus.SummaryVec } var _ Metricer = (*Metrics)(nil) @@ -432,6 +437,26 @@ func NewMetrics(procName string) *Metrics { Name: "sequencer_sealing_total", Help: "Number of sequencer block sealing jobs", }), + eigendaRpcSubmit: factory.NewCounterVec(prometheus.CounterOpts{ + Namespace: ns, + Name: "eigenda_rpc_submit", + Help: "Number of eigenda rpc submit", + }, []string{"method"}), + eigendaRpcSuccess: factory.NewCounterVec(prometheus.CounterOpts{ + Namespace: ns, + Name: "eigenda_rpc_success", + Help: "Number of eigenda rpc success", + }, []string{"method"}), + eigendaRpcFailed: factory.NewCounterVec(prometheus.CounterOpts{ + Namespace: ns, + Name: "eigenda_rpc_failed", + Help: "Number of eigenda rpc failed", + }, []string{"method"}), + eigendaRpcDuration: factory.NewSummaryVec(prometheus.SummaryOpts{ + Namespace: ns, + Name: "eigenda_rpc_duration", + Help: "Eigenda rpc duration", + }, []string{"method"}), registry: registry, factory: factory, @@ -691,6 +716,19 @@ func (m *Metrics) RecordFrames(frameSize int) { m.ParseFrameSize.Add(float64(frameSize)) } +func (m *Metrics) RecordInterval(method string) func(error) { + m.eigendaRpcSubmit.WithLabelValues(method).Inc() + timer := prometheus.NewTimer(m.eigendaRpcDuration.WithLabelValues(method)) + return func(err error) { + timer.ObserveDuration() + if err != nil { + m.eigendaRpcFailed.WithLabelValues(method).Inc() + } else { + m.eigendaRpcSuccess.WithLabelValues(method).Inc() + } + } +} + type noopMetricer struct{} var NoopMetrics Metricer = new(noopMetricer) diff --git a/op-node/node/config.go b/op-node/node/config.go index 06e4706b0..55f83c13d 100644 --- a/op-node/node/config.go +++ b/op-node/node/config.go @@ -113,5 +113,12 @@ func (cfg *Config) Check() error { return fmt.Errorf("beacon config error: %w", err) } } + + if cfg.Rollup.MantleDaSwitch && !cfg.DatastoreConfig.MantleDAIndexerEnable { + if cfg.DA.RPC != "" && cfg.Beacon == nil { + return errors.New("beacon config must be set when using eigenda") + } + } + return nil } diff --git a/op-node/node/node.go b/op-node/node/node.go index 8f68b035f..1669e76d5 100644 --- a/op-node/node/node.go +++ b/op-node/node/node.go @@ -211,7 +211,7 @@ func (n *OpNode) initL2(ctx context.Context, cfg *Config, snapshotLog log.Logger } n.daSyncer = da.NewMantleDataStore(ctx, &cfg.DatastoreConfig) - n.eigenDaSyncer = da.NewEigenDADataStore(ctx, n.log, &cfg.DA, &cfg.DatastoreConfig) + n.eigenDaSyncer = da.NewEigenDADataStore(ctx, n.log, &cfg.DA, &cfg.DatastoreConfig, n.metrics) n.l2Driver = driver.NewDriver(&cfg.Driver, &cfg.Rollup, n.l2Source, n.l1Source, n.beacon, n.daSyncer, n, n, n.log, snapshotLog, n.metrics, &cfg.Sync, n.eigenDaSyncer) diff --git a/op-node/rollup/da/datastore.go b/op-node/rollup/da/datastore.go index c11995635..1b5fbb284 100644 --- a/op-node/rollup/da/datastore.go +++ b/op-node/rollup/da/datastore.go @@ -209,12 +209,13 @@ type EigenDADataStore struct { Ctx context.Context } -func NewEigenDADataStore(ctx context.Context, log log.Logger, daCfg *eigenda.Config, cfg *MantleDataStoreConfig) *EigenDADataStore { +func NewEigenDADataStore(ctx context.Context, log log.Logger, daCfg *eigenda.Config, cfg *MantleDataStoreConfig, m eigenda.Metrics) *EigenDADataStore { var daClient eigenda.IEigenDA if daCfg != nil { daClient = &eigenda.EigenDA{ - Log: log, - Config: *daCfg, + Log: log, + Config: *daCfg, + Metricer: m, } } return &EigenDADataStore{ diff --git a/op-node/rollup/da/datastore_test.go b/op-node/rollup/da/datastore_test.go new file mode 100644 index 000000000..7cad6e31d --- /dev/null +++ b/op-node/rollup/da/datastore_test.go @@ -0,0 +1,60 @@ +package da + +import ( + "context" + "fmt" + "testing" + + "github.com/Layr-Labs/datalayr/common/graphView" + "github.com/shurcooL/graphql" +) + +func TestMantleDataStore_RetrievalFramesFromDaIndexer(t *testing.T) { + type fields struct { + Ctx context.Context + Cfg *MantleDataStoreConfig + GraphClient *graphView.GraphClient + GraphqlClient *graphql.Client + } + type args struct { + dataStoreId uint32 + } + tests := []struct { + name string + fields fields + args args + want []byte + wantErr bool + }{ + { + name: "t1", + fields: fields{ + Ctx: context.Background(), + Cfg: &MantleDataStoreConfig{ + MantleDaIndexerSocket: "da-indexer-api-sepolia-qa6.qa.gomantle.org:80", + }, + GraphClient: &graphView.GraphClient{}, + GraphqlClient: &graphql.Client{}, + }, + args: args{ + dataStoreId: 10138, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mda := &MantleDataStore{ + Ctx: tt.fields.Ctx, + Cfg: tt.fields.Cfg, + GraphClient: tt.fields.GraphClient, + GraphqlClient: tt.fields.GraphqlClient, + } + got, err := mda.RetrievalFramesFromDaIndexer(tt.args.dataStoreId) + if (err != nil) != tt.wantErr { + t.Errorf("MantleDataStore.RetrievalFramesFromDaIndexer() error = %v, wantErr %v", err, tt.wantErr) + return + } + fmt.Println("got:", len(got)) + }) + } +} diff --git a/op-node/rollup/derive/calldata_source_test.go b/op-node/rollup/derive/calldata_source_test.go index 3f18a1fce..d7edb7d96 100644 --- a/op-node/rollup/derive/calldata_source_test.go +++ b/op-node/rollup/derive/calldata_source_test.go @@ -180,7 +180,7 @@ func TestRetrieveFromDaIndexer(t *testing.T) { eigenDaSyncer := da.NewEigenDADataStore(context.Background(), log.New("t1"), &eigenDA, &da.MantleDataStoreConfig{ MantleDaIndexerSocket: "127.0.0.1:32111", MantleDAIndexerEnable: true, - }) + }, nil) out := []eth.Data{} diff --git a/op-node/service.go b/op-node/service.go index 44822afdd..7bc1aa8ae 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -253,8 +253,10 @@ func NewSyncConfig(ctx *cli.Context) *sync.Config { func NewEigenDAConfig(ctx *cli.Context) (eigenda.Config, error) { rpc := ctx.String(flags.DARPC.Name) + rpcTimeout := ctx.Duration(flags.DARPCTimeout.Name) return eigenda.Config{ - RPC: rpc, + RPC: rpc, + RPCTimeout: rpcTimeout, // Can leave everything else unfilled for the node }, nil } diff --git a/op-service/eigenda/bindings/EigenDAServiceManager/binding.go b/op-service/eigenda/bindings/EigenDAServiceManager/binding.go new file mode 100644 index 000000000..82a0f0a8f --- /dev/null +++ b/op-service/eigenda/bindings/EigenDAServiceManager/binding.go @@ -0,0 +1,2660 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package contractEigenDAServiceManager + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// BN254G1Point is an auto generated low-level Go binding around an user-defined struct. +type BN254G1Point struct { + X *big.Int + Y *big.Int +} + +// BN254G2Point is an auto generated low-level Go binding around an user-defined struct. +type BN254G2Point struct { + X [2]*big.Int + Y [2]*big.Int +} + +// IBLSSignatureCheckerNonSignerStakesAndSignature is an auto generated low-level Go binding around an user-defined struct. +type IBLSSignatureCheckerNonSignerStakesAndSignature struct { + NonSignerQuorumBitmapIndices []uint32 + NonSignerPubkeys []BN254G1Point + QuorumApks []BN254G1Point + ApkG2 BN254G2Point + Sigma BN254G1Point + QuorumApkIndices []uint32 + TotalStakeIndices []uint32 + NonSignerStakeIndices [][]uint32 +} + +// IBLSSignatureCheckerQuorumStakeTotals is an auto generated low-level Go binding around an user-defined struct. +type IBLSSignatureCheckerQuorumStakeTotals struct { + SignedStakeForQuorum []*big.Int + TotalStakeForQuorum []*big.Int +} + +// IEigenDAServiceManagerBatchHeader is an auto generated low-level Go binding around an user-defined struct. +type IEigenDAServiceManagerBatchHeader struct { + BlobHeadersRoot [32]byte + QuorumNumbers []byte + SignedStakeForQuorums []byte + ReferenceBlockNumber uint32 +} + +// IRewardsCoordinatorRewardsSubmission is an auto generated low-level Go binding around an user-defined struct. +type IRewardsCoordinatorRewardsSubmission struct { + StrategiesAndMultipliers []IRewardsCoordinatorStrategyAndMultiplier + Token common.Address + Amount *big.Int + StartTimestamp uint32 + Duration uint32 +} + +// IRewardsCoordinatorStrategyAndMultiplier is an auto generated low-level Go binding around an user-defined struct. +type IRewardsCoordinatorStrategyAndMultiplier struct { + Strategy common.Address + Multiplier *big.Int +} + +// ISignatureUtilsSignatureWithSaltAndExpiry is an auto generated low-level Go binding around an user-defined struct. +type ISignatureUtilsSignatureWithSaltAndExpiry struct { + Signature []byte + Salt [32]byte + Expiry *big.Int +} + +// ContractEigenDAServiceManagerMetaData contains all meta data concerning the ContractEigenDAServiceManager contract. +var ContractEigenDAServiceManagerMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"__avsDirectory\",\"type\":\"address\",\"internalType\":\"contractIAVSDirectory\"},{\"name\":\"__rewardsCoordinator\",\"type\":\"address\",\"internalType\":\"contractIRewardsCoordinator\"},{\"name\":\"__registryCoordinator\",\"type\":\"address\",\"internalType\":\"contractIRegistryCoordinator\"},{\"name\":\"__stakeRegistry\",\"type\":\"address\",\"internalType\":\"contractIStakeRegistry\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"BLOCK_STALE_MEASURE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"STORE_DURATION_BLOCKS\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"THRESHOLD_DENOMINATOR\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"avsDirectory\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"batchId\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"batchIdToBatchMetadataHash\",\"inputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"blsApkRegistry\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIBLSApkRegistry\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"checkSignatures\",\"inputs\":[{\"name\":\"msgHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"quorumNumbers\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"referenceBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"params\",\"type\":\"tuple\",\"internalType\":\"structIBLSSignatureChecker.NonSignerStakesAndSignature\",\"components\":[{\"name\":\"nonSignerQuorumBitmapIndices\",\"type\":\"uint32[]\",\"internalType\":\"uint32[]\"},{\"name\":\"nonSignerPubkeys\",\"type\":\"tuple[]\",\"internalType\":\"structBN254.G1Point[]\",\"components\":[{\"name\":\"X\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"Y\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"name\":\"quorumApks\",\"type\":\"tuple[]\",\"internalType\":\"structBN254.G1Point[]\",\"components\":[{\"name\":\"X\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"Y\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"name\":\"apkG2\",\"type\":\"tuple\",\"internalType\":\"structBN254.G2Point\",\"components\":[{\"name\":\"X\",\"type\":\"uint256[2]\",\"internalType\":\"uint256[2]\"},{\"name\":\"Y\",\"type\":\"uint256[2]\",\"internalType\":\"uint256[2]\"}]},{\"name\":\"sigma\",\"type\":\"tuple\",\"internalType\":\"structBN254.G1Point\",\"components\":[{\"name\":\"X\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"Y\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"name\":\"quorumApkIndices\",\"type\":\"uint32[]\",\"internalType\":\"uint32[]\"},{\"name\":\"totalStakeIndices\",\"type\":\"uint32[]\",\"internalType\":\"uint32[]\"},{\"name\":\"nonSignerStakeIndices\",\"type\":\"uint32[][]\",\"internalType\":\"uint32[][]\"}]}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIBLSSignatureChecker.QuorumStakeTotals\",\"components\":[{\"name\":\"signedStakeForQuorum\",\"type\":\"uint96[]\",\"internalType\":\"uint96[]\"},{\"name\":\"totalStakeForQuorum\",\"type\":\"uint96[]\",\"internalType\":\"uint96[]\"}]},{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"confirmBatch\",\"inputs\":[{\"name\":\"batchHeader\",\"type\":\"tuple\",\"internalType\":\"structIEigenDAServiceManager.BatchHeader\",\"components\":[{\"name\":\"blobHeadersRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"quorumNumbers\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"signedStakeForQuorums\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"referenceBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"nonSignerStakesAndSignature\",\"type\":\"tuple\",\"internalType\":\"structIBLSSignatureChecker.NonSignerStakesAndSignature\",\"components\":[{\"name\":\"nonSignerQuorumBitmapIndices\",\"type\":\"uint32[]\",\"internalType\":\"uint32[]\"},{\"name\":\"nonSignerPubkeys\",\"type\":\"tuple[]\",\"internalType\":\"structBN254.G1Point[]\",\"components\":[{\"name\":\"X\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"Y\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"name\":\"quorumApks\",\"type\":\"tuple[]\",\"internalType\":\"structBN254.G1Point[]\",\"components\":[{\"name\":\"X\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"Y\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"name\":\"apkG2\",\"type\":\"tuple\",\"internalType\":\"structBN254.G2Point\",\"components\":[{\"name\":\"X\",\"type\":\"uint256[2]\",\"internalType\":\"uint256[2]\"},{\"name\":\"Y\",\"type\":\"uint256[2]\",\"internalType\":\"uint256[2]\"}]},{\"name\":\"sigma\",\"type\":\"tuple\",\"internalType\":\"structBN254.G1Point\",\"components\":[{\"name\":\"X\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"Y\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"name\":\"quorumApkIndices\",\"type\":\"uint32[]\",\"internalType\":\"uint32[]\"},{\"name\":\"totalStakeIndices\",\"type\":\"uint32[]\",\"internalType\":\"uint32[]\"},{\"name\":\"nonSignerStakeIndices\",\"type\":\"uint32[][]\",\"internalType\":\"uint32[][]\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"createAVSRewardsSubmission\",\"inputs\":[{\"name\":\"rewardsSubmissions\",\"type\":\"tuple[]\",\"internalType\":\"structIRewardsCoordinator.RewardsSubmission[]\",\"components\":[{\"name\":\"strategiesAndMultipliers\",\"type\":\"tuple[]\",\"internalType\":\"structIRewardsCoordinator.StrategyAndMultiplier[]\",\"components\":[{\"name\":\"strategy\",\"type\":\"address\",\"internalType\":\"contractIStrategy\"},{\"name\":\"multiplier\",\"type\":\"uint96\",\"internalType\":\"uint96\"}]},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"contractIERC20\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"startTimestamp\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"duration\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"delegation\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIDelegationManager\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"deregisterOperatorFromAVS\",\"inputs\":[{\"name\":\"operator\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"getOperatorRestakedStrategies\",\"inputs\":[{\"name\":\"operator\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getRestakeableStrategies\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"_pauserRegistry\",\"type\":\"address\",\"internalType\":\"contractIPauserRegistry\"},{\"name\":\"_initialPausedStatus\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_initialOwner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_batchConfirmers\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"_rewardsInitiator\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"isBatchConfirmer\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"latestServeUntilBlock\",\"inputs\":[{\"name\":\"referenceBlockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pause\",\"inputs\":[{\"name\":\"newPausedStatus\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"pauseAll\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"paused\",\"inputs\":[{\"name\":\"index\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"paused\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pauserRegistry\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIPauserRegistry\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"quorumAdversaryThresholdPercentages\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"quorumConfirmationThresholdPercentages\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"quorumNumbersRequired\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"registerOperatorToAVS\",\"inputs\":[{\"name\":\"operator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operatorSignature\",\"type\":\"tuple\",\"internalType\":\"structISignatureUtils.SignatureWithSaltAndExpiry\",\"components\":[{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"expiry\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"registryCoordinator\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIRegistryCoordinator\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"rewardsInitiator\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setBatchConfirmer\",\"inputs\":[{\"name\":\"_batchConfirmer\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setPauserRegistry\",\"inputs\":[{\"name\":\"newPauserRegistry\",\"type\":\"address\",\"internalType\":\"contractIPauserRegistry\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setRewardsInitiator\",\"inputs\":[{\"name\":\"newRewardsInitiator\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setStaleStakesForbidden\",\"inputs\":[{\"name\":\"value\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"stakeRegistry\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIStakeRegistry\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"staleStakesForbidden\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"taskNumber\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"trySignatureAndApkVerification\",\"inputs\":[{\"name\":\"msgHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"apk\",\"type\":\"tuple\",\"internalType\":\"structBN254.G1Point\",\"components\":[{\"name\":\"X\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"Y\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"name\":\"apkG2\",\"type\":\"tuple\",\"internalType\":\"structBN254.G2Point\",\"components\":[{\"name\":\"X\",\"type\":\"uint256[2]\",\"internalType\":\"uint256[2]\"},{\"name\":\"Y\",\"type\":\"uint256[2]\",\"internalType\":\"uint256[2]\"}]},{\"name\":\"sigma\",\"type\":\"tuple\",\"internalType\":\"structBN254.G1Point\",\"components\":[{\"name\":\"X\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"Y\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}],\"outputs\":[{\"name\":\"pairingSuccessful\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"siganatureIsValid\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unpause\",\"inputs\":[{\"name\":\"newPausedStatus\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"updateAVSMetadataURI\",\"inputs\":[{\"name\":\"_metadataURI\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"BatchConfirmed\",\"inputs\":[{\"name\":\"batchHeaderHash\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"batchId\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"BatchConfirmerStatusChanged\",\"inputs\":[{\"name\":\"batchConfirmer\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"status\",\"type\":\"bool\",\"indexed\":false,\"internalType\":\"bool\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"indexed\":false,\"internalType\":\"uint8\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Paused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newPausedStatus\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"PauserRegistrySet\",\"inputs\":[{\"name\":\"pauserRegistry\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"contractIPauserRegistry\"},{\"name\":\"newPauserRegistry\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"contractIPauserRegistry\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RewardsInitiatorUpdated\",\"inputs\":[{\"name\":\"prevRewardsInitiator\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"newRewardsInitiator\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"StaleStakesForbiddenUpdate\",\"inputs\":[{\"name\":\"value\",\"type\":\"bool\",\"indexed\":false,\"internalType\":\"bool\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Unpaused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newPausedStatus\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false}]", + Bin: "0x6101806040523480156200001257600080fd5b5060405162005871380380620058718339810160408190526200003591620002e5565b6001600160a01b0380851660805280841660a05280831660c052811660e0528184848284620000636200020a565b50505050806001600160a01b0316610100816001600160a01b031681525050806001600160a01b031663683048356040518163ffffffff1660e01b8152600401602060405180830381865afa158015620000c1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620000e791906200034d565b6001600160a01b0316610120816001600160a01b031681525050806001600160a01b0316635df459466040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000140573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200016691906200034d565b6001600160a01b0316610140816001600160a01b031681525050610120516001600160a01b031663df5cf7236040518163ffffffff1660e01b8152600401602060405180830381865afa158015620001c2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001e891906200034d565b6001600160a01b03166101605250620002006200020a565b5050505062000374565b600054610100900460ff1615620002775760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60005460ff9081161015620002ca576000805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b6001600160a01b0381168114620002e257600080fd5b50565b60008060008060808587031215620002fc57600080fd5b84516200030981620002cc565b60208601519094506200031c81620002cc565b60408601519093506200032f81620002cc565b60608601519092506200034281620002cc565b939692955090935050565b6000602082840312156200036057600080fd5b81516200036d81620002cc565b9392505050565b60805160a05160c05160e051610100516101205161014051610160516153ef6200048260003960008181610569015261179501526000818161036c01526119770152600081816103be01528181611b4d0152611d0f01526000818161040b01528181610eb101528181611460015281816115f80152611832015260008181610bc901528181610d2401528181610dbb0152818161298001528181612b030152612ba20152600081816109f401528181610a8301528181610b03015281816126e4015281816127a8015281816128be0152612a5e01526000818161305f0152818161311b01526132070152600081816103e20152818161273801528181612804015261288301526153ef6000f3fe608060405234801561001057600080fd5b50600436106102535760003560e01c80637794965a11610146578063df5cf723116100c3578063ef02445811610087578063ef024458146105e3578063f1220983146105eb578063f2fde38b146105fe578063fabc1cbc14610611578063fc299dee14610624578063fce36c7d1461063757600080fd5b8063df5cf72314610564578063e15234ff1461058b578063e481af9d146105a8578063eaefd27d146105b0578063eccbbfc9146105c357600080fd5b8063a364f4da1161010a578063a364f4da146104ee578063a5b7890a14610501578063a98fb35514610524578063b98d090814610537578063bafa91071461054457600080fd5b80637794965a146104775780638687feae1461048a578063886f1195146104b75780638da5cb5b146104ca5780639926ee7d146104db57600080fd5b80635df45946116101d45780636d14a987116101985780636d14a987146104065780636efb46361461042d578063715018a61461044e57806372d18e8d14610456578063775bbcb51461046457600080fd5b80635df45946146103675780635e033476146103a65780635e8b3f2d146103b057806368304835146103b95780636b3aa72e146103e057600080fd5b8063416c7e5e1161021b578063416c7e5e146102e25780634972134a146102f5578063595c6a671461031a5780635ac86ab7146103225780635c975abb1461035557600080fd5b806310d67a2f14610258578063136439dd1461026d578063171f1d5b1461028057806333cfb7b7146102af5780633bc28c8c146102cf575b600080fd5b61026b6102663660046141af565b61064a565b005b61026b61027b3660046141cc565b610706565b61029361028e366004614336565b610845565b6040805192151583529015156020830152015b60405180910390f35b6102c26102bd3660046141af565b6109cf565b6040516102a69190614392565b61026b6102dd3660046141af565b610e9e565b61026b6102f03660046143ed565b610eaf565b6065546103059063ffffffff1681565b60405163ffffffff90911681526020016102a6565b61026b610fe6565b610345610330366004614419565b60fc54600160ff9092169190911b9081161490565b60405190151581526020016102a6565b60fc545b6040519081526020016102a6565b61038e7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016102a6565b610305620189c081565b61030561012c81565b61038e7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000061038e565b61038e7f000000000000000000000000000000000000000000000000000000000000000081565b61044061043b3660046146ec565b6110ad565b6040516102a69291906147df565b61026b611fc4565b60655463ffffffff16610305565b61026b610472366004614828565b611fd8565b61026b610485366004614903565b612141565b6104aa604051806040016040528060018152602001602160f81b81525081565b6040516102a691906149bb565b60fb5461038e906001600160a01b031681565b6033546001600160a01b031661038e565b61026b6104e9366004614a45565b6126d9565b61026b6104fc3660046141af565b61279d565b61034561050f3660046141af565b60676020526000908152604090205460ff1681565b61026b610532366004614af0565b612864565b60c9546103459060ff1681565b6104aa604051806040016040528060018152602001603760f81b81525081565b61038e7f000000000000000000000000000000000000000000000000000000000000000081565b6104aa604051806040016040528060018152602001600081525081565b6102c26128b8565b6103056105be366004614b40565b612c81565b6103596105d1366004614b40565b60666020526000908152604090205481565b610359606481565b61026b6105f93660046141af565b612ca3565b61026b61060c3660046141af565b612cb4565b61026b61061f3660046141cc565b612d2a565b60975461038e906001600160a01b031681565b61026b610645366004614b5b565b612e86565b60fb60009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561069d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106c19190614bcf565b6001600160a01b0316336001600160a01b0316146106fa5760405162461bcd60e51b81526004016106f190614bec565b60405180910390fd5b6107038161323e565b50565b60fb5460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa15801561074e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107729190614c36565b61078e5760405162461bcd60e51b81526004016106f190614c53565b60fc54818116146108075760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e70617573653a20696e76616c696420617474656d70742060448201527f746f20756e70617573652066756e6374696f6e616c697479000000000000000060648201526084016106f1565b60fc81905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d906020015b60405180910390a250565b60008060007f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018787600001518860200151886000015160006002811061088d5761088d614c9b565b60200201518951600160200201518a602001516000600281106108b2576108b2614c9b565b60200201518b602001516001600281106108ce576108ce614c9b565b602090810291909101518c518d83015160405161092b9a99989796959401988952602089019790975260408801959095526060870193909352608086019190915260a085015260c084015260e08301526101008201526101200190565b6040516020818303038152906040528051906020012060001c61094e9190614cb1565b90506109c16109676109608884613335565b86906133cc565b61096f613460565b6109b76109a8856109a2604080518082018252600080825260209182015281518083019092526001825260029082015290565b90613335565b6109b18c613520565b906133cc565b886201d4c06135b0565b909890975095505050505050565b6040516309aa152760e11b81526001600160a01b0382811660048301526060916000917f000000000000000000000000000000000000000000000000000000000000000016906313542a4e90602401602060405180830381865afa158015610a3b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a5f9190614cd3565b60405163871ef04960e01b8152600481018290529091506000906001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063871ef04990602401602060405180830381865afa158015610aca573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610aee9190614cec565b90506001600160c01b0381161580610b8857507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639aa1653d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b5f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b839190614d15565b60ff16155b15610ba457505060408051600081526020810190915292915050565b6000610bb8826001600160c01b03166137d4565b90506000805b8251811015610c8e577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316633ca5a5f5848381518110610c0857610c08614c9b565b01602001516040516001600160e01b031960e084901b16815260f89190911c6004820152602401602060405180830381865afa158015610c4c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c709190614cd3565b610c7a9083614d48565b915080610c8681614d60565b915050610bbe565b506000816001600160401b03811115610ca957610ca96141e5565b604051908082528060200260200182016040528015610cd2578160200160208202803683370190505b5090506000805b8451811015610e91576000858281518110610cf657610cf6614c9b565b0160200151604051633ca5a5f560e01b815260f89190911c6004820181905291506000906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690633ca5a5f590602401602060405180830381865afa158015610d6b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d8f9190614cd3565b905060005b81811015610e7b576040516356e4026d60e11b815260ff84166004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063adc804da906044016040805180830381865afa158015610e09573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e2d9190614d90565b60000151868681518110610e4357610e43614c9b565b6001600160a01b039092166020928302919091019091015284610e6581614d60565b9550508080610e7390614d60565b915050610d94565b5050508080610e8990614d60565b915050610cd9565b5090979650505050505050565b610ea6613896565b610703816138f0565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f0d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f319190614bcf565b6001600160a01b0316336001600160a01b031614610fdd5760405162461bcd60e51b815260206004820152605c60248201527f424c535369676e6174757265436865636b65722e6f6e6c79436f6f7264696e6160448201527f746f724f776e65723a2063616c6c6572206973206e6f7420746865206f776e6560648201527f72206f6620746865207265676973747279436f6f7264696e61746f7200000000608482015260a4016106f1565b61070381613959565b60fb5460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa15801561102e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110529190614c36565b61106e5760405162461bcd60e51b81526004016106f190614c53565b60001960fc81905560405190815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a2565b60408051808201909152606080825260208201526000846111245760405162461bcd60e51b8152602060048201526037602482015260008051602061539a83398151915260448201527f7265733a20656d7074792071756f72756d20696e70757400000000000000000060648201526084016106f1565b6040830151518514801561113c575060a08301515185145b801561114c575060c08301515185145b801561115c575060e08301515185145b6111c65760405162461bcd60e51b8152602060048201526041602482015260008051602061539a83398151915260448201527f7265733a20696e7075742071756f72756d206c656e677468206d69736d6174636064820152600d60fb1b608482015260a4016106f1565b8251516020840151511461123e5760405162461bcd60e51b81526020600482015260446024820181905260008051602061539a833981519152908201527f7265733a20696e707574206e6f6e7369676e6572206c656e677468206d69736d6064820152630c2e8c6d60e31b608482015260a4016106f1565b4363ffffffff168463ffffffff16106112ad5760405162461bcd60e51b815260206004820152603c602482015260008051602061539a83398151915260448201527f7265733a20696e76616c6964207265666572656e636520626c6f636b0000000060648201526084016106f1565b6040805180820182526000808252602080830191909152825180840190935260608084529083015290866001600160401b038111156112ee576112ee6141e5565b604051908082528060200260200182016040528015611317578160200160208202803683370190505b506020820152866001600160401b03811115611335576113356141e5565b60405190808252806020026020018201604052801561135e578160200160208202803683370190505b50815260408051808201909152606080825260208201528560200151516001600160401b03811115611392576113926141e5565b6040519080825280602002602001820160405280156113bb578160200160208202803683370190505b5081526020860151516001600160401b038111156113db576113db6141e5565b604051908082528060200260200182016040528015611404578160200160208202803683370190505b50816020018190525060006114d68a8a8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505060408051639aa1653d60e01b815290516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169350639aa1653d925060048083019260209291908290030181865afa1580156114ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114d19190614d15565b6139a1565b905060005b876020015151811015611771576115208860200151828151811061150157611501614c9b565b6020026020010151805160009081526020918201519091526040902090565b8360200151828151811061153657611536614c9b565b602090810291909101015280156115f6576020830151611557600183614dd1565b8151811061156757611567614c9b565b602002602001015160001c8360200151828151811061158857611588614c9b565b602002602001015160001c116115f6576040805162461bcd60e51b815260206004820152602481019190915260008051602061539a83398151915260448201527f7265733a206e6f6e5369676e65725075626b657973206e6f7420736f7274656460648201526084016106f1565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166304ec63518460200151838151811061163b5761163b614c9b565b60200260200101518b8b60000151858151811061165a5761165a614c9b565b60200260200101516040518463ffffffff1660e01b81526004016116979392919092835263ffffffff918216602084015216604082015260600190565b602060405180830381865afa1580156116b4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116d89190614cec565b6001600160c01b0316836000015182815181106116f7576116f7614c9b565b60200260200101818152505061175d610960611731848660000151858151811061172357611723614c9b565b602002602001015116613a32565b8a60200151848151811061174757611747614c9b565b6020026020010151613a5d90919063ffffffff16565b94508061176981614d60565b9150506114db565b505061177c83613b41565b60c95490935060ff16600081611793576000611815565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663c448feb86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156117f1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118159190614cd3565b905060005b8a811015611e93578215611975578963ffffffff16827f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663249a0c428f8f8681811061187157611871614c9b565b60405160e085901b6001600160e01b031916815292013560f81c600483015250602401602060405180830381865afa1580156118b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118d59190614cd3565b6118df9190614d48565b116119755760405162461bcd60e51b8152602060048201526066602482015260008051602061539a83398151915260448201527f7265733a205374616b6552656769737472792075706461746573206d7573742060648201527f62652077697468696e207769746864726177616c44656c6179426c6f636b732060848201526577696e646f7760d01b60a482015260c4016106f1565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166368bccaac8d8d848181106119b6576119b6614c9b565b9050013560f81c60f81b60f81c8c8c60a0015185815181106119da576119da614c9b565b60209081029190910101516040516001600160e01b031960e086901b16815260ff909316600484015263ffffffff9182166024840152166044820152606401602060405180830381865afa158015611a36573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a5a9190614de8565b6001600160401b031916611a7d8a60400151838151811061150157611501614c9b565b67ffffffffffffffff191614611b195760405162461bcd60e51b8152602060048201526061602482015260008051602061539a83398151915260448201527f7265733a2071756f72756d41706b206861736820696e2073746f72616765206460648201527f6f6573206e6f74206d617463682070726f76696465642071756f72756d2061706084820152606b60f81b60a482015260c4016106f1565b611b4989604001518281518110611b3257611b32614c9b565b6020026020010151876133cc90919063ffffffff16565b95507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663c8294c568d8d84818110611b8c57611b8c614c9b565b9050013560f81c60f81b60f81c8c8c60c001518581518110611bb057611bb0614c9b565b60209081029190910101516040516001600160e01b031960e086901b16815260ff909316600484015263ffffffff9182166024840152166044820152606401602060405180830381865afa158015611c0c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c309190614e13565b85602001518281518110611c4657611c46614c9b565b6001600160601b03909216602092830291909101820152850151805182908110611c7257611c72614c9b565b602002602001015185600001518281518110611c9057611c90614c9b565b60200260200101906001600160601b031690816001600160601b0316815250506000805b8a6020015151811015611e7e57611d0886600001518281518110611cda57611cda614c9b565b60200260200101518f8f86818110611cf457611cf4614c9b565b600192013560f81c9290921c811614919050565b15611e6c577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663f2be94ae8f8f86818110611d4e57611d4e614c9b565b9050013560f81c60f81b60f81c8e89602001518581518110611d7257611d72614c9b565b60200260200101518f60e001518881518110611d9057611d90614c9b565b60200260200101518781518110611da957611da9614c9b565b60209081029190910101516040516001600160e01b031960e087901b16815260ff909416600485015263ffffffff92831660248501526044840191909152166064820152608401602060405180830381865afa158015611e0d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e319190614e13565b8751805185908110611e4557611e45614c9b565b60200260200101818151611e599190614e30565b6001600160601b03169052506001909101905b80611e7681614d60565b915050611cb4565b50508080611e8b90614d60565b91505061181a565b505050600080611ead8c868a606001518b60800151610845565b9150915081611f1e5760405162461bcd60e51b8152602060048201526043602482015260008051602061539a83398151915260448201527f7265733a2070616972696e6720707265636f6d70696c652063616c6c206661696064820152621b195960ea1b608482015260a4016106f1565b80611f7f5760405162461bcd60e51b8152602060048201526039602482015260008051602061539a83398151915260448201527f7265733a207369676e617475726520697320696e76616c69640000000000000060648201526084016106f1565b50506000878260200151604051602001611f9a929190614e58565b60408051808303601f190181529190528051602090910120929b929a509198505050505050505050565b611fcc613896565b611fd66000613bdc565b565b600054610100900460ff1615808015611ff85750600054600160ff909116105b806120125750303b158015612012575060005460ff166001145b6120755760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016106f1565b6000805460ff191660011790558015612098576000805461ff0019166101001790555b6120a28686613c2e565b6120ab84613bdc565b6120b4826138f0565b60005b83518110156120f2576120e28482815181106120d5576120d5614c9b565b6020026020010151613d18565b6120eb81614d60565b90506120b7565b508015612139576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b60fc546000906001908116141561219a5760405162461bcd60e51b815260206004820152601960248201527f5061757361626c653a20696e646578206973207061757365640000000000000060448201526064016106f1565b3360009081526067602052604090205460ff1661220e5760405162461bcd60e51b815260206004820152602c60248201527f6f6e6c794261746368436f6e6669726d65723a206e6f742066726f6d2062617460448201526b31b41031b7b73334b936b2b960a11b60648201526084016106f1565b32331461228b5760405162461bcd60e51b8152602060048201526051602482015260008051602061537a83398151915260448201527f63683a2068656164657220616e64206e6f6e7369676e65722064617461206d75606482015270737420626520696e2063616c6c6461746160781b608482015260a4016106f1565b4361229c6080850160608601614b40565b63ffffffff161061231b5760405162461bcd60e51b815260206004820152604f602482015260008051602061537a83398151915260448201527f63683a20737065636966696564207265666572656e6365426c6f636b4e756d6260648201526e657220697320696e2066757475726560881b608482015260a4016106f1565b63ffffffff431661012c6123356080860160608701614b40565b61233f9190614ea0565b63ffffffff1610156123c55760405162461bcd60e51b8152602060048201526055602482015260008051602061537a83398151915260448201527f63683a20737065636966696564207265666572656e6365426c6f636b4e756d62606482015274195c881a5cc81d1bdbc819985c881a5b881c185cdd605a1b608482015260a4016106f1565b6123d26040840184614ec8565b90506123e16020850185614ec8565b9050146124795760405162461bcd60e51b8152602060048201526066602482015260008051602061537a83398151915260448201527f63683a2071756f72756d4e756d6265727320616e64207369676e65645374616b60648201527f65466f7251756f72756d73206d757374206265206f66207468652073616d65206084820152650d8cadccee8d60d31b60a482015260c4016106f1565b600061248c61248785614f15565b613d7b565b90506000806124b8836124a26020890189614ec8565b6124b260808b0160608c01614b40565b896110ad565b9150915060005b6124cc6040880188614ec8565b905081101561260e576124e26040880188614ec8565b828181106124f2576124f2614c9b565b9050013560f81c60f81b60f81c60ff168360200151828151811061251857612518614c9b565b602002602001015161252a9190614fb5565b6001600160601b031660648460000151838151811061254b5761254b614c9b565b60200260200101516001600160601b03166125669190614fe4565b10156125fc5760405162461bcd60e51b81526020600482015260646024820181905260008051602061537a83398151915260448301527f63683a207369676e61746f7269657320646f206e6f74206f776e206174206c65908201527f617374207468726573686f6c642070657263656e74616765206f6620612071756084820152636f72756d60e01b60a482015260c4016106f1565b8061260681614d60565b9150506124bf565b5060655463ffffffff16600061262388613df6565b6040805160208082018490528183018790524360e01b6001600160e01b0319166060830152825160448184030181526064830180855281519183019190912063ffffffff881660008181526066909452928590205552905191925086917fc75557c4ad49697e231449688be13ef11cb6be8ed0d18819d8dde074a5a16f8a9181900360840190a26126b5826001614ea0565b6065805463ffffffff191663ffffffff929092169190911790555050505050505050565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146127215760405162461bcd60e51b81526004016106f190615003565b604051639926ee7d60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690639926ee7d9061276f908590859060040161507b565b600060405180830381600087803b15801561278957600080fd5b505af1158015612139573d6000803e3d6000fd5b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146127e55760405162461bcd60e51b81526004016106f190615003565b6040516351b27a6d60e11b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063a364f4da906024015b600060405180830381600087803b15801561284957600080fd5b505af115801561285d573d6000803e3d6000fd5b5050505050565b61286c613896565b60405163a98fb35560e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063a98fb3559061282f9084906004016149bb565b606060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639aa1653d6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561291a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061293e9190614d15565b60ff1690508061295c57505060408051600081526020810190915290565b6000805b82811015612a1157604051633ca5a5f560e01b815260ff821660048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690633ca5a5f590602401602060405180830381865afa1580156129cf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129f39190614cd3565b6129fd9083614d48565b915080612a0981614d60565b915050612960565b506000816001600160401b03811115612a2c57612a2c6141e5565b604051908082528060200260200182016040528015612a55578160200160208202803683370190505b5090506000805b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639aa1653d6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612aba573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ade9190614d15565b60ff16811015612c7757604051633ca5a5f560e01b815260ff821660048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690633ca5a5f590602401602060405180830381865afa158015612b52573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b769190614cd3565b905060005b81811015612c62576040516356e4026d60e11b815260ff84166004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063adc804da906044016040805180830381865afa158015612bf0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c149190614d90565b60000151858581518110612c2a57612c2a614c9b565b6001600160a01b039092166020928302919091019091015283612c4c81614d60565b9450508080612c5a90614d60565b915050612b7b565b50508080612c6f90614d60565b915050612a5c565b5090949350505050565b600061012c612c93620189c084614ea0565b612c9d9190614ea0565b92915050565b612cab613896565b61070381613d18565b612cbc613896565b6001600160a01b038116612d215760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016106f1565b61070381613bdc565b60fb60009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612d7d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612da19190614bcf565b6001600160a01b0316336001600160a01b031614612dd15760405162461bcd60e51b81526004016106f190614bec565b60fc5419811960fc54191614612e4f5760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e756e70617573653a20696e76616c696420617474656d7060448201527f7420746f2070617573652066756e6374696f6e616c697479000000000000000060648201526084016106f1565b60fc81905560405181815233907f3582d1828e26bf56bd801502bc021ac0bc8afb57c826e4986b45593c8fad389c9060200161083a565b6097546001600160a01b03163314612f1b5760405162461bcd60e51b815260206004820152604c60248201527f536572766963654d616e61676572426173652e6f6e6c7952657761726473496e60448201527f69746961746f723a2063616c6c6572206973206e6f742074686520726577617260648201526b32399034b734ba34b0ba37b960a11b608482015260a4016106f1565b60005b818110156131ef57828282818110612f3857612f38614c9b565b9050602002810190612f4a91906150c6565b612f5b9060408101906020016141af565b6001600160a01b03166323b872dd3330868686818110612f7d57612f7d614c9b565b9050602002810190612f8f91906150c6565b604080516001600160e01b031960e087901b1681526001600160a01b039485166004820152939092166024840152013560448201526064016020604051808303816000875af1158015612fe6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061300a9190614c36565b50600083838381811061301f5761301f614c9b565b905060200281019061303191906150c6565b6130429060408101906020016141af565b604051636eb1769f60e11b81523060048201526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166024830152919091169063dd62ed3e90604401602060405180830381865afa1580156130b0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130d49190614cd3565b90508383838181106130e8576130e8614c9b565b90506020028101906130fa91906150c6565b61310b9060408101906020016141af565b6001600160a01b031663095ea7b37f00000000000000000000000000000000000000000000000000000000000000008387878781811061314d5761314d614c9b565b905060200281019061315f91906150c6565b6040013561316d9190614d48565b6040516001600160e01b031960e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303816000875af11580156131b8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906131dc9190614c36565b5050806131e890614d60565b9050612f1e565b5060405163fce36c7d60e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063fce36c7d9061276f9085908590600401615141565b6001600160a01b0381166132cc5760405162461bcd60e51b815260206004820152604960248201527f5061757361626c652e5f73657450617573657252656769737472793a206e657760448201527f50617573657252656769737472792063616e6e6f7420626520746865207a65726064820152686f206164647265737360b81b608482015260a4016106f1565b60fb54604080516001600160a01b03928316815291831660208301527f6e9fcd539896fca60e8b0f01dd580233e48a6b0f7df013b89ba7f565869acdb6910160405180910390a160fb80546001600160a01b0319166001600160a01b0392909216919091179055565b60408051808201909152600080825260208201526133516140c0565b835181526020808501519082015260408082018490526000908360608460076107d05a03fa905080801561338457613386565bfe5b50806133c45760405162461bcd60e51b815260206004820152600d60248201526c1958cb5b5d5b0b59985a5b1959609a1b60448201526064016106f1565b505092915050565b60408051808201909152600080825260208201526133e86140de565b835181526020808501518183015283516040808401919091529084015160608301526000908360808460066107d05a03fa90508080156133845750806133c45760405162461bcd60e51b815260206004820152600d60248201526c1958cb5859190b59985a5b1959609a1b60448201526064016106f1565b6134686140fc565b50604080516080810182527f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c28183019081527f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed6060830152815281518083019092527f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec82527f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d60208381019190915281019190915290565b60408051808201909152600080825260208201526000808061355060008051602061535a83398151915286614cb1565b90505b61355c81613e09565b909350915060008051602061535a833981519152828309831415613596576040805180820190915290815260208101919091529392505050565b60008051602061535a833981519152600182089050613553565b6040805180820182528681526020808201869052825180840190935286835282018490526000918291906135e2614121565b60005b60028110156137a75760006135fb826006614fe4565b905084826002811061360f5761360f614c9b565b60200201515183613621836000614d48565b600c811061363157613631614c9b565b602002015284826002811061364857613648614c9b565b6020020151602001518382600161365f9190614d48565b600c811061366f5761366f614c9b565b602002015283826002811061368657613686614c9b565b6020020151515183613699836002614d48565b600c81106136a9576136a9614c9b565b60200201528382600281106136c0576136c0614c9b565b60200201515160016020020151836136d9836003614d48565b600c81106136e9576136e9614c9b565b602002015283826002811061370057613700614c9b565b60200201516020015160006002811061371b5761371b614c9b565b60200201518361372c836004614d48565b600c811061373c5761373c614c9b565b602002015283826002811061375357613753614c9b565b60200201516020015160016002811061376e5761376e614c9b565b60200201518361377f836005614d48565b600c811061378f5761378f614c9b565b6020020152508061379f81614d60565b9150506135e5565b506137b0614140565b60006020826101808560088cfa9151919c9115159b50909950505050505050505050565b60606000806137e284613a32565b61ffff166001600160401b038111156137fd576137fd6141e5565b6040519080825280601f01601f191660200182016040528015613827576020820181803683370190505b5090506000805b82518210801561383f575061010081105b15612c77576001811b935085841615613886578060f81b83838151811061386857613868614c9b565b60200101906001600160f81b031916908160001a9053508160010191505b61388f81614d60565b905061382e565b6033546001600160a01b03163314611fd65760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016106f1565b609754604080516001600160a01b03928316815291831660208301527fe11cddf1816a43318ca175bbc52cd0185436e9cbead7c83acc54a73e461717e3910160405180910390a1609780546001600160a01b0319166001600160a01b0392909216919091179055565b60c9805460ff19168215159081179091556040519081527f40e4ed880a29e0f6ddce307457fb75cddf4feef7d3ecb0301bfdf4976a0e2dfc906020015b60405180910390a150565b6000806139ad84613e8b565b9050808360ff166001901b11613a2b5760405162461bcd60e51b815260206004820152603f60248201527f4269746d61705574696c732e6f72646572656442797465734172726179546f4260448201527f69746d61703a206269746d61702065786365656473206d61782076616c75650060648201526084016106f1565b9392505050565b6000805b8215612c9d57613a47600184614dd1565b9092169180613a558161524e565b915050613a36565b60408051808201909152600080825260208201526102008261ffff1610613ab95760405162461bcd60e51b815260206004820152601060248201526f7363616c61722d746f6f2d6c6172676560801b60448201526064016106f1565b8161ffff1660011415613acd575081612c9d565b6040805180820190915260008082526020820181905284906001905b8161ffff168661ffff1610613b3657600161ffff871660ff83161c81161415613b1957613b1684846133cc565b93505b613b2383846133cc565b92506201fffe600192831b169101613ae9565b509195945050505050565b60408051808201909152600080825260208201528151158015613b6657506020820151155b15613b84575050604080518082019091526000808252602082015290565b60405180604001604052808360000151815260200160008051602061535a8339815191528460200151613bb79190614cb1565b613bcf9060008051602061535a833981519152614dd1565b905292915050565b919050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60fb546001600160a01b0316158015613c4f57506001600160a01b03821615155b613cd15760405162461bcd60e51b815260206004820152604760248201527f5061757361626c652e5f696e697469616c697a655061757365723a205f696e6960448201527f7469616c697a6550617573657228292063616e206f6e6c792062652063616c6c6064820152666564206f6e636560c81b608482015260a4016106f1565b60fc81905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a2613d148261323e565b5050565b6001600160a01b038116600081815260676020908152604091829020805460ff8082161560ff1990921682179092558351948552161515908301527f5c3265f5fb462ef4930fe47beaa183647c97f19ba545b761f41bc8cd4621d4149101613996565b6000613db882604080518082019091526000808252602082015250604080518082019091528151815260609091015163ffffffff16602082015290565b6040805182516020808301919091529092015163ffffffff16908201526060015b604051602081830303815290604052805190602001209050919050565b600081604051602001613dd991906152de565b6000808060008051602061535a833981519152600360008051602061535a8339815191528660008051602061535a833981519152888909090890506000613e7f827f0c19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f5260008051602061535a833981519152614018565b91959194509092505050565b600061010082511115613f145760405162461bcd60e51b8152602060048201526044602482018190527f4269746d61705574696c732e6f72646572656442797465734172726179546f42908201527f69746d61703a206f7264657265644279746573417272617920697320746f6f206064820152636c6f6e6760e01b608482015260a4016106f1565b8151613f2257506000919050565b60008083600081518110613f3857613f38614c9b565b0160200151600160f89190911c81901b92505b845181101561400f57848181518110613f6657613f66614c9b565b0160200151600160f89190911c1b9150828211613ffb5760405162461bcd60e51b815260206004820152604760248201527f4269746d61705574696c732e6f72646572656442797465734172726179546f4260448201527f69746d61703a206f72646572656442797465734172726179206973206e6f74206064820152661bdc99195c995960ca1b608482015260a4016106f1565b9181179161400881614d60565b9050613f4b565b50909392505050565b600080614023614140565b61402b61415e565b602080825281810181905260408201819052606082018890526080820187905260a082018690528260c08360056107d05a03fa92508280156133845750826140b55760405162461bcd60e51b815260206004820152601a60248201527f424e3235342e6578704d6f643a2063616c6c206661696c75726500000000000060448201526064016106f1565b505195945050505050565b60405180606001604052806003906020820280368337509192915050565b60405180608001604052806004906020820280368337509192915050565b604051806040016040528061410f61417c565b815260200161411c61417c565b905290565b604051806101800160405280600c906020820280368337509192915050565b60405180602001604052806001906020820280368337509192915050565b6040518060c001604052806006906020820280368337509192915050565b60405180604001604052806002906020820280368337509192915050565b6001600160a01b038116811461070357600080fd5b6000602082840312156141c157600080fd5b8135613a2b8161419a565b6000602082840312156141de57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b038111828210171561421d5761421d6141e5565b60405290565b60405161010081016001600160401b038111828210171561421d5761421d6141e5565b604051601f8201601f191681016001600160401b038111828210171561426e5761426e6141e5565b604052919050565b60006040828403121561428857600080fd5b6142906141fb565b9050813581526020820135602082015292915050565b600082601f8301126142b757600080fd5b6142bf6141fb565b8060408401858111156142d157600080fd5b845b818110156142eb5780358452602093840193016142d3565b509095945050505050565b60006080828403121561430857600080fd5b6143106141fb565b905061431c83836142a6565b815261432b83604084016142a6565b602082015292915050565b600080600080610120858703121561434d57600080fd5b8435935061435e8660208701614276565b925061436d86606087016142f6565b915061437c8660e08701614276565b905092959194509250565b8035613bd78161419a565b6020808252825182820181905260009190848201906040850190845b818110156143d35783516001600160a01b0316835292840192918401916001016143ae565b50909695505050505050565b801515811461070357600080fd5b6000602082840312156143ff57600080fd5b8135613a2b816143df565b60ff8116811461070357600080fd5b60006020828403121561442b57600080fd5b8135613a2b8161440a565b803563ffffffff81168114613bd757600080fd5b60006001600160401b03821115614463576144636141e5565b5060051b60200190565b600082601f83011261447e57600080fd5b8135602061449361448e8361444a565b614246565b82815260059290921b840181019181810190868411156144b257600080fd5b8286015b848110156144d4576144c781614436565b83529183019183016144b6565b509695505050505050565b600082601f8301126144f057600080fd5b8135602061450061448e8361444a565b82815260069290921b8401810191818101908684111561451f57600080fd5b8286015b848110156144d4576145358882614276565b835291830191604001614523565b600082601f83011261455457600080fd5b8135602061456461448e8361444a565b82815260059290921b8401810191818101908684111561458357600080fd5b8286015b848110156144d45780356001600160401b038111156145a65760008081fd5b6145b48986838b010161446d565b845250918301918301614587565b600061018082840312156145d557600080fd5b6145dd614223565b905081356001600160401b03808211156145f657600080fd5b6146028583860161446d565b8352602084013591508082111561461857600080fd5b614624858386016144df565b6020840152604084013591508082111561463d57600080fd5b614649858386016144df565b604084015261465b85606086016142f6565b606084015261466d8560e08601614276565b608084015261012084013591508082111561468757600080fd5b6146938583860161446d565b60a08401526101408401359150808211156146ad57600080fd5b6146b98583860161446d565b60c08401526101608401359150808211156146d357600080fd5b506146e084828501614543565b60e08301525092915050565b60008060008060006080868803121561470457600080fd5b8535945060208601356001600160401b038082111561472257600080fd5b818801915088601f83011261473657600080fd5b81358181111561474557600080fd5b89602082850101111561475757600080fd5b602083019650945061476b60408901614436565b9350606088013591508082111561478157600080fd5b5061478e888289016145c2565b9150509295509295909350565b600081518084526020808501945080840160005b838110156147d45781516001600160601b0316875295820195908201906001016147af565b509495945050505050565b60408152600083516040808401526147fa608084018261479b565b90506020850151603f19848303016060850152614817828261479b565b925050508260208301529392505050565b600080600080600060a0868803121561484057600080fd5b853561484b8161419a565b9450602086810135945060408701356148638161419a565b935060608701356001600160401b0381111561487e57600080fd5b8701601f8101891361488f57600080fd5b803561489d61448e8261444a565b81815260059190911b8201830190838101908b8311156148bc57600080fd5b928401925b828410156148e35783356148d48161419a565b825292840192908401906148c1565b80965050505050506148f760808701614387565b90509295509295909350565b6000806040838503121561491657600080fd5b82356001600160401b038082111561492d57600080fd5b908401906080828703121561494157600080fd5b9092506020840135908082111561495757600080fd5b50614964858286016145c2565b9150509250929050565b6000815180845260005b8181101561499457602081850181015186830182015201614978565b818111156149a6576000602083870101525b50601f01601f19169290920160200192915050565b602081526000613a2b602083018461496e565b60006001600160401b038311156149e7576149e76141e5565b6149fa601f8401601f1916602001614246565b9050828152838383011115614a0e57600080fd5b828260208301376000602084830101529392505050565b600082601f830112614a3657600080fd5b613a2b838335602085016149ce565b60008060408385031215614a5857600080fd5b8235614a638161419a565b915060208301356001600160401b0380821115614a7f57600080fd5b9084019060608287031215614a9357600080fd5b604051606081018181108382111715614aae57614aae6141e5565b604052823582811115614ac057600080fd5b614acc88828601614a25565b82525060208301356020820152604083013560408201528093505050509250929050565b600060208284031215614b0257600080fd5b81356001600160401b03811115614b1857600080fd5b8201601f81018413614b2957600080fd5b614b38848235602084016149ce565b949350505050565b600060208284031215614b5257600080fd5b613a2b82614436565b60008060208385031215614b6e57600080fd5b82356001600160401b0380821115614b8557600080fd5b818501915085601f830112614b9957600080fd5b813581811115614ba857600080fd5b8660208260051b8501011115614bbd57600080fd5b60209290920196919550909350505050565b600060208284031215614be157600080fd5b8151613a2b8161419a565b6020808252602a908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526939903ab73830bab9b2b960b11b606082015260800190565b600060208284031215614c4857600080fd5b8151613a2b816143df565b60208082526028908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526739903830bab9b2b960c11b606082015260800190565b634e487b7160e01b600052603260045260246000fd5b600082614cce57634e487b7160e01b600052601260045260246000fd5b500690565b600060208284031215614ce557600080fd5b5051919050565b600060208284031215614cfe57600080fd5b81516001600160c01b0381168114613a2b57600080fd5b600060208284031215614d2757600080fd5b8151613a2b8161440a565b634e487b7160e01b600052601160045260246000fd5b60008219821115614d5b57614d5b614d32565b500190565b6000600019821415614d7457614d74614d32565b5060010190565b6001600160601b038116811461070357600080fd5b600060408284031215614da257600080fd5b614daa6141fb565b8251614db58161419a565b81526020830151614dc581614d7b565b60208201529392505050565b600082821015614de357614de3614d32565b500390565b600060208284031215614dfa57600080fd5b815167ffffffffffffffff1981168114613a2b57600080fd5b600060208284031215614e2557600080fd5b8151613a2b81614d7b565b60006001600160601b0383811690831681811015614e5057614e50614d32565b039392505050565b63ffffffff60e01b8360e01b1681526000600482018351602080860160005b83811015614e9357815185529382019390820190600101614e77565b5092979650505050505050565b600063ffffffff808316818516808303821115614ebf57614ebf614d32565b01949350505050565b6000808335601e19843603018112614edf57600080fd5b8301803591506001600160401b03821115614ef957600080fd5b602001915036819003821315614f0e57600080fd5b9250929050565b600060808236031215614f2757600080fd5b604051608081016001600160401b038282108183111715614f4a57614f4a6141e5565b81604052843583526020850135915080821115614f6657600080fd5b614f7236838701614a25565b60208401526040850135915080821115614f8b57600080fd5b50614f9836828601614a25565b604083015250614faa60608401614436565b606082015292915050565b60006001600160601b0380831681851681830481118215151615614fdb57614fdb614d32565b02949350505050565b6000816000190483118215151615614ffe57614ffe614d32565b500290565b60208082526052908201527f536572766963654d616e61676572426173652e6f6e6c7952656769737472794360408201527f6f6f7264696e61746f723a2063616c6c6572206973206e6f742074686520726560608201527133b4b9ba393c9031b7b7b93234b730ba37b960711b608082015260a00190565b60018060a01b03831681526040602082015260008251606060408401526150a560a084018261496e565b90506020840151606084015260408401516080840152809150509392505050565b60008235609e198336030181126150dc57600080fd5b9190910192915050565b8183526000602080850194508260005b858110156147d45781356151098161419a565b6001600160a01b031687528183013561512181614d7b565b6001600160601b03168784015260409687019691909101906001016150f6565b60208082528181018390526000906040808401600586901b8501820187855b8881101561524057878303603f190184528135368b9003609e1901811261518657600080fd5b8a0160a0813536839003601e1901811261519f57600080fd5b820180356001600160401b038111156151b757600080fd5b8060061b36038413156151c957600080fd5b8287526151db838801828c85016150e6565b925050506151ea888301614387565b6001600160a01b0316888601528187013587860152606061520c818401614436565b63ffffffff16908601526080615223838201614436565b63ffffffff16950194909452509285019290850190600101615160565b509098975050505050505050565b600061ffff8083168181141561526657615266614d32565b6001019392505050565b6000808335601e1984360301811261528757600080fd5b83016020810192503590506001600160401b038111156152a657600080fd5b803603831315614f0e57600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b602081528135602082015260006152f86020840184615270565b6080604085015261530d60a0850182846152b5565b91505061531d6040850185615270565b848303601f190160608601526153348382846152b5565b9250505063ffffffff61534960608601614436565b166080840152809150509291505056fe30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47456967656e4441536572766963654d616e616765722e636f6e6669726d426174424c535369676e6174757265436865636b65722e636865636b5369676e617475a26469706673582212207accc702a8f2096b87da1b6e1c551ac55954c11ae816097f3eda83aaad57196064736f6c634300080c0033", +} + +// ContractEigenDAServiceManagerABI is the input ABI used to generate the binding from. +// Deprecated: Use ContractEigenDAServiceManagerMetaData.ABI instead. +var ContractEigenDAServiceManagerABI = ContractEigenDAServiceManagerMetaData.ABI + +// ContractEigenDAServiceManagerBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ContractEigenDAServiceManagerMetaData.Bin instead. +var ContractEigenDAServiceManagerBin = ContractEigenDAServiceManagerMetaData.Bin + +// DeployContractEigenDAServiceManager deploys a new Ethereum contract, binding an instance of ContractEigenDAServiceManager to it. +func DeployContractEigenDAServiceManager(auth *bind.TransactOpts, backend bind.ContractBackend, __avsDirectory common.Address, __rewardsCoordinator common.Address, __registryCoordinator common.Address, __stakeRegistry common.Address) (common.Address, *types.Transaction, *ContractEigenDAServiceManager, error) { + parsed, err := ContractEigenDAServiceManagerMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ContractEigenDAServiceManagerBin), backend, __avsDirectory, __rewardsCoordinator, __registryCoordinator, __stakeRegistry) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ContractEigenDAServiceManager{ContractEigenDAServiceManagerCaller: ContractEigenDAServiceManagerCaller{contract: contract}, ContractEigenDAServiceManagerTransactor: ContractEigenDAServiceManagerTransactor{contract: contract}, ContractEigenDAServiceManagerFilterer: ContractEigenDAServiceManagerFilterer{contract: contract}}, nil +} + +// ContractEigenDAServiceManager is an auto generated Go binding around an Ethereum contract. +type ContractEigenDAServiceManager struct { + ContractEigenDAServiceManagerCaller // Read-only binding to the contract + ContractEigenDAServiceManagerTransactor // Write-only binding to the contract + ContractEigenDAServiceManagerFilterer // Log filterer for contract events +} + +// ContractEigenDAServiceManagerCaller is an auto generated read-only Go binding around an Ethereum contract. +type ContractEigenDAServiceManagerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ContractEigenDAServiceManagerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ContractEigenDAServiceManagerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ContractEigenDAServiceManagerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ContractEigenDAServiceManagerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ContractEigenDAServiceManagerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ContractEigenDAServiceManagerSession struct { + Contract *ContractEigenDAServiceManager // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ContractEigenDAServiceManagerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ContractEigenDAServiceManagerCallerSession struct { + Contract *ContractEigenDAServiceManagerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ContractEigenDAServiceManagerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ContractEigenDAServiceManagerTransactorSession struct { + Contract *ContractEigenDAServiceManagerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ContractEigenDAServiceManagerRaw is an auto generated low-level Go binding around an Ethereum contract. +type ContractEigenDAServiceManagerRaw struct { + Contract *ContractEigenDAServiceManager // Generic contract binding to access the raw methods on +} + +// ContractEigenDAServiceManagerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ContractEigenDAServiceManagerCallerRaw struct { + Contract *ContractEigenDAServiceManagerCaller // Generic read-only contract binding to access the raw methods on +} + +// ContractEigenDAServiceManagerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ContractEigenDAServiceManagerTransactorRaw struct { + Contract *ContractEigenDAServiceManagerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewContractEigenDAServiceManager creates a new instance of ContractEigenDAServiceManager, bound to a specific deployed contract. +func NewContractEigenDAServiceManager(address common.Address, backend bind.ContractBackend) (*ContractEigenDAServiceManager, error) { + contract, err := bindContractEigenDAServiceManager(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ContractEigenDAServiceManager{ContractEigenDAServiceManagerCaller: ContractEigenDAServiceManagerCaller{contract: contract}, ContractEigenDAServiceManagerTransactor: ContractEigenDAServiceManagerTransactor{contract: contract}, ContractEigenDAServiceManagerFilterer: ContractEigenDAServiceManagerFilterer{contract: contract}}, nil +} + +// NewContractEigenDAServiceManagerCaller creates a new read-only instance of ContractEigenDAServiceManager, bound to a specific deployed contract. +func NewContractEigenDAServiceManagerCaller(address common.Address, caller bind.ContractCaller) (*ContractEigenDAServiceManagerCaller, error) { + contract, err := bindContractEigenDAServiceManager(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ContractEigenDAServiceManagerCaller{contract: contract}, nil +} + +// NewContractEigenDAServiceManagerTransactor creates a new write-only instance of ContractEigenDAServiceManager, bound to a specific deployed contract. +func NewContractEigenDAServiceManagerTransactor(address common.Address, transactor bind.ContractTransactor) (*ContractEigenDAServiceManagerTransactor, error) { + contract, err := bindContractEigenDAServiceManager(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ContractEigenDAServiceManagerTransactor{contract: contract}, nil +} + +// NewContractEigenDAServiceManagerFilterer creates a new log filterer instance of ContractEigenDAServiceManager, bound to a specific deployed contract. +func NewContractEigenDAServiceManagerFilterer(address common.Address, filterer bind.ContractFilterer) (*ContractEigenDAServiceManagerFilterer, error) { + contract, err := bindContractEigenDAServiceManager(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ContractEigenDAServiceManagerFilterer{contract: contract}, nil +} + +// bindContractEigenDAServiceManager binds a generic wrapper to an already deployed contract. +func bindContractEigenDAServiceManager(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := ContractEigenDAServiceManagerMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ContractEigenDAServiceManager.Contract.ContractEigenDAServiceManagerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.ContractEigenDAServiceManagerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.ContractEigenDAServiceManagerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ContractEigenDAServiceManager.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.contract.Transact(opts, method, params...) +} + +// BLOCKSTALEMEASURE is a free data retrieval call binding the contract method 0x5e8b3f2d. +// +// Solidity: function BLOCK_STALE_MEASURE() view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) BLOCKSTALEMEASURE(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "BLOCK_STALE_MEASURE") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// BLOCKSTALEMEASURE is a free data retrieval call binding the contract method 0x5e8b3f2d. +// +// Solidity: function BLOCK_STALE_MEASURE() view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) BLOCKSTALEMEASURE() (uint32, error) { + return _ContractEigenDAServiceManager.Contract.BLOCKSTALEMEASURE(&_ContractEigenDAServiceManager.CallOpts) +} + +// BLOCKSTALEMEASURE is a free data retrieval call binding the contract method 0x5e8b3f2d. +// +// Solidity: function BLOCK_STALE_MEASURE() view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) BLOCKSTALEMEASURE() (uint32, error) { + return _ContractEigenDAServiceManager.Contract.BLOCKSTALEMEASURE(&_ContractEigenDAServiceManager.CallOpts) +} + +// STOREDURATIONBLOCKS is a free data retrieval call binding the contract method 0x5e033476. +// +// Solidity: function STORE_DURATION_BLOCKS() view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) STOREDURATIONBLOCKS(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "STORE_DURATION_BLOCKS") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// STOREDURATIONBLOCKS is a free data retrieval call binding the contract method 0x5e033476. +// +// Solidity: function STORE_DURATION_BLOCKS() view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) STOREDURATIONBLOCKS() (uint32, error) { + return _ContractEigenDAServiceManager.Contract.STOREDURATIONBLOCKS(&_ContractEigenDAServiceManager.CallOpts) +} + +// STOREDURATIONBLOCKS is a free data retrieval call binding the contract method 0x5e033476. +// +// Solidity: function STORE_DURATION_BLOCKS() view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) STOREDURATIONBLOCKS() (uint32, error) { + return _ContractEigenDAServiceManager.Contract.STOREDURATIONBLOCKS(&_ContractEigenDAServiceManager.CallOpts) +} + +// THRESHOLDDENOMINATOR is a free data retrieval call binding the contract method 0xef024458. +// +// Solidity: function THRESHOLD_DENOMINATOR() view returns(uint256) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) THRESHOLDDENOMINATOR(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "THRESHOLD_DENOMINATOR") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// THRESHOLDDENOMINATOR is a free data retrieval call binding the contract method 0xef024458. +// +// Solidity: function THRESHOLD_DENOMINATOR() view returns(uint256) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) THRESHOLDDENOMINATOR() (*big.Int, error) { + return _ContractEigenDAServiceManager.Contract.THRESHOLDDENOMINATOR(&_ContractEigenDAServiceManager.CallOpts) +} + +// THRESHOLDDENOMINATOR is a free data retrieval call binding the contract method 0xef024458. +// +// Solidity: function THRESHOLD_DENOMINATOR() view returns(uint256) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) THRESHOLDDENOMINATOR() (*big.Int, error) { + return _ContractEigenDAServiceManager.Contract.THRESHOLDDENOMINATOR(&_ContractEigenDAServiceManager.CallOpts) +} + +// AvsDirectory is a free data retrieval call binding the contract method 0x6b3aa72e. +// +// Solidity: function avsDirectory() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) AvsDirectory(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "avsDirectory") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// AvsDirectory is a free data retrieval call binding the contract method 0x6b3aa72e. +// +// Solidity: function avsDirectory() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) AvsDirectory() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.AvsDirectory(&_ContractEigenDAServiceManager.CallOpts) +} + +// AvsDirectory is a free data retrieval call binding the contract method 0x6b3aa72e. +// +// Solidity: function avsDirectory() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) AvsDirectory() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.AvsDirectory(&_ContractEigenDAServiceManager.CallOpts) +} + +// BatchId is a free data retrieval call binding the contract method 0x4972134a. +// +// Solidity: function batchId() view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) BatchId(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "batchId") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// BatchId is a free data retrieval call binding the contract method 0x4972134a. +// +// Solidity: function batchId() view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) BatchId() (uint32, error) { + return _ContractEigenDAServiceManager.Contract.BatchId(&_ContractEigenDAServiceManager.CallOpts) +} + +// BatchId is a free data retrieval call binding the contract method 0x4972134a. +// +// Solidity: function batchId() view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) BatchId() (uint32, error) { + return _ContractEigenDAServiceManager.Contract.BatchId(&_ContractEigenDAServiceManager.CallOpts) +} + +// BatchIdToBatchMetadataHash is a free data retrieval call binding the contract method 0xeccbbfc9. +// +// Solidity: function batchIdToBatchMetadataHash(uint32 ) view returns(bytes32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) BatchIdToBatchMetadataHash(opts *bind.CallOpts, arg0 uint32) ([32]byte, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "batchIdToBatchMetadataHash", arg0) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// BatchIdToBatchMetadataHash is a free data retrieval call binding the contract method 0xeccbbfc9. +// +// Solidity: function batchIdToBatchMetadataHash(uint32 ) view returns(bytes32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) BatchIdToBatchMetadataHash(arg0 uint32) ([32]byte, error) { + return _ContractEigenDAServiceManager.Contract.BatchIdToBatchMetadataHash(&_ContractEigenDAServiceManager.CallOpts, arg0) +} + +// BatchIdToBatchMetadataHash is a free data retrieval call binding the contract method 0xeccbbfc9. +// +// Solidity: function batchIdToBatchMetadataHash(uint32 ) view returns(bytes32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) BatchIdToBatchMetadataHash(arg0 uint32) ([32]byte, error) { + return _ContractEigenDAServiceManager.Contract.BatchIdToBatchMetadataHash(&_ContractEigenDAServiceManager.CallOpts, arg0) +} + +// BlsApkRegistry is a free data retrieval call binding the contract method 0x5df45946. +// +// Solidity: function blsApkRegistry() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) BlsApkRegistry(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "blsApkRegistry") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// BlsApkRegistry is a free data retrieval call binding the contract method 0x5df45946. +// +// Solidity: function blsApkRegistry() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) BlsApkRegistry() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.BlsApkRegistry(&_ContractEigenDAServiceManager.CallOpts) +} + +// BlsApkRegistry is a free data retrieval call binding the contract method 0x5df45946. +// +// Solidity: function blsApkRegistry() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) BlsApkRegistry() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.BlsApkRegistry(&_ContractEigenDAServiceManager.CallOpts) +} + +// CheckSignatures is a free data retrieval call binding the contract method 0x6efb4636. +// +// Solidity: function checkSignatures(bytes32 msgHash, bytes quorumNumbers, uint32 referenceBlockNumber, (uint32[],(uint256,uint256)[],(uint256,uint256)[],(uint256[2],uint256[2]),(uint256,uint256),uint32[],uint32[],uint32[][]) params) view returns((uint96[],uint96[]), bytes32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) CheckSignatures(opts *bind.CallOpts, msgHash [32]byte, quorumNumbers []byte, referenceBlockNumber uint32, params IBLSSignatureCheckerNonSignerStakesAndSignature) (IBLSSignatureCheckerQuorumStakeTotals, [32]byte, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "checkSignatures", msgHash, quorumNumbers, referenceBlockNumber, params) + + if err != nil { + return *new(IBLSSignatureCheckerQuorumStakeTotals), *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new(IBLSSignatureCheckerQuorumStakeTotals)).(*IBLSSignatureCheckerQuorumStakeTotals) + out1 := *abi.ConvertType(out[1], new([32]byte)).(*[32]byte) + + return out0, out1, err + +} + +// CheckSignatures is a free data retrieval call binding the contract method 0x6efb4636. +// +// Solidity: function checkSignatures(bytes32 msgHash, bytes quorumNumbers, uint32 referenceBlockNumber, (uint32[],(uint256,uint256)[],(uint256,uint256)[],(uint256[2],uint256[2]),(uint256,uint256),uint32[],uint32[],uint32[][]) params) view returns((uint96[],uint96[]), bytes32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) CheckSignatures(msgHash [32]byte, quorumNumbers []byte, referenceBlockNumber uint32, params IBLSSignatureCheckerNonSignerStakesAndSignature) (IBLSSignatureCheckerQuorumStakeTotals, [32]byte, error) { + return _ContractEigenDAServiceManager.Contract.CheckSignatures(&_ContractEigenDAServiceManager.CallOpts, msgHash, quorumNumbers, referenceBlockNumber, params) +} + +// CheckSignatures is a free data retrieval call binding the contract method 0x6efb4636. +// +// Solidity: function checkSignatures(bytes32 msgHash, bytes quorumNumbers, uint32 referenceBlockNumber, (uint32[],(uint256,uint256)[],(uint256,uint256)[],(uint256[2],uint256[2]),(uint256,uint256),uint32[],uint32[],uint32[][]) params) view returns((uint96[],uint96[]), bytes32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) CheckSignatures(msgHash [32]byte, quorumNumbers []byte, referenceBlockNumber uint32, params IBLSSignatureCheckerNonSignerStakesAndSignature) (IBLSSignatureCheckerQuorumStakeTotals, [32]byte, error) { + return _ContractEigenDAServiceManager.Contract.CheckSignatures(&_ContractEigenDAServiceManager.CallOpts, msgHash, quorumNumbers, referenceBlockNumber, params) +} + +// Delegation is a free data retrieval call binding the contract method 0xdf5cf723. +// +// Solidity: function delegation() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) Delegation(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "delegation") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Delegation is a free data retrieval call binding the contract method 0xdf5cf723. +// +// Solidity: function delegation() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) Delegation() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.Delegation(&_ContractEigenDAServiceManager.CallOpts) +} + +// Delegation is a free data retrieval call binding the contract method 0xdf5cf723. +// +// Solidity: function delegation() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) Delegation() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.Delegation(&_ContractEigenDAServiceManager.CallOpts) +} + +// GetOperatorRestakedStrategies is a free data retrieval call binding the contract method 0x33cfb7b7. +// +// Solidity: function getOperatorRestakedStrategies(address operator) view returns(address[]) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) GetOperatorRestakedStrategies(opts *bind.CallOpts, operator common.Address) ([]common.Address, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "getOperatorRestakedStrategies", operator) + + if err != nil { + return *new([]common.Address), err + } + + out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) + + return out0, err + +} + +// GetOperatorRestakedStrategies is a free data retrieval call binding the contract method 0x33cfb7b7. +// +// Solidity: function getOperatorRestakedStrategies(address operator) view returns(address[]) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) GetOperatorRestakedStrategies(operator common.Address) ([]common.Address, error) { + return _ContractEigenDAServiceManager.Contract.GetOperatorRestakedStrategies(&_ContractEigenDAServiceManager.CallOpts, operator) +} + +// GetOperatorRestakedStrategies is a free data retrieval call binding the contract method 0x33cfb7b7. +// +// Solidity: function getOperatorRestakedStrategies(address operator) view returns(address[]) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) GetOperatorRestakedStrategies(operator common.Address) ([]common.Address, error) { + return _ContractEigenDAServiceManager.Contract.GetOperatorRestakedStrategies(&_ContractEigenDAServiceManager.CallOpts, operator) +} + +// GetRestakeableStrategies is a free data retrieval call binding the contract method 0xe481af9d. +// +// Solidity: function getRestakeableStrategies() view returns(address[]) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) GetRestakeableStrategies(opts *bind.CallOpts) ([]common.Address, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "getRestakeableStrategies") + + if err != nil { + return *new([]common.Address), err + } + + out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) + + return out0, err + +} + +// GetRestakeableStrategies is a free data retrieval call binding the contract method 0xe481af9d. +// +// Solidity: function getRestakeableStrategies() view returns(address[]) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) GetRestakeableStrategies() ([]common.Address, error) { + return _ContractEigenDAServiceManager.Contract.GetRestakeableStrategies(&_ContractEigenDAServiceManager.CallOpts) +} + +// GetRestakeableStrategies is a free data retrieval call binding the contract method 0xe481af9d. +// +// Solidity: function getRestakeableStrategies() view returns(address[]) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) GetRestakeableStrategies() ([]common.Address, error) { + return _ContractEigenDAServiceManager.Contract.GetRestakeableStrategies(&_ContractEigenDAServiceManager.CallOpts) +} + +// IsBatchConfirmer is a free data retrieval call binding the contract method 0xa5b7890a. +// +// Solidity: function isBatchConfirmer(address ) view returns(bool) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) IsBatchConfirmer(opts *bind.CallOpts, arg0 common.Address) (bool, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "isBatchConfirmer", arg0) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsBatchConfirmer is a free data retrieval call binding the contract method 0xa5b7890a. +// +// Solidity: function isBatchConfirmer(address ) view returns(bool) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) IsBatchConfirmer(arg0 common.Address) (bool, error) { + return _ContractEigenDAServiceManager.Contract.IsBatchConfirmer(&_ContractEigenDAServiceManager.CallOpts, arg0) +} + +// IsBatchConfirmer is a free data retrieval call binding the contract method 0xa5b7890a. +// +// Solidity: function isBatchConfirmer(address ) view returns(bool) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) IsBatchConfirmer(arg0 common.Address) (bool, error) { + return _ContractEigenDAServiceManager.Contract.IsBatchConfirmer(&_ContractEigenDAServiceManager.CallOpts, arg0) +} + +// LatestServeUntilBlock is a free data retrieval call binding the contract method 0xeaefd27d. +// +// Solidity: function latestServeUntilBlock(uint32 referenceBlockNumber) view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) LatestServeUntilBlock(opts *bind.CallOpts, referenceBlockNumber uint32) (uint32, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "latestServeUntilBlock", referenceBlockNumber) + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// LatestServeUntilBlock is a free data retrieval call binding the contract method 0xeaefd27d. +// +// Solidity: function latestServeUntilBlock(uint32 referenceBlockNumber) view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) LatestServeUntilBlock(referenceBlockNumber uint32) (uint32, error) { + return _ContractEigenDAServiceManager.Contract.LatestServeUntilBlock(&_ContractEigenDAServiceManager.CallOpts, referenceBlockNumber) +} + +// LatestServeUntilBlock is a free data retrieval call binding the contract method 0xeaefd27d. +// +// Solidity: function latestServeUntilBlock(uint32 referenceBlockNumber) view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) LatestServeUntilBlock(referenceBlockNumber uint32) (uint32, error) { + return _ContractEigenDAServiceManager.Contract.LatestServeUntilBlock(&_ContractEigenDAServiceManager.CallOpts, referenceBlockNumber) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) Owner() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.Owner(&_ContractEigenDAServiceManager.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) Owner() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.Owner(&_ContractEigenDAServiceManager.CallOpts) +} + +// Paused is a free data retrieval call binding the contract method 0x5ac86ab7. +// +// Solidity: function paused(uint8 index) view returns(bool) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) Paused(opts *bind.CallOpts, index uint8) (bool, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "paused", index) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// Paused is a free data retrieval call binding the contract method 0x5ac86ab7. +// +// Solidity: function paused(uint8 index) view returns(bool) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) Paused(index uint8) (bool, error) { + return _ContractEigenDAServiceManager.Contract.Paused(&_ContractEigenDAServiceManager.CallOpts, index) +} + +// Paused is a free data retrieval call binding the contract method 0x5ac86ab7. +// +// Solidity: function paused(uint8 index) view returns(bool) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) Paused(index uint8) (bool, error) { + return _ContractEigenDAServiceManager.Contract.Paused(&_ContractEigenDAServiceManager.CallOpts, index) +} + +// Paused0 is a free data retrieval call binding the contract method 0x5c975abb. +// +// Solidity: function paused() view returns(uint256) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) Paused0(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "paused0") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Paused0 is a free data retrieval call binding the contract method 0x5c975abb. +// +// Solidity: function paused() view returns(uint256) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) Paused0() (*big.Int, error) { + return _ContractEigenDAServiceManager.Contract.Paused0(&_ContractEigenDAServiceManager.CallOpts) +} + +// Paused0 is a free data retrieval call binding the contract method 0x5c975abb. +// +// Solidity: function paused() view returns(uint256) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) Paused0() (*big.Int, error) { + return _ContractEigenDAServiceManager.Contract.Paused0(&_ContractEigenDAServiceManager.CallOpts) +} + +// PauserRegistry is a free data retrieval call binding the contract method 0x886f1195. +// +// Solidity: function pauserRegistry() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) PauserRegistry(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "pauserRegistry") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// PauserRegistry is a free data retrieval call binding the contract method 0x886f1195. +// +// Solidity: function pauserRegistry() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) PauserRegistry() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.PauserRegistry(&_ContractEigenDAServiceManager.CallOpts) +} + +// PauserRegistry is a free data retrieval call binding the contract method 0x886f1195. +// +// Solidity: function pauserRegistry() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) PauserRegistry() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.PauserRegistry(&_ContractEigenDAServiceManager.CallOpts) +} + +// QuorumAdversaryThresholdPercentages is a free data retrieval call binding the contract method 0x8687feae. +// +// Solidity: function quorumAdversaryThresholdPercentages() view returns(bytes) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) QuorumAdversaryThresholdPercentages(opts *bind.CallOpts) ([]byte, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "quorumAdversaryThresholdPercentages") + + if err != nil { + return *new([]byte), err + } + + out0 := *abi.ConvertType(out[0], new([]byte)).(*[]byte) + + return out0, err + +} + +// QuorumAdversaryThresholdPercentages is a free data retrieval call binding the contract method 0x8687feae. +// +// Solidity: function quorumAdversaryThresholdPercentages() view returns(bytes) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) QuorumAdversaryThresholdPercentages() ([]byte, error) { + return _ContractEigenDAServiceManager.Contract.QuorumAdversaryThresholdPercentages(&_ContractEigenDAServiceManager.CallOpts) +} + +// QuorumAdversaryThresholdPercentages is a free data retrieval call binding the contract method 0x8687feae. +// +// Solidity: function quorumAdversaryThresholdPercentages() view returns(bytes) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) QuorumAdversaryThresholdPercentages() ([]byte, error) { + return _ContractEigenDAServiceManager.Contract.QuorumAdversaryThresholdPercentages(&_ContractEigenDAServiceManager.CallOpts) +} + +// QuorumConfirmationThresholdPercentages is a free data retrieval call binding the contract method 0xbafa9107. +// +// Solidity: function quorumConfirmationThresholdPercentages() view returns(bytes) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) QuorumConfirmationThresholdPercentages(opts *bind.CallOpts) ([]byte, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "quorumConfirmationThresholdPercentages") + + if err != nil { + return *new([]byte), err + } + + out0 := *abi.ConvertType(out[0], new([]byte)).(*[]byte) + + return out0, err + +} + +// QuorumConfirmationThresholdPercentages is a free data retrieval call binding the contract method 0xbafa9107. +// +// Solidity: function quorumConfirmationThresholdPercentages() view returns(bytes) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) QuorumConfirmationThresholdPercentages() ([]byte, error) { + return _ContractEigenDAServiceManager.Contract.QuorumConfirmationThresholdPercentages(&_ContractEigenDAServiceManager.CallOpts) +} + +// QuorumConfirmationThresholdPercentages is a free data retrieval call binding the contract method 0xbafa9107. +// +// Solidity: function quorumConfirmationThresholdPercentages() view returns(bytes) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) QuorumConfirmationThresholdPercentages() ([]byte, error) { + return _ContractEigenDAServiceManager.Contract.QuorumConfirmationThresholdPercentages(&_ContractEigenDAServiceManager.CallOpts) +} + +// QuorumNumbersRequired is a free data retrieval call binding the contract method 0xe15234ff. +// +// Solidity: function quorumNumbersRequired() view returns(bytes) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) QuorumNumbersRequired(opts *bind.CallOpts) ([]byte, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "quorumNumbersRequired") + + if err != nil { + return *new([]byte), err + } + + out0 := *abi.ConvertType(out[0], new([]byte)).(*[]byte) + + return out0, err + +} + +// QuorumNumbersRequired is a free data retrieval call binding the contract method 0xe15234ff. +// +// Solidity: function quorumNumbersRequired() view returns(bytes) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) QuorumNumbersRequired() ([]byte, error) { + return _ContractEigenDAServiceManager.Contract.QuorumNumbersRequired(&_ContractEigenDAServiceManager.CallOpts) +} + +// QuorumNumbersRequired is a free data retrieval call binding the contract method 0xe15234ff. +// +// Solidity: function quorumNumbersRequired() view returns(bytes) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) QuorumNumbersRequired() ([]byte, error) { + return _ContractEigenDAServiceManager.Contract.QuorumNumbersRequired(&_ContractEigenDAServiceManager.CallOpts) +} + +// RegistryCoordinator is a free data retrieval call binding the contract method 0x6d14a987. +// +// Solidity: function registryCoordinator() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) RegistryCoordinator(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "registryCoordinator") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// RegistryCoordinator is a free data retrieval call binding the contract method 0x6d14a987. +// +// Solidity: function registryCoordinator() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) RegistryCoordinator() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.RegistryCoordinator(&_ContractEigenDAServiceManager.CallOpts) +} + +// RegistryCoordinator is a free data retrieval call binding the contract method 0x6d14a987. +// +// Solidity: function registryCoordinator() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) RegistryCoordinator() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.RegistryCoordinator(&_ContractEigenDAServiceManager.CallOpts) +} + +// RewardsInitiator is a free data retrieval call binding the contract method 0xfc299dee. +// +// Solidity: function rewardsInitiator() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) RewardsInitiator(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "rewardsInitiator") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// RewardsInitiator is a free data retrieval call binding the contract method 0xfc299dee. +// +// Solidity: function rewardsInitiator() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) RewardsInitiator() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.RewardsInitiator(&_ContractEigenDAServiceManager.CallOpts) +} + +// RewardsInitiator is a free data retrieval call binding the contract method 0xfc299dee. +// +// Solidity: function rewardsInitiator() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) RewardsInitiator() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.RewardsInitiator(&_ContractEigenDAServiceManager.CallOpts) +} + +// StakeRegistry is a free data retrieval call binding the contract method 0x68304835. +// +// Solidity: function stakeRegistry() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) StakeRegistry(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "stakeRegistry") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// StakeRegistry is a free data retrieval call binding the contract method 0x68304835. +// +// Solidity: function stakeRegistry() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) StakeRegistry() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.StakeRegistry(&_ContractEigenDAServiceManager.CallOpts) +} + +// StakeRegistry is a free data retrieval call binding the contract method 0x68304835. +// +// Solidity: function stakeRegistry() view returns(address) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) StakeRegistry() (common.Address, error) { + return _ContractEigenDAServiceManager.Contract.StakeRegistry(&_ContractEigenDAServiceManager.CallOpts) +} + +// StaleStakesForbidden is a free data retrieval call binding the contract method 0xb98d0908. +// +// Solidity: function staleStakesForbidden() view returns(bool) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) StaleStakesForbidden(opts *bind.CallOpts) (bool, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "staleStakesForbidden") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// StaleStakesForbidden is a free data retrieval call binding the contract method 0xb98d0908. +// +// Solidity: function staleStakesForbidden() view returns(bool) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) StaleStakesForbidden() (bool, error) { + return _ContractEigenDAServiceManager.Contract.StaleStakesForbidden(&_ContractEigenDAServiceManager.CallOpts) +} + +// StaleStakesForbidden is a free data retrieval call binding the contract method 0xb98d0908. +// +// Solidity: function staleStakesForbidden() view returns(bool) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) StaleStakesForbidden() (bool, error) { + return _ContractEigenDAServiceManager.Contract.StaleStakesForbidden(&_ContractEigenDAServiceManager.CallOpts) +} + +// TaskNumber is a free data retrieval call binding the contract method 0x72d18e8d. +// +// Solidity: function taskNumber() view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) TaskNumber(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "taskNumber") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// TaskNumber is a free data retrieval call binding the contract method 0x72d18e8d. +// +// Solidity: function taskNumber() view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) TaskNumber() (uint32, error) { + return _ContractEigenDAServiceManager.Contract.TaskNumber(&_ContractEigenDAServiceManager.CallOpts) +} + +// TaskNumber is a free data retrieval call binding the contract method 0x72d18e8d. +// +// Solidity: function taskNumber() view returns(uint32) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) TaskNumber() (uint32, error) { + return _ContractEigenDAServiceManager.Contract.TaskNumber(&_ContractEigenDAServiceManager.CallOpts) +} + +// TrySignatureAndApkVerification is a free data retrieval call binding the contract method 0x171f1d5b. +// +// Solidity: function trySignatureAndApkVerification(bytes32 msgHash, (uint256,uint256) apk, (uint256[2],uint256[2]) apkG2, (uint256,uint256) sigma) view returns(bool pairingSuccessful, bool siganatureIsValid) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCaller) TrySignatureAndApkVerification(opts *bind.CallOpts, msgHash [32]byte, apk BN254G1Point, apkG2 BN254G2Point, sigma BN254G1Point) (struct { + PairingSuccessful bool + SiganatureIsValid bool +}, error) { + var out []interface{} + err := _ContractEigenDAServiceManager.contract.Call(opts, &out, "trySignatureAndApkVerification", msgHash, apk, apkG2, sigma) + + outstruct := new(struct { + PairingSuccessful bool + SiganatureIsValid bool + }) + if err != nil { + return *outstruct, err + } + + outstruct.PairingSuccessful = *abi.ConvertType(out[0], new(bool)).(*bool) + outstruct.SiganatureIsValid = *abi.ConvertType(out[1], new(bool)).(*bool) + + return *outstruct, err + +} + +// TrySignatureAndApkVerification is a free data retrieval call binding the contract method 0x171f1d5b. +// +// Solidity: function trySignatureAndApkVerification(bytes32 msgHash, (uint256,uint256) apk, (uint256[2],uint256[2]) apkG2, (uint256,uint256) sigma) view returns(bool pairingSuccessful, bool siganatureIsValid) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) TrySignatureAndApkVerification(msgHash [32]byte, apk BN254G1Point, apkG2 BN254G2Point, sigma BN254G1Point) (struct { + PairingSuccessful bool + SiganatureIsValid bool +}, error) { + return _ContractEigenDAServiceManager.Contract.TrySignatureAndApkVerification(&_ContractEigenDAServiceManager.CallOpts, msgHash, apk, apkG2, sigma) +} + +// TrySignatureAndApkVerification is a free data retrieval call binding the contract method 0x171f1d5b. +// +// Solidity: function trySignatureAndApkVerification(bytes32 msgHash, (uint256,uint256) apk, (uint256[2],uint256[2]) apkG2, (uint256,uint256) sigma) view returns(bool pairingSuccessful, bool siganatureIsValid) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerCallerSession) TrySignatureAndApkVerification(msgHash [32]byte, apk BN254G1Point, apkG2 BN254G2Point, sigma BN254G1Point) (struct { + PairingSuccessful bool + SiganatureIsValid bool +}, error) { + return _ContractEigenDAServiceManager.Contract.TrySignatureAndApkVerification(&_ContractEigenDAServiceManager.CallOpts, msgHash, apk, apkG2, sigma) +} + +// ConfirmBatch is a paid mutator transaction binding the contract method 0x7794965a. +// +// Solidity: function confirmBatch((bytes32,bytes,bytes,uint32) batchHeader, (uint32[],(uint256,uint256)[],(uint256,uint256)[],(uint256[2],uint256[2]),(uint256,uint256),uint32[],uint32[],uint32[][]) nonSignerStakesAndSignature) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) ConfirmBatch(opts *bind.TransactOpts, batchHeader IEigenDAServiceManagerBatchHeader, nonSignerStakesAndSignature IBLSSignatureCheckerNonSignerStakesAndSignature) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "confirmBatch", batchHeader, nonSignerStakesAndSignature) +} + +// ConfirmBatch is a paid mutator transaction binding the contract method 0x7794965a. +// +// Solidity: function confirmBatch((bytes32,bytes,bytes,uint32) batchHeader, (uint32[],(uint256,uint256)[],(uint256,uint256)[],(uint256[2],uint256[2]),(uint256,uint256),uint32[],uint32[],uint32[][]) nonSignerStakesAndSignature) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) ConfirmBatch(batchHeader IEigenDAServiceManagerBatchHeader, nonSignerStakesAndSignature IBLSSignatureCheckerNonSignerStakesAndSignature) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.ConfirmBatch(&_ContractEigenDAServiceManager.TransactOpts, batchHeader, nonSignerStakesAndSignature) +} + +// ConfirmBatch is a paid mutator transaction binding the contract method 0x7794965a. +// +// Solidity: function confirmBatch((bytes32,bytes,bytes,uint32) batchHeader, (uint32[],(uint256,uint256)[],(uint256,uint256)[],(uint256[2],uint256[2]),(uint256,uint256),uint32[],uint32[],uint32[][]) nonSignerStakesAndSignature) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) ConfirmBatch(batchHeader IEigenDAServiceManagerBatchHeader, nonSignerStakesAndSignature IBLSSignatureCheckerNonSignerStakesAndSignature) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.ConfirmBatch(&_ContractEigenDAServiceManager.TransactOpts, batchHeader, nonSignerStakesAndSignature) +} + +// CreateAVSRewardsSubmission is a paid mutator transaction binding the contract method 0xfce36c7d. +// +// Solidity: function createAVSRewardsSubmission(((address,uint96)[],address,uint256,uint32,uint32)[] rewardsSubmissions) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) CreateAVSRewardsSubmission(opts *bind.TransactOpts, rewardsSubmissions []IRewardsCoordinatorRewardsSubmission) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "createAVSRewardsSubmission", rewardsSubmissions) +} + +// CreateAVSRewardsSubmission is a paid mutator transaction binding the contract method 0xfce36c7d. +// +// Solidity: function createAVSRewardsSubmission(((address,uint96)[],address,uint256,uint32,uint32)[] rewardsSubmissions) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) CreateAVSRewardsSubmission(rewardsSubmissions []IRewardsCoordinatorRewardsSubmission) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.CreateAVSRewardsSubmission(&_ContractEigenDAServiceManager.TransactOpts, rewardsSubmissions) +} + +// CreateAVSRewardsSubmission is a paid mutator transaction binding the contract method 0xfce36c7d. +// +// Solidity: function createAVSRewardsSubmission(((address,uint96)[],address,uint256,uint32,uint32)[] rewardsSubmissions) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) CreateAVSRewardsSubmission(rewardsSubmissions []IRewardsCoordinatorRewardsSubmission) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.CreateAVSRewardsSubmission(&_ContractEigenDAServiceManager.TransactOpts, rewardsSubmissions) +} + +// DeregisterOperatorFromAVS is a paid mutator transaction binding the contract method 0xa364f4da. +// +// Solidity: function deregisterOperatorFromAVS(address operator) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) DeregisterOperatorFromAVS(opts *bind.TransactOpts, operator common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "deregisterOperatorFromAVS", operator) +} + +// DeregisterOperatorFromAVS is a paid mutator transaction binding the contract method 0xa364f4da. +// +// Solidity: function deregisterOperatorFromAVS(address operator) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) DeregisterOperatorFromAVS(operator common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.DeregisterOperatorFromAVS(&_ContractEigenDAServiceManager.TransactOpts, operator) +} + +// DeregisterOperatorFromAVS is a paid mutator transaction binding the contract method 0xa364f4da. +// +// Solidity: function deregisterOperatorFromAVS(address operator) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) DeregisterOperatorFromAVS(operator common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.DeregisterOperatorFromAVS(&_ContractEigenDAServiceManager.TransactOpts, operator) +} + +// Initialize is a paid mutator transaction binding the contract method 0x775bbcb5. +// +// Solidity: function initialize(address _pauserRegistry, uint256 _initialPausedStatus, address _initialOwner, address[] _batchConfirmers, address _rewardsInitiator) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) Initialize(opts *bind.TransactOpts, _pauserRegistry common.Address, _initialPausedStatus *big.Int, _initialOwner common.Address, _batchConfirmers []common.Address, _rewardsInitiator common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "initialize", _pauserRegistry, _initialPausedStatus, _initialOwner, _batchConfirmers, _rewardsInitiator) +} + +// Initialize is a paid mutator transaction binding the contract method 0x775bbcb5. +// +// Solidity: function initialize(address _pauserRegistry, uint256 _initialPausedStatus, address _initialOwner, address[] _batchConfirmers, address _rewardsInitiator) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) Initialize(_pauserRegistry common.Address, _initialPausedStatus *big.Int, _initialOwner common.Address, _batchConfirmers []common.Address, _rewardsInitiator common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.Initialize(&_ContractEigenDAServiceManager.TransactOpts, _pauserRegistry, _initialPausedStatus, _initialOwner, _batchConfirmers, _rewardsInitiator) +} + +// Initialize is a paid mutator transaction binding the contract method 0x775bbcb5. +// +// Solidity: function initialize(address _pauserRegistry, uint256 _initialPausedStatus, address _initialOwner, address[] _batchConfirmers, address _rewardsInitiator) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) Initialize(_pauserRegistry common.Address, _initialPausedStatus *big.Int, _initialOwner common.Address, _batchConfirmers []common.Address, _rewardsInitiator common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.Initialize(&_ContractEigenDAServiceManager.TransactOpts, _pauserRegistry, _initialPausedStatus, _initialOwner, _batchConfirmers, _rewardsInitiator) +} + +// Pause is a paid mutator transaction binding the contract method 0x136439dd. +// +// Solidity: function pause(uint256 newPausedStatus) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) Pause(opts *bind.TransactOpts, newPausedStatus *big.Int) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "pause", newPausedStatus) +} + +// Pause is a paid mutator transaction binding the contract method 0x136439dd. +// +// Solidity: function pause(uint256 newPausedStatus) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) Pause(newPausedStatus *big.Int) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.Pause(&_ContractEigenDAServiceManager.TransactOpts, newPausedStatus) +} + +// Pause is a paid mutator transaction binding the contract method 0x136439dd. +// +// Solidity: function pause(uint256 newPausedStatus) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) Pause(newPausedStatus *big.Int) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.Pause(&_ContractEigenDAServiceManager.TransactOpts, newPausedStatus) +} + +// PauseAll is a paid mutator transaction binding the contract method 0x595c6a67. +// +// Solidity: function pauseAll() returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) PauseAll(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "pauseAll") +} + +// PauseAll is a paid mutator transaction binding the contract method 0x595c6a67. +// +// Solidity: function pauseAll() returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) PauseAll() (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.PauseAll(&_ContractEigenDAServiceManager.TransactOpts) +} + +// PauseAll is a paid mutator transaction binding the contract method 0x595c6a67. +// +// Solidity: function pauseAll() returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) PauseAll() (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.PauseAll(&_ContractEigenDAServiceManager.TransactOpts) +} + +// RegisterOperatorToAVS is a paid mutator transaction binding the contract method 0x9926ee7d. +// +// Solidity: function registerOperatorToAVS(address operator, (bytes,bytes32,uint256) operatorSignature) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) RegisterOperatorToAVS(opts *bind.TransactOpts, operator common.Address, operatorSignature ISignatureUtilsSignatureWithSaltAndExpiry) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "registerOperatorToAVS", operator, operatorSignature) +} + +// RegisterOperatorToAVS is a paid mutator transaction binding the contract method 0x9926ee7d. +// +// Solidity: function registerOperatorToAVS(address operator, (bytes,bytes32,uint256) operatorSignature) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) RegisterOperatorToAVS(operator common.Address, operatorSignature ISignatureUtilsSignatureWithSaltAndExpiry) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.RegisterOperatorToAVS(&_ContractEigenDAServiceManager.TransactOpts, operator, operatorSignature) +} + +// RegisterOperatorToAVS is a paid mutator transaction binding the contract method 0x9926ee7d. +// +// Solidity: function registerOperatorToAVS(address operator, (bytes,bytes32,uint256) operatorSignature) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) RegisterOperatorToAVS(operator common.Address, operatorSignature ISignatureUtilsSignatureWithSaltAndExpiry) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.RegisterOperatorToAVS(&_ContractEigenDAServiceManager.TransactOpts, operator, operatorSignature) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) RenounceOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "renounceOwnership") +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) RenounceOwnership() (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.RenounceOwnership(&_ContractEigenDAServiceManager.TransactOpts) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) RenounceOwnership() (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.RenounceOwnership(&_ContractEigenDAServiceManager.TransactOpts) +} + +// SetBatchConfirmer is a paid mutator transaction binding the contract method 0xf1220983. +// +// Solidity: function setBatchConfirmer(address _batchConfirmer) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) SetBatchConfirmer(opts *bind.TransactOpts, _batchConfirmer common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "setBatchConfirmer", _batchConfirmer) +} + +// SetBatchConfirmer is a paid mutator transaction binding the contract method 0xf1220983. +// +// Solidity: function setBatchConfirmer(address _batchConfirmer) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) SetBatchConfirmer(_batchConfirmer common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.SetBatchConfirmer(&_ContractEigenDAServiceManager.TransactOpts, _batchConfirmer) +} + +// SetBatchConfirmer is a paid mutator transaction binding the contract method 0xf1220983. +// +// Solidity: function setBatchConfirmer(address _batchConfirmer) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) SetBatchConfirmer(_batchConfirmer common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.SetBatchConfirmer(&_ContractEigenDAServiceManager.TransactOpts, _batchConfirmer) +} + +// SetPauserRegistry is a paid mutator transaction binding the contract method 0x10d67a2f. +// +// Solidity: function setPauserRegistry(address newPauserRegistry) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) SetPauserRegistry(opts *bind.TransactOpts, newPauserRegistry common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "setPauserRegistry", newPauserRegistry) +} + +// SetPauserRegistry is a paid mutator transaction binding the contract method 0x10d67a2f. +// +// Solidity: function setPauserRegistry(address newPauserRegistry) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) SetPauserRegistry(newPauserRegistry common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.SetPauserRegistry(&_ContractEigenDAServiceManager.TransactOpts, newPauserRegistry) +} + +// SetPauserRegistry is a paid mutator transaction binding the contract method 0x10d67a2f. +// +// Solidity: function setPauserRegistry(address newPauserRegistry) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) SetPauserRegistry(newPauserRegistry common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.SetPauserRegistry(&_ContractEigenDAServiceManager.TransactOpts, newPauserRegistry) +} + +// SetRewardsInitiator is a paid mutator transaction binding the contract method 0x3bc28c8c. +// +// Solidity: function setRewardsInitiator(address newRewardsInitiator) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) SetRewardsInitiator(opts *bind.TransactOpts, newRewardsInitiator common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "setRewardsInitiator", newRewardsInitiator) +} + +// SetRewardsInitiator is a paid mutator transaction binding the contract method 0x3bc28c8c. +// +// Solidity: function setRewardsInitiator(address newRewardsInitiator) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) SetRewardsInitiator(newRewardsInitiator common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.SetRewardsInitiator(&_ContractEigenDAServiceManager.TransactOpts, newRewardsInitiator) +} + +// SetRewardsInitiator is a paid mutator transaction binding the contract method 0x3bc28c8c. +// +// Solidity: function setRewardsInitiator(address newRewardsInitiator) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) SetRewardsInitiator(newRewardsInitiator common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.SetRewardsInitiator(&_ContractEigenDAServiceManager.TransactOpts, newRewardsInitiator) +} + +// SetStaleStakesForbidden is a paid mutator transaction binding the contract method 0x416c7e5e. +// +// Solidity: function setStaleStakesForbidden(bool value) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) SetStaleStakesForbidden(opts *bind.TransactOpts, value bool) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "setStaleStakesForbidden", value) +} + +// SetStaleStakesForbidden is a paid mutator transaction binding the contract method 0x416c7e5e. +// +// Solidity: function setStaleStakesForbidden(bool value) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) SetStaleStakesForbidden(value bool) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.SetStaleStakesForbidden(&_ContractEigenDAServiceManager.TransactOpts, value) +} + +// SetStaleStakesForbidden is a paid mutator transaction binding the contract method 0x416c7e5e. +// +// Solidity: function setStaleStakesForbidden(bool value) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) SetStaleStakesForbidden(value bool) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.SetStaleStakesForbidden(&_ContractEigenDAServiceManager.TransactOpts, value) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "transferOwnership", newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.TransferOwnership(&_ContractEigenDAServiceManager.TransactOpts, newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.TransferOwnership(&_ContractEigenDAServiceManager.TransactOpts, newOwner) +} + +// Unpause is a paid mutator transaction binding the contract method 0xfabc1cbc. +// +// Solidity: function unpause(uint256 newPausedStatus) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) Unpause(opts *bind.TransactOpts, newPausedStatus *big.Int) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "unpause", newPausedStatus) +} + +// Unpause is a paid mutator transaction binding the contract method 0xfabc1cbc. +// +// Solidity: function unpause(uint256 newPausedStatus) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) Unpause(newPausedStatus *big.Int) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.Unpause(&_ContractEigenDAServiceManager.TransactOpts, newPausedStatus) +} + +// Unpause is a paid mutator transaction binding the contract method 0xfabc1cbc. +// +// Solidity: function unpause(uint256 newPausedStatus) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) Unpause(newPausedStatus *big.Int) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.Unpause(&_ContractEigenDAServiceManager.TransactOpts, newPausedStatus) +} + +// UpdateAVSMetadataURI is a paid mutator transaction binding the contract method 0xa98fb355. +// +// Solidity: function updateAVSMetadataURI(string _metadataURI) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactor) UpdateAVSMetadataURI(opts *bind.TransactOpts, _metadataURI string) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.contract.Transact(opts, "updateAVSMetadataURI", _metadataURI) +} + +// UpdateAVSMetadataURI is a paid mutator transaction binding the contract method 0xa98fb355. +// +// Solidity: function updateAVSMetadataURI(string _metadataURI) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerSession) UpdateAVSMetadataURI(_metadataURI string) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.UpdateAVSMetadataURI(&_ContractEigenDAServiceManager.TransactOpts, _metadataURI) +} + +// UpdateAVSMetadataURI is a paid mutator transaction binding the contract method 0xa98fb355. +// +// Solidity: function updateAVSMetadataURI(string _metadataURI) returns() +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerTransactorSession) UpdateAVSMetadataURI(_metadataURI string) (*types.Transaction, error) { + return _ContractEigenDAServiceManager.Contract.UpdateAVSMetadataURI(&_ContractEigenDAServiceManager.TransactOpts, _metadataURI) +} + +// ContractEigenDAServiceManagerBatchConfirmedIterator is returned from FilterBatchConfirmed and is used to iterate over the raw logs and unpacked data for BatchConfirmed events raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerBatchConfirmedIterator struct { + Event *ContractEigenDAServiceManagerBatchConfirmed // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractEigenDAServiceManagerBatchConfirmedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerBatchConfirmed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerBatchConfirmed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractEigenDAServiceManagerBatchConfirmedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractEigenDAServiceManagerBatchConfirmedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractEigenDAServiceManagerBatchConfirmed represents a BatchConfirmed event raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerBatchConfirmed struct { + BatchHeaderHash [32]byte + BatchId uint32 + Raw types.Log // Blockchain specific contextual infos +} + +// FilterBatchConfirmed is a free log retrieval operation binding the contract event 0xc75557c4ad49697e231449688be13ef11cb6be8ed0d18819d8dde074a5a16f8a. +// +// Solidity: event BatchConfirmed(bytes32 indexed batchHeaderHash, uint32 batchId) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) FilterBatchConfirmed(opts *bind.FilterOpts, batchHeaderHash [][32]byte) (*ContractEigenDAServiceManagerBatchConfirmedIterator, error) { + + var batchHeaderHashRule []interface{} + for _, batchHeaderHashItem := range batchHeaderHash { + batchHeaderHashRule = append(batchHeaderHashRule, batchHeaderHashItem) + } + + logs, sub, err := _ContractEigenDAServiceManager.contract.FilterLogs(opts, "BatchConfirmed", batchHeaderHashRule) + if err != nil { + return nil, err + } + return &ContractEigenDAServiceManagerBatchConfirmedIterator{contract: _ContractEigenDAServiceManager.contract, event: "BatchConfirmed", logs: logs, sub: sub}, nil +} + +// WatchBatchConfirmed is a free log subscription operation binding the contract event 0xc75557c4ad49697e231449688be13ef11cb6be8ed0d18819d8dde074a5a16f8a. +// +// Solidity: event BatchConfirmed(bytes32 indexed batchHeaderHash, uint32 batchId) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) WatchBatchConfirmed(opts *bind.WatchOpts, sink chan<- *ContractEigenDAServiceManagerBatchConfirmed, batchHeaderHash [][32]byte) (event.Subscription, error) { + + var batchHeaderHashRule []interface{} + for _, batchHeaderHashItem := range batchHeaderHash { + batchHeaderHashRule = append(batchHeaderHashRule, batchHeaderHashItem) + } + + logs, sub, err := _ContractEigenDAServiceManager.contract.WatchLogs(opts, "BatchConfirmed", batchHeaderHashRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractEigenDAServiceManagerBatchConfirmed) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "BatchConfirmed", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseBatchConfirmed is a log parse operation binding the contract event 0xc75557c4ad49697e231449688be13ef11cb6be8ed0d18819d8dde074a5a16f8a. +// +// Solidity: event BatchConfirmed(bytes32 indexed batchHeaderHash, uint32 batchId) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) ParseBatchConfirmed(log types.Log) (*ContractEigenDAServiceManagerBatchConfirmed, error) { + event := new(ContractEigenDAServiceManagerBatchConfirmed) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "BatchConfirmed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ContractEigenDAServiceManagerBatchConfirmerStatusChangedIterator is returned from FilterBatchConfirmerStatusChanged and is used to iterate over the raw logs and unpacked data for BatchConfirmerStatusChanged events raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerBatchConfirmerStatusChangedIterator struct { + Event *ContractEigenDAServiceManagerBatchConfirmerStatusChanged // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractEigenDAServiceManagerBatchConfirmerStatusChangedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerBatchConfirmerStatusChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerBatchConfirmerStatusChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractEigenDAServiceManagerBatchConfirmerStatusChangedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractEigenDAServiceManagerBatchConfirmerStatusChangedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractEigenDAServiceManagerBatchConfirmerStatusChanged represents a BatchConfirmerStatusChanged event raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerBatchConfirmerStatusChanged struct { + BatchConfirmer common.Address + Status bool + Raw types.Log // Blockchain specific contextual infos +} + +// FilterBatchConfirmerStatusChanged is a free log retrieval operation binding the contract event 0x5c3265f5fb462ef4930fe47beaa183647c97f19ba545b761f41bc8cd4621d414. +// +// Solidity: event BatchConfirmerStatusChanged(address batchConfirmer, bool status) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) FilterBatchConfirmerStatusChanged(opts *bind.FilterOpts) (*ContractEigenDAServiceManagerBatchConfirmerStatusChangedIterator, error) { + + logs, sub, err := _ContractEigenDAServiceManager.contract.FilterLogs(opts, "BatchConfirmerStatusChanged") + if err != nil { + return nil, err + } + return &ContractEigenDAServiceManagerBatchConfirmerStatusChangedIterator{contract: _ContractEigenDAServiceManager.contract, event: "BatchConfirmerStatusChanged", logs: logs, sub: sub}, nil +} + +// WatchBatchConfirmerStatusChanged is a free log subscription operation binding the contract event 0x5c3265f5fb462ef4930fe47beaa183647c97f19ba545b761f41bc8cd4621d414. +// +// Solidity: event BatchConfirmerStatusChanged(address batchConfirmer, bool status) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) WatchBatchConfirmerStatusChanged(opts *bind.WatchOpts, sink chan<- *ContractEigenDAServiceManagerBatchConfirmerStatusChanged) (event.Subscription, error) { + + logs, sub, err := _ContractEigenDAServiceManager.contract.WatchLogs(opts, "BatchConfirmerStatusChanged") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractEigenDAServiceManagerBatchConfirmerStatusChanged) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "BatchConfirmerStatusChanged", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseBatchConfirmerStatusChanged is a log parse operation binding the contract event 0x5c3265f5fb462ef4930fe47beaa183647c97f19ba545b761f41bc8cd4621d414. +// +// Solidity: event BatchConfirmerStatusChanged(address batchConfirmer, bool status) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) ParseBatchConfirmerStatusChanged(log types.Log) (*ContractEigenDAServiceManagerBatchConfirmerStatusChanged, error) { + event := new(ContractEigenDAServiceManagerBatchConfirmerStatusChanged) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "BatchConfirmerStatusChanged", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ContractEigenDAServiceManagerInitializedIterator is returned from FilterInitialized and is used to iterate over the raw logs and unpacked data for Initialized events raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerInitializedIterator struct { + Event *ContractEigenDAServiceManagerInitialized // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractEigenDAServiceManagerInitializedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerInitialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerInitialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractEigenDAServiceManagerInitializedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractEigenDAServiceManagerInitializedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractEigenDAServiceManagerInitialized represents a Initialized event raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerInitialized struct { + Version uint8 + Raw types.Log // Blockchain specific contextual infos +} + +// FilterInitialized is a free log retrieval operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) FilterInitialized(opts *bind.FilterOpts) (*ContractEigenDAServiceManagerInitializedIterator, error) { + + logs, sub, err := _ContractEigenDAServiceManager.contract.FilterLogs(opts, "Initialized") + if err != nil { + return nil, err + } + return &ContractEigenDAServiceManagerInitializedIterator{contract: _ContractEigenDAServiceManager.contract, event: "Initialized", logs: logs, sub: sub}, nil +} + +// WatchInitialized is a free log subscription operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) WatchInitialized(opts *bind.WatchOpts, sink chan<- *ContractEigenDAServiceManagerInitialized) (event.Subscription, error) { + + logs, sub, err := _ContractEigenDAServiceManager.contract.WatchLogs(opts, "Initialized") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractEigenDAServiceManagerInitialized) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "Initialized", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseInitialized is a log parse operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) ParseInitialized(log types.Log) (*ContractEigenDAServiceManagerInitialized, error) { + event := new(ContractEigenDAServiceManagerInitialized) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "Initialized", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ContractEigenDAServiceManagerOwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerOwnershipTransferredIterator struct { + Event *ContractEigenDAServiceManagerOwnershipTransferred // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractEigenDAServiceManagerOwnershipTransferredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractEigenDAServiceManagerOwnershipTransferredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractEigenDAServiceManagerOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractEigenDAServiceManagerOwnershipTransferred represents a OwnershipTransferred event raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerOwnershipTransferred struct { + PreviousOwner common.Address + NewOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferred is a free log retrieval operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, previousOwner []common.Address, newOwner []common.Address) (*ContractEigenDAServiceManagerOwnershipTransferredIterator, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _ContractEigenDAServiceManager.contract.FilterLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return &ContractEigenDAServiceManagerOwnershipTransferredIterator{contract: _ContractEigenDAServiceManager.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferred is a free log subscription operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *ContractEigenDAServiceManagerOwnershipTransferred, previousOwner []common.Address, newOwner []common.Address) (event.Subscription, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _ContractEigenDAServiceManager.contract.WatchLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractEigenDAServiceManagerOwnershipTransferred) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOwnershipTransferred is a log parse operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) ParseOwnershipTransferred(log types.Log) (*ContractEigenDAServiceManagerOwnershipTransferred, error) { + event := new(ContractEigenDAServiceManagerOwnershipTransferred) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ContractEigenDAServiceManagerPausedIterator is returned from FilterPaused and is used to iterate over the raw logs and unpacked data for Paused events raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerPausedIterator struct { + Event *ContractEigenDAServiceManagerPaused // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractEigenDAServiceManagerPausedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerPaused) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerPaused) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractEigenDAServiceManagerPausedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractEigenDAServiceManagerPausedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractEigenDAServiceManagerPaused represents a Paused event raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerPaused struct { + Account common.Address + NewPausedStatus *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterPaused is a free log retrieval operation binding the contract event 0xab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d. +// +// Solidity: event Paused(address indexed account, uint256 newPausedStatus) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) FilterPaused(opts *bind.FilterOpts, account []common.Address) (*ContractEigenDAServiceManagerPausedIterator, error) { + + var accountRule []interface{} + for _, accountItem := range account { + accountRule = append(accountRule, accountItem) + } + + logs, sub, err := _ContractEigenDAServiceManager.contract.FilterLogs(opts, "Paused", accountRule) + if err != nil { + return nil, err + } + return &ContractEigenDAServiceManagerPausedIterator{contract: _ContractEigenDAServiceManager.contract, event: "Paused", logs: logs, sub: sub}, nil +} + +// WatchPaused is a free log subscription operation binding the contract event 0xab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d. +// +// Solidity: event Paused(address indexed account, uint256 newPausedStatus) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) WatchPaused(opts *bind.WatchOpts, sink chan<- *ContractEigenDAServiceManagerPaused, account []common.Address) (event.Subscription, error) { + + var accountRule []interface{} + for _, accountItem := range account { + accountRule = append(accountRule, accountItem) + } + + logs, sub, err := _ContractEigenDAServiceManager.contract.WatchLogs(opts, "Paused", accountRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractEigenDAServiceManagerPaused) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "Paused", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParsePaused is a log parse operation binding the contract event 0xab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d. +// +// Solidity: event Paused(address indexed account, uint256 newPausedStatus) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) ParsePaused(log types.Log) (*ContractEigenDAServiceManagerPaused, error) { + event := new(ContractEigenDAServiceManagerPaused) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "Paused", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ContractEigenDAServiceManagerPauserRegistrySetIterator is returned from FilterPauserRegistrySet and is used to iterate over the raw logs and unpacked data for PauserRegistrySet events raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerPauserRegistrySetIterator struct { + Event *ContractEigenDAServiceManagerPauserRegistrySet // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractEigenDAServiceManagerPauserRegistrySetIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerPauserRegistrySet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerPauserRegistrySet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractEigenDAServiceManagerPauserRegistrySetIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractEigenDAServiceManagerPauserRegistrySetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractEigenDAServiceManagerPauserRegistrySet represents a PauserRegistrySet event raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerPauserRegistrySet struct { + PauserRegistry common.Address + NewPauserRegistry common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterPauserRegistrySet is a free log retrieval operation binding the contract event 0x6e9fcd539896fca60e8b0f01dd580233e48a6b0f7df013b89ba7f565869acdb6. +// +// Solidity: event PauserRegistrySet(address pauserRegistry, address newPauserRegistry) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) FilterPauserRegistrySet(opts *bind.FilterOpts) (*ContractEigenDAServiceManagerPauserRegistrySetIterator, error) { + + logs, sub, err := _ContractEigenDAServiceManager.contract.FilterLogs(opts, "PauserRegistrySet") + if err != nil { + return nil, err + } + return &ContractEigenDAServiceManagerPauserRegistrySetIterator{contract: _ContractEigenDAServiceManager.contract, event: "PauserRegistrySet", logs: logs, sub: sub}, nil +} + +// WatchPauserRegistrySet is a free log subscription operation binding the contract event 0x6e9fcd539896fca60e8b0f01dd580233e48a6b0f7df013b89ba7f565869acdb6. +// +// Solidity: event PauserRegistrySet(address pauserRegistry, address newPauserRegistry) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) WatchPauserRegistrySet(opts *bind.WatchOpts, sink chan<- *ContractEigenDAServiceManagerPauserRegistrySet) (event.Subscription, error) { + + logs, sub, err := _ContractEigenDAServiceManager.contract.WatchLogs(opts, "PauserRegistrySet") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractEigenDAServiceManagerPauserRegistrySet) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "PauserRegistrySet", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParsePauserRegistrySet is a log parse operation binding the contract event 0x6e9fcd539896fca60e8b0f01dd580233e48a6b0f7df013b89ba7f565869acdb6. +// +// Solidity: event PauserRegistrySet(address pauserRegistry, address newPauserRegistry) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) ParsePauserRegistrySet(log types.Log) (*ContractEigenDAServiceManagerPauserRegistrySet, error) { + event := new(ContractEigenDAServiceManagerPauserRegistrySet) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "PauserRegistrySet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ContractEigenDAServiceManagerRewardsInitiatorUpdatedIterator is returned from FilterRewardsInitiatorUpdated and is used to iterate over the raw logs and unpacked data for RewardsInitiatorUpdated events raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerRewardsInitiatorUpdatedIterator struct { + Event *ContractEigenDAServiceManagerRewardsInitiatorUpdated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractEigenDAServiceManagerRewardsInitiatorUpdatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerRewardsInitiatorUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerRewardsInitiatorUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractEigenDAServiceManagerRewardsInitiatorUpdatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractEigenDAServiceManagerRewardsInitiatorUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractEigenDAServiceManagerRewardsInitiatorUpdated represents a RewardsInitiatorUpdated event raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerRewardsInitiatorUpdated struct { + PrevRewardsInitiator common.Address + NewRewardsInitiator common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRewardsInitiatorUpdated is a free log retrieval operation binding the contract event 0xe11cddf1816a43318ca175bbc52cd0185436e9cbead7c83acc54a73e461717e3. +// +// Solidity: event RewardsInitiatorUpdated(address prevRewardsInitiator, address newRewardsInitiator) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) FilterRewardsInitiatorUpdated(opts *bind.FilterOpts) (*ContractEigenDAServiceManagerRewardsInitiatorUpdatedIterator, error) { + + logs, sub, err := _ContractEigenDAServiceManager.contract.FilterLogs(opts, "RewardsInitiatorUpdated") + if err != nil { + return nil, err + } + return &ContractEigenDAServiceManagerRewardsInitiatorUpdatedIterator{contract: _ContractEigenDAServiceManager.contract, event: "RewardsInitiatorUpdated", logs: logs, sub: sub}, nil +} + +// WatchRewardsInitiatorUpdated is a free log subscription operation binding the contract event 0xe11cddf1816a43318ca175bbc52cd0185436e9cbead7c83acc54a73e461717e3. +// +// Solidity: event RewardsInitiatorUpdated(address prevRewardsInitiator, address newRewardsInitiator) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) WatchRewardsInitiatorUpdated(opts *bind.WatchOpts, sink chan<- *ContractEigenDAServiceManagerRewardsInitiatorUpdated) (event.Subscription, error) { + + logs, sub, err := _ContractEigenDAServiceManager.contract.WatchLogs(opts, "RewardsInitiatorUpdated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractEigenDAServiceManagerRewardsInitiatorUpdated) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "RewardsInitiatorUpdated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRewardsInitiatorUpdated is a log parse operation binding the contract event 0xe11cddf1816a43318ca175bbc52cd0185436e9cbead7c83acc54a73e461717e3. +// +// Solidity: event RewardsInitiatorUpdated(address prevRewardsInitiator, address newRewardsInitiator) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) ParseRewardsInitiatorUpdated(log types.Log) (*ContractEigenDAServiceManagerRewardsInitiatorUpdated, error) { + event := new(ContractEigenDAServiceManagerRewardsInitiatorUpdated) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "RewardsInitiatorUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ContractEigenDAServiceManagerStaleStakesForbiddenUpdateIterator is returned from FilterStaleStakesForbiddenUpdate and is used to iterate over the raw logs and unpacked data for StaleStakesForbiddenUpdate events raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerStaleStakesForbiddenUpdateIterator struct { + Event *ContractEigenDAServiceManagerStaleStakesForbiddenUpdate // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractEigenDAServiceManagerStaleStakesForbiddenUpdateIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerStaleStakesForbiddenUpdate) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerStaleStakesForbiddenUpdate) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractEigenDAServiceManagerStaleStakesForbiddenUpdateIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractEigenDAServiceManagerStaleStakesForbiddenUpdateIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractEigenDAServiceManagerStaleStakesForbiddenUpdate represents a StaleStakesForbiddenUpdate event raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerStaleStakesForbiddenUpdate struct { + Value bool + Raw types.Log // Blockchain specific contextual infos +} + +// FilterStaleStakesForbiddenUpdate is a free log retrieval operation binding the contract event 0x40e4ed880a29e0f6ddce307457fb75cddf4feef7d3ecb0301bfdf4976a0e2dfc. +// +// Solidity: event StaleStakesForbiddenUpdate(bool value) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) FilterStaleStakesForbiddenUpdate(opts *bind.FilterOpts) (*ContractEigenDAServiceManagerStaleStakesForbiddenUpdateIterator, error) { + + logs, sub, err := _ContractEigenDAServiceManager.contract.FilterLogs(opts, "StaleStakesForbiddenUpdate") + if err != nil { + return nil, err + } + return &ContractEigenDAServiceManagerStaleStakesForbiddenUpdateIterator{contract: _ContractEigenDAServiceManager.contract, event: "StaleStakesForbiddenUpdate", logs: logs, sub: sub}, nil +} + +// WatchStaleStakesForbiddenUpdate is a free log subscription operation binding the contract event 0x40e4ed880a29e0f6ddce307457fb75cddf4feef7d3ecb0301bfdf4976a0e2dfc. +// +// Solidity: event StaleStakesForbiddenUpdate(bool value) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) WatchStaleStakesForbiddenUpdate(opts *bind.WatchOpts, sink chan<- *ContractEigenDAServiceManagerStaleStakesForbiddenUpdate) (event.Subscription, error) { + + logs, sub, err := _ContractEigenDAServiceManager.contract.WatchLogs(opts, "StaleStakesForbiddenUpdate") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractEigenDAServiceManagerStaleStakesForbiddenUpdate) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "StaleStakesForbiddenUpdate", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseStaleStakesForbiddenUpdate is a log parse operation binding the contract event 0x40e4ed880a29e0f6ddce307457fb75cddf4feef7d3ecb0301bfdf4976a0e2dfc. +// +// Solidity: event StaleStakesForbiddenUpdate(bool value) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) ParseStaleStakesForbiddenUpdate(log types.Log) (*ContractEigenDAServiceManagerStaleStakesForbiddenUpdate, error) { + event := new(ContractEigenDAServiceManagerStaleStakesForbiddenUpdate) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "StaleStakesForbiddenUpdate", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ContractEigenDAServiceManagerUnpausedIterator is returned from FilterUnpaused and is used to iterate over the raw logs and unpacked data for Unpaused events raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerUnpausedIterator struct { + Event *ContractEigenDAServiceManagerUnpaused // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractEigenDAServiceManagerUnpausedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerUnpaused) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractEigenDAServiceManagerUnpaused) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractEigenDAServiceManagerUnpausedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractEigenDAServiceManagerUnpausedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractEigenDAServiceManagerUnpaused represents a Unpaused event raised by the ContractEigenDAServiceManager contract. +type ContractEigenDAServiceManagerUnpaused struct { + Account common.Address + NewPausedStatus *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterUnpaused is a free log retrieval operation binding the contract event 0x3582d1828e26bf56bd801502bc021ac0bc8afb57c826e4986b45593c8fad389c. +// +// Solidity: event Unpaused(address indexed account, uint256 newPausedStatus) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) FilterUnpaused(opts *bind.FilterOpts, account []common.Address) (*ContractEigenDAServiceManagerUnpausedIterator, error) { + + var accountRule []interface{} + for _, accountItem := range account { + accountRule = append(accountRule, accountItem) + } + + logs, sub, err := _ContractEigenDAServiceManager.contract.FilterLogs(opts, "Unpaused", accountRule) + if err != nil { + return nil, err + } + return &ContractEigenDAServiceManagerUnpausedIterator{contract: _ContractEigenDAServiceManager.contract, event: "Unpaused", logs: logs, sub: sub}, nil +} + +// WatchUnpaused is a free log subscription operation binding the contract event 0x3582d1828e26bf56bd801502bc021ac0bc8afb57c826e4986b45593c8fad389c. +// +// Solidity: event Unpaused(address indexed account, uint256 newPausedStatus) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) WatchUnpaused(opts *bind.WatchOpts, sink chan<- *ContractEigenDAServiceManagerUnpaused, account []common.Address) (event.Subscription, error) { + + var accountRule []interface{} + for _, accountItem := range account { + accountRule = append(accountRule, accountItem) + } + + logs, sub, err := _ContractEigenDAServiceManager.contract.WatchLogs(opts, "Unpaused", accountRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractEigenDAServiceManagerUnpaused) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "Unpaused", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseUnpaused is a log parse operation binding the contract event 0x3582d1828e26bf56bd801502bc021ac0bc8afb57c826e4986b45593c8fad389c. +// +// Solidity: event Unpaused(address indexed account, uint256 newPausedStatus) +func (_ContractEigenDAServiceManager *ContractEigenDAServiceManagerFilterer) ParseUnpaused(log types.Log) (*ContractEigenDAServiceManagerUnpaused, error) { + event := new(ContractEigenDAServiceManagerUnpaused) + if err := _ContractEigenDAServiceManager.contract.UnpackLog(event, "Unpaused", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/op-service/eigenda/cli.go b/op-service/eigenda/cli.go index 39e21e000..be5b16cab 100644 --- a/op-service/eigenda/cli.go +++ b/op-service/eigenda/cli.go @@ -11,6 +11,20 @@ const ( RPCFlagName = "da-rpc" StatusQueryRetryIntervalFlagName = "da-status-query-retry-interval" StatusQueryTimeoutFlagName = "da-status-query-timeout" + DARPCTimeoutFlagName = "da-rpc-timeout" + EnableDAHsmFlagName = "enable-da-hsm" + DAHsmCredenFlagName = "da-hsm-creden" + DAHsmPubkeyFlagName = "da-hsm-pubkey" + DAHsmAPINameFlagName = "da-hsm-api-name" + DAPrivateKeyFlagName = "da-private-key" + DAEthRPCFlagName = "eigenda-eth-rpc" + DASvcManagerAddrFlagName = "eigenda-svc-manager-addr" + DAEthConfirmationDepthFlagName = "eigenda-eth-confirmation-depth" + // Kzg flags + DAG1PathFlagName = "eigenda-g1-path" + DAG2TauFlagName = "eigenda-g2-tau-path" + DAMaxBlobLengthFlagName = "eigenda-max-blob-length" + DACachePathFlagName = "eigenda-cache-path" ) func PrefixEnvVar(prefix, suffix string) string { @@ -21,6 +35,24 @@ type CLIConfig struct { RPC string StatusQueryRetryInterval time.Duration StatusQueryTimeout time.Duration + DARPCTimeout time.Duration + EnableHsm bool + HsmCreden string + HsmPubkey string + HsmAPIName string + PrivateKey string + + // ETH vars + EthRPC string + SvcManagerAddr string + EthConfirmationDepth uint64 + + // KZG vars + CacheDir string + G1Path string + G2PowerOfTauPath string + + MaxBlobLength uint64 } // NewConfig parses the Config from the provided flags or environment variables. @@ -30,6 +62,22 @@ func ReadCLIConfig(ctx *cli.Context) CLIConfig { RPC: ctx.String(RPCFlagName), StatusQueryRetryInterval: ctx.Duration(StatusQueryRetryIntervalFlagName), StatusQueryTimeout: ctx.Duration(StatusQueryTimeoutFlagName), + DARPCTimeout: ctx.Duration(DARPCTimeoutFlagName), + + /* Optional Flags */ + EnableHsm: ctx.Bool(EnableDAHsmFlagName), + HsmCreden: ctx.String(DAHsmCredenFlagName), + HsmPubkey: ctx.String(DAHsmPubkeyFlagName), + HsmAPIName: ctx.String(DAHsmAPINameFlagName), + PrivateKey: ctx.String(DAPrivateKeyFlagName), + + G1Path: ctx.String(DAG1PathFlagName), + G2PowerOfTauPath: ctx.String(DAG2TauFlagName), + CacheDir: ctx.String(DACachePathFlagName), + MaxBlobLength: ctx.Uint64(DAMaxBlobLengthFlagName), + SvcManagerAddr: ctx.String(DASvcManagerAddrFlagName), + EthRPC: ctx.String(DAEthRPCFlagName), + EthConfirmationDepth: ctx.Uint64(DAEthConfirmationDepthFlagName), } } @@ -43,6 +91,17 @@ func (m CLIConfig) Check() error { if m.StatusQueryRetryInterval == 0 { return errors.New("DA status query retry interval must be greater than 0") } + if m.EnableHsm { + if m.HsmCreden == "" { + return errors.New("must provide a HSM creden") + } + if m.HsmPubkey == "" { + return errors.New("must provide a HSM pubkey") + } + if m.HsmAPIName == "" { + return errors.New("must provide a HSM API name") + } + } return nil } @@ -68,5 +127,80 @@ func CLIFlags(envPrefix string) []cli.Flag { Value: 5 * time.Second, EnvVar: prefixEnvVars("DA_STATUS_QUERY_INTERVAL"), }, + cli.DurationFlag{ + Name: DARPCTimeoutFlagName, + Usage: "Timeout for EigenDA rpc calls", + Value: 5 * time.Second, + EnvVar: prefixEnvVars("DA_RPC_TIMEOUT"), + }, + cli.BoolFlag{ + Name: EnableDAHsmFlagName, + Usage: "EigenDA whether or not to use cloud hsm", + EnvVar: prefixEnvVars("ENABLE_DA_HSM"), + }, + cli.StringFlag{ + Name: DAHsmPubkeyFlagName, + Usage: "The public-key of EigenDA account in hsm", + EnvVar: prefixEnvVars("DA_HSM_PUBKEY"), + Value: "", + }, + cli.StringFlag{ + Name: DAHsmAPINameFlagName, + Usage: "The api-name of EigenDA account in hsm", + EnvVar: prefixEnvVars("DA_HSM_API_NAME"), + Value: "", + }, + cli.StringFlag{ + Name: DAHsmCredenFlagName, + Usage: "The creden of EigenDA account in hsm", + EnvVar: prefixEnvVars("DA_HSM_CREDEN"), + Value: "", + }, + cli.StringFlag{ + Name: DAPrivateKeyFlagName, + Usage: "The private-key of EigenDA account", + EnvVar: prefixEnvVars("DA_PRIVATE_KEY"), + Value: "", + }, + cli.StringFlag{ + Name: DAEthRPCFlagName, + Usage: "JSON RPC node endpoint for the Ethereum network used for DA blobs verify. See available list here: https://docs.eigenlayer.xyz/eigenda/networks/", + EnvVar: prefixEnvVars("DA_ETH_RPC"), + }, + cli.StringFlag{ + Name: DASvcManagerAddrFlagName, + Usage: "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", + EnvVar: prefixEnvVars("DA_SERVICE_MANAGER_ADDR"), + }, + cli.Uint64Flag{ + Name: DAEthConfirmationDepthFlagName, + Usage: "The number of Ethereum blocks of confirmation before verify.", + EnvVar: prefixEnvVars("DA_ETH_CONFIRMATION_DEPTH"), + Value: 6, + }, + cli.Uint64Flag{ + Name: DAMaxBlobLengthFlagName, + Usage: "Maximum blob length to be written or read from EigenDA. Determines the number of SRS points loaded into memory for KZG commitments. Example units: '30MiB', '4Kb', '30MB'. Maximum size slightly exceeds 1GB.", + EnvVar: prefixEnvVars("DA_MAX_BLOB_LENGTH"), + Value: 2_000_000, + }, + cli.StringFlag{ + Name: DAG1PathFlagName, + Usage: "Directory path to g1.point file.", + EnvVar: prefixEnvVars("DA_TARGET_KZG_G1_PATH"), + Value: "resources/g1.point", + }, + cli.StringFlag{ + Name: DAG2TauFlagName, + Usage: "Directory path to g2.point.powerOf2 file.", + EnvVar: prefixEnvVars("DA_TARGET_G2_TAU_PATH"), + Value: "resources/g2.point.powerOf2", + }, + cli.StringFlag{ + Name: DACachePathFlagName, + Usage: "Directory path to SRS tables for caching.", + EnvVar: prefixEnvVars("DA_TARGET_CACHE_PATH"), + Value: "resources/SRSTables/", + }, } } diff --git a/op-service/eigenda/codecs/blob_codec.go b/op-service/eigenda/codecs/blob_codec.go new file mode 100644 index 000000000..94e19a132 --- /dev/null +++ b/op-service/eigenda/codecs/blob_codec.go @@ -0,0 +1,45 @@ +package codecs + +import ( + "fmt" +) + +type BlobEncodingVersion byte + +// All blob encodings are IFFT'd before being dispersed +const ( + // This minimal blob encoding includes a version byte, a length uint32, and 31 byte field element mapping. + DefaultBlobEncoding BlobEncodingVersion = 0x0 +) + +type BlobCodec interface { + DecodeBlob(encodedData []byte) ([]byte, error) + EncodeBlob(rawData []byte) ([]byte, error) +} + +func BlobEncodingVersionToCodec(version BlobEncodingVersion) (BlobCodec, error) { + switch version { + case DefaultBlobEncoding: + return DefaultBlobCodec{}, nil + default: + return nil, fmt.Errorf("unsupported blob encoding version: %x", version) + } +} + +func GenericDecodeBlob(data []byte) ([]byte, error) { + if len(data) <= 32 { + return nil, fmt.Errorf("data is not of length greater than 32 bytes: %d", len(data)) + } + version := BlobEncodingVersion(data[1]) + codec, err := BlobEncodingVersionToCodec(version) + if err != nil { + return nil, err + } + + data, err = codec.DecodeBlob(data) + if err != nil { + return nil, err + } + + return data, nil +} diff --git a/op-service/eigenda/codecs/default_blob_codec.go b/op-service/eigenda/codecs/default_blob_codec.go new file mode 100644 index 000000000..74d27c728 --- /dev/null +++ b/op-service/eigenda/codecs/default_blob_codec.go @@ -0,0 +1,59 @@ +package codecs + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/utils/codec" +) + +type DefaultBlobCodec struct{} + +var _ BlobCodec = DefaultBlobCodec{} + +func NewDefaultBlobCodec() DefaultBlobCodec { + return DefaultBlobCodec{} +} + +func (v DefaultBlobCodec) EncodeBlob(rawData []byte) ([]byte, error) { + codecBlobHeader := make([]byte, 32) + // first byte is always 0 to ensure the codecBlobHeader is a valid bn254 element + // encode version byte + codecBlobHeader[1] = byte(DefaultBlobEncoding) + + // encode length as uint32 + binary.BigEndian.PutUint32(codecBlobHeader[2:6], uint32(len(rawData))) // uint32 should be more than enough to store the length (approx 4gb) + + // encode raw data modulo bn254 + rawDataPadded := codec.ConvertByPaddingEmptyByte(rawData) + + // append raw data + encodedData := append(codecBlobHeader, rawDataPadded...) + + return encodedData, nil +} + +func (v DefaultBlobCodec) DecodeBlob(data []byte) ([]byte, error) { + if len(data) < 32 { + return nil, fmt.Errorf("blob does not contain 32 header bytes, meaning it is malformed") + } + + length := binary.BigEndian.Uint32(data[2:6]) + + // decode raw data modulo bn254 + decodedData := codec.RemoveEmptyByteFromPaddedBytes(data[32:]) + + // get non blob header data + reader := bytes.NewReader(decodedData) + rawData := make([]byte, length) + n, err := reader.Read(rawData) + if err != nil { + return nil, fmt.Errorf("failed to copy unpadded data into final buffer, length: %d, bytes read: %d", length, n) + } + if uint32(n) != length { + return nil, fmt.Errorf("data length does not match length prefix") + } + + return rawData, nil +} diff --git a/op-service/eigenda/codecs/fft.go b/op-service/eigenda/codecs/fft.go new file mode 100644 index 000000000..9b0416a21 --- /dev/null +++ b/op-service/eigenda/codecs/fft.go @@ -0,0 +1,68 @@ +package codecs + +import ( + "fmt" + "math" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/fft" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" +) + +func FFT(data []byte) ([]byte, error) { + dataFr, err := rs.ToFrArray(data) + if err != nil { + return nil, fmt.Errorf("error converting data to fr.Element: %w", err) + } + dataFrLen := uint64(len(dataFr)) + dataFrLenPow2 := encoding.NextPowerOf2(dataFrLen) + + if dataFrLenPow2 != dataFrLen { + return nil, fmt.Errorf("data length %d is not a power of 2", dataFrLen) + } + + maxScale := uint8(math.Log2(float64(dataFrLenPow2))) + + fs := fft.NewFFTSettings(maxScale) + + dataFFTFr, err := fs.FFT(dataFr, false) + if err != nil { + return nil, fmt.Errorf("failed to perform FFT: %w", err) + } + + return rs.ToByteArray(dataFFTFr, dataFrLenPow2*encoding.BYTES_PER_SYMBOL), nil +} + +func IFFT(data []byte) ([]byte, error) { + // we now IFFT data regardless of the encoding type + // convert data to fr.Element + dataFr, err := rs.ToFrArray(data) + if err != nil { + return nil, fmt.Errorf("error converting data to fr.Element: %w", err) + } + + dataFrLen := len(dataFr) + dataFrLenPow2 := encoding.NextPowerOf2(uint64(dataFrLen)) + + // expand data to the next power of 2 + paddedDataFr := make([]fr.Element, dataFrLenPow2) + for i := 0; i < len(paddedDataFr); i++ { + if i < len(dataFr) { + paddedDataFr[i].Set(&dataFr[i]) + } else { + paddedDataFr[i].SetZero() + } + } + + maxScale := uint8(math.Log2(float64(dataFrLenPow2))) + + // perform IFFT + fs := fft.NewFFTSettings(maxScale) + dataIFFTFr, err := fs.FFT(paddedDataFr, true) + if err != nil { + return nil, fmt.Errorf("failed to perform IFFT: %w", err) + } + + return rs.ToByteArray(dataIFFTFr, dataFrLenPow2*encoding.BYTES_PER_SYMBOL), nil +} diff --git a/op-service/eigenda/codecs/ifft_codec.go b/op-service/eigenda/codecs/ifft_codec.go new file mode 100644 index 000000000..edd5040d4 --- /dev/null +++ b/op-service/eigenda/codecs/ifft_codec.go @@ -0,0 +1,38 @@ +package codecs + +import "fmt" + +type IFFTCodec struct { + writeCodec BlobCodec +} + +var _ BlobCodec = IFFTCodec{} + +func NewIFFTCodec(writeCodec BlobCodec) IFFTCodec { + return IFFTCodec{ + writeCodec: writeCodec, + } +} + +func (v IFFTCodec) EncodeBlob(data []byte) ([]byte, error) { + var err error + data, err = v.writeCodec.EncodeBlob(data) + if err != nil { + return nil, fmt.Errorf("error encoding data: %w", err) + } + + return IFFT(data) +} + +func (v IFFTCodec) DecodeBlob(data []byte) ([]byte, error) { + if len(data) == 0 { + return nil, fmt.Errorf("blob has length 0, meaning it is malformed") + } + var err error + data, err = FFT(data) + if err != nil { + return nil, fmt.Errorf("error FFTing data: %w", err) + } + + return GenericDecodeBlob(data) +} diff --git a/op-service/eigenda/codecs/no_ifft_codec.go b/op-service/eigenda/codecs/no_ifft_codec.go new file mode 100644 index 000000000..372bbfbcd --- /dev/null +++ b/op-service/eigenda/codecs/no_ifft_codec.go @@ -0,0 +1,21 @@ +package codecs + +type NoIFFTCodec struct { + writeCodec BlobCodec +} + +var _ BlobCodec = NoIFFTCodec{} + +func NewNoIFFTCodec(writeCodec BlobCodec) NoIFFTCodec { + return NoIFFTCodec{ + writeCodec: writeCodec, + } +} + +func (v NoIFFTCodec) EncodeBlob(data []byte) ([]byte, error) { + return v.writeCodec.EncodeBlob(data) +} + +func (v NoIFFTCodec) DecodeBlob(data []byte) ([]byte, error) { + return GenericDecodeBlob(data) +} diff --git a/op-service/eigenda/config.go b/op-service/eigenda/config.go index b18598103..5a955d4ef 100644 --- a/op-service/eigenda/config.go +++ b/op-service/eigenda/config.go @@ -3,8 +3,11 @@ package eigenda import ( "fmt" "math" + "runtime" "time" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + "github.com/ethereum-optimism/optimism/op-service/eigenda/verify" "github.com/urfave/cli/v2" ) @@ -20,6 +23,87 @@ type Config struct { // The amount of time to wait between status queries of a newly dispersed blob StatusQueryRetryInterval time.Duration + + // rpc timeout + RPCTimeout time.Duration + + // EigenDA whether or not to use cloud hsm + EnableHsm bool + + // The public-key of EigenDA account in hsm + HsmCreden string + + // The public-key of EigenDA account in hsm + HsmPubkey string + + // The API name of EigenDA account in hsm + HsmAPIName string + + // The private key of EigenDA account if not using cloud hsm + PrivateKey string + + // ETH vars + EthRPC string + SvcManagerAddr string + EthConfirmationDepth uint64 + + // KZG vars + CacheDir string + + G1Path string + G2Path string + + MaxBlobLength uint64 + G2PowerOfTauPath string +} + +const BytesPerSymbol = 31 +const MaxCodingRatio = 8 + +var MaxSRSPoints = math.Pow(2, 28) + +var MaxAllowedBlobSize = uint64(MaxSRSPoints * BytesPerSymbol / MaxCodingRatio) + +func (c *Config) GetMaxBlobLength() (uint64, error) { + if c.MaxBlobLength > MaxAllowedBlobSize { + return 0, fmt.Errorf("excluding disperser constraints on max blob size, SRS points constrain the maxBlobLength configuration parameter to be less than than ~1 GB (%d bytes)", MaxAllowedBlobSize) + } + + return c.MaxBlobLength, nil +} + +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)) + + 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, + EthConfirmationDepth: c.EthConfirmationDepth, + } + } // We add this because the urfave/cli library doesn't support uint32 specifically diff --git a/op-service/eigenda/da.go b/op-service/eigenda/da.go index 48b3a8d3b..c16456393 100644 --- a/op-service/eigenda/da.go +++ b/op-service/eigenda/da.go @@ -5,10 +5,13 @@ import ( "crypto/tls" "encoding/base64" "encoding/hex" + "encoding/json" + "errors" "fmt" "time" "github.com/Layr-Labs/eigenda/api/grpc/disperser" + "github.com/ethereum-optimism/optimism/op-service/eigenda/verify" "github.com/ethereum/go-ethereum/log" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -23,7 +26,43 @@ type IEigenDA interface { type EigenDA struct { Config - Log log.Logger + Log log.Logger + Metricer Metrics + signer BlobRequestSigner + verifier *verify.Verifier +} + +func NewEigenDAClient(config Config, log log.Logger, metricer Metrics) (*EigenDA, error) { + var signer BlobRequestSigner + var err error + if config.EnableHsm { + signer, err = NewHsmBlobSigner(config.HsmCreden, config.HsmAPIName, config.HsmPubkey) + if err != nil { + return nil, fmt.Errorf("error creating HSM signer: %w", err) + } + } else if config.PrivateKey != "" { + signer, err = NewLocalBlobSigner(config.PrivateKey) + if err != nil { + return nil, fmt.Errorf("error creating local signer: %w", err) + } + } + + var verifier *verify.Verifier + if config.EthRPC != "" && config.SvcManagerAddr != "" { + vCfg := config.VerificationCfg() + verifier, err = verify.NewVerifier(vCfg, log) + if err != nil { + return nil, fmt.Errorf("error creating verifier: %w", err) + } + } + + return &EigenDA{ + Config: config, + Log: log, + signer: signer, + verifier: verifier, + Metricer: metricer, + }, nil } func (m *EigenDA) GetBlobStatus(ctx context.Context, requestID []byte) (*disperser.BlobStatusReply, error) { @@ -35,6 +74,7 @@ func (m *EigenDA) GetBlobStatus(ctx context.Context, requestID []byte) (*dispers if err != nil { return nil, err } + defer func() { _ = conn.Close() }() daClient := disperser.NewDisperserClient(conn) statusRes, err := daClient.GetBlobStatus(ctx, &disperser.BlobStatusRequest{ @@ -55,9 +95,12 @@ func (m *EigenDA) RetrieveBlob(ctx context.Context, BatchHeaderHash []byte, Blob if err != nil { return nil, err } + defer func() { _ = conn.Close() }() daClient := disperser.NewDisperserClient(conn) - reply, err := daClient.RetrieveBlob(ctx, &disperser.RetrieveBlobRequest{ + ctxTimeout, cancel := context.WithTimeout(ctx, m.RPCTimeout) + defer cancel() + reply, err := daClient.RetrieveBlob(ctxTimeout, &disperser.RetrieveBlobRequest{ BatchHeaderHash: BatchHeaderHash, BlobIndex: BlobIndex, }) @@ -72,7 +115,72 @@ func (m *EigenDA) RetrieveBlob(ctx context.Context, BatchHeaderHash []byte, Blob } func (m *EigenDA) DisperseBlob(ctx context.Context, txData []byte) (*disperser.BlobInfo, []byte, error) { - m.Log.Info("Attempting to disperse blob to EigenDA") + dispersalStart := time.Now() + var blobInfo *disperser.BlobInfo + var blobData []byte + var err error + done := m.recordInterval("WholeDisperseLifecycle") + if m.signer == nil { + blobInfo, blobData, err = m.DisperseBlobWithoutAuth(ctx, txData) + } else { + blobInfo, blobData, err = m.DisperseBlobAuthenticated(ctx, txData) + } + done(err) + if err != nil { + m.Log.Error("error disperse blob", "err", err) + return nil, nil, err + } + + if m.verifier == nil { + m.Log.Info("DisperseBlob without verification", "time", time.Since(dispersalStart)) + return blobInfo, blobData, nil + } + + encodedTxData := ConvertByPaddingEmptyByte(txData) + err = m.verifier.VerifyCommitment(blobInfo.BlobHeader.Commitment, encodedTxData) + if err != nil { + m.Log.Error("error verify commitment", "err", err) + return nil, nil, err + } + + m.Log.Info("Dispersal took", "time", time.Since(dispersalStart)) + + dispersalDuration := time.Since(dispersalStart) + remainingTimeout := m.StatusQueryTimeout - dispersalDuration + + ticker := time.NewTicker(12 * time.Second) + defer ticker.Stop() + ctx, cancel := context.WithTimeout(context.Background(), remainingTimeout) + defer cancel() + + blobJson, _ := json.Marshal(blobInfo) + m.Log.Info("Verifying blob", "info", string(blobJson)) + + cert := (*verify.Certificate)(blobInfo) + finished := false + for !finished { + select { + case <-ctx.Done(): + return nil, nil, ctx.Err() + case <-ticker.C: + err = m.verifier.VerifyCert(cert) + if err == nil { + m.Log.Info("Blob verified successfully", "info", string(blobJson)) + finished = true + } else if !errors.Is(err, verify.ErrBatchMetadataHashNotFound) { + m.Log.Error("Blob verified with error", "info", string(blobJson)) + return nil, nil, err + } else { + m.Log.Info("Blob confirmed, waiting for sufficient confirmation depth...", "targetDepth", m.EthConfirmationDepth) + } + } + } + + return blobInfo, blobData, nil +} + +func (m *EigenDA) DisperseBlobWithoutAuth(ctx context.Context, txData []byte) (*disperser.BlobInfo, []byte, error) { + m.Log.Info("Attempting to disperse blob to EigenDA without auth") config := &tls.Config{} credential := credentials.NewTLS(config) dialOptions := []grpc.DialOption{grpc.WithTransportCredentials(credential)} @@ -80,16 +188,21 @@ func (m *EigenDA) DisperseBlob(ctx context.Context, txData []byte) (*disperser.B if err != nil { return nil, nil, err } + defer func() { _ = conn.Close() }() daClient := disperser.NewDisperserClient(conn) + ctxTimeout, cancel := context.WithTimeout(ctx, m.RPCTimeout) + defer cancel() + // encode modulo bn254 encodedTxData := ConvertByPaddingEmptyByte(txData) - disperseReq := &disperser.DisperseBlobRequest{ Data: encodedTxData, } - disperseRes, err := daClient.DisperseBlob(ctx, disperseReq) + done := m.recordInterval("DisperseBlob") + disperseRes, err := daClient.DisperseBlob(ctxTimeout, disperseReq) + done(err) if err != nil || disperseRes == nil { m.Log.Error("Unable to disperse blob to EigenDA, aborting", "err", err) return nil, nil, err @@ -114,9 +227,11 @@ func (m *EigenDA) DisperseBlob(ctx context.Context, txData []byte) (*disperser.B m.Log.Warn("context error", "err", ctx.Err()) return nil, nil, ctx.Err() } + done := m.recordInterval("GetBlobStatus") statusRes, err = daClient.GetBlobStatus(ctx, &disperser.BlobStatusRequest{ RequestId: disperseRes.RequestId, }) + done(err) if err != nil { m.Log.Warn("Unable to retrieve blob dispersal status, will retry", "requestID", base64RequestID, "err", err) } else if statusRes.Status == disperser.BlobStatus_CONFIRMED || statusRes.Status == disperser.BlobStatus_FINALIZED { @@ -141,3 +256,144 @@ func (m *EigenDA) DisperseBlob(ctx context.Context, txData []byte) (*disperser.B return nil, nil, fmt.Errorf("timed out getting EigenDA status for dispersed blob key: %s", base64RequestID) } + +func (m *EigenDA) DisperseBlobAuthenticated(ctx context.Context, rawData []byte) (*disperser.BlobInfo, []byte, error) { + m.Log.Info("Attempting to disperse blob to EigenDA with auth") + + // disperse blob + done := m.recordInterval("DisperseBlobAuthenticated") + blobStatus, requestID, err := m.disperseBlobAuthenticated(ctx, rawData) + done(err) + if err != nil { + return nil, nil, fmt.Errorf("error calling DisperseBlobAuthenticated() client: %w", err) + } + + // process response + if *blobStatus == disperser.BlobStatus_FAILED { + return nil, nil, fmt.Errorf("reply status is %d", blobStatus) + } + + base64RequestID := base64.StdEncoding.EncodeToString(requestID) + m.Log.Info("Blob dispersed to EigenDA, now waiting for confirmation", "requestID", base64RequestID) + + ticker := time.NewTicker(m.Config.StatusQueryRetryInterval) + defer ticker.Stop() + + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, m.Config.StatusQueryTimeout) + defer cancel() + + for { + select { + case <-ctx.Done(): + return nil, nil, fmt.Errorf("timed out waiting for EigenDA blob to confirm blob with request id=%s: %w", base64RequestID, ctx.Err()) + case <-ticker.C: + done := m.recordInterval("GetBlobStatus") + statusRes, err := m.GetBlobStatus(ctx, requestID) + done(err) + if err != nil { + m.Log.Error("Unable to retrieve blob dispersal status, will retry", "requestID", base64RequestID, "err", err) + continue + } + + switch statusRes.Status { + case disperser.BlobStatus_PROCESSING: + m.Log.Info("Blob submitted, waiting for dispersal from EigenDA", "requestID", base64RequestID) + case disperser.BlobStatus_FAILED, disperser.BlobStatus_UNKNOWN: + m.Log.Error("EigenDA blob dispersal failed in processing", "requestID", base64RequestID, "err", err) + return nil, nil, fmt.Errorf("EigenDA blob dispersal failed in processing, requestID=%s: %w", base64RequestID, err) + case disperser.BlobStatus_INSUFFICIENT_SIGNATURES: + m.Log.Error("EigenDA blob dispersal failed in processing with insufficient signatures", "requestID", base64RequestID, "err", err) + return nil, nil, fmt.Errorf("EigenDA blob dispersal failed in processing with insufficient signatures, requestID=%s: %w", base64RequestID, err) + case disperser.BlobStatus_CONFIRMED, disperser.BlobStatus_FINALIZED: + batchHeaderHashHex := fmt.Sprintf("0x%s", hex.EncodeToString(statusRes.Info.BlobVerificationProof.BatchMetadata.BatchHeaderHash)) + m.Log.Info("Successfully dispersed blob to EigenDA", "requestID", base64RequestID, "batchHeaderHash", batchHeaderHashHex) + return statusRes.Info, requestID, nil + default: + m.Log.Warn("Still waiting for confirmation from EigenDA", "requestID", base64RequestID) + } + } + } +} + +func (m *EigenDA) disperseBlobAuthenticated(ctx context.Context, data []byte) (*disperser.BlobStatus, []byte, error) { + config := &tls.Config{} + credential := credentials.NewTLS(config) + dialOptions := []grpc.DialOption{grpc.WithTransportCredentials(credential)} + conn, err := grpc.Dial(m.RPC, dialOptions...) + if err != nil { + return nil, nil, err + } + defer func() { _ = conn.Close() }() + daClient := disperser.NewDisperserClient(conn) + + ctxTimeout, cancel := context.WithTimeout(ctx, m.RPCTimeout) + defer cancel() + + stream, err := daClient.DisperseBlobAuthenticated(ctxTimeout) + if err != nil || stream == nil { + return nil, nil, fmt.Errorf("error while calling DisperseBlobAuthenticated: %w", err) + } + + encodedTxData := ConvertByPaddingEmptyByte(data) + + request := &disperser.DisperseBlobRequest{ + Data: encodedTxData, + AccountId: m.signer.GetAccountID(), + } + + // Send the initial request + err = stream.Send(&disperser.AuthenticatedRequest{Payload: &disperser.AuthenticatedRequest_DisperseRequest{ + DisperseRequest: request, + }}) + + if err != nil { + return nil, nil, fmt.Errorf("failed to send request: %w", err) + } + + // Get the Challenge + reply, err := stream.Recv() + if err != nil || reply == nil { + return nil, nil, fmt.Errorf("error while receiving: %w", err) + } + authHeaderReply, ok := reply.Payload.(*disperser.AuthenticatedReply_BlobAuthHeader) + if !ok { + return nil, nil, errors.New("expected challenge") + } + + authData, err := m.signer.SignBlobRequest(authHeaderReply.BlobAuthHeader.ChallengeParameter) + if err != nil { + return nil, nil, errors.New("error signing blob request") + } + + // Process challenge and send back challenge_reply + err = stream.Send(&disperser.AuthenticatedRequest{Payload: &disperser.AuthenticatedRequest_AuthenticationData{ + AuthenticationData: &disperser.AuthenticationData{ + AuthenticationData: authData, + }, + }}) + if err != nil { + return nil, nil, fmt.Errorf("failed to send challenge reply: %w", err) + } + + reply, err = stream.Recv() + if err != nil || reply == nil { + return nil, nil, fmt.Errorf("error while receiving final reply: %w", err) + } + disperseReply, ok := reply.Payload.(*disperser.AuthenticatedReply_DisperseReply) // Process the final disperse_reply + if !ok { + return nil, nil, errors.New("expected DisperseReply") + } + + status := disperseReply.DisperseReply.GetResult() + + return &status, disperseReply.DisperseReply.GetRequestId(), nil +} + +func (m *EigenDA) recordInterval(method string) func(error) { + if m.Metricer == nil { + return func(err error) {} + } + + return m.Metricer.RecordInterval(method) +} diff --git a/op-service/eigenda/encoding/constants.go b/op-service/eigenda/encoding/constants.go new file mode 100644 index 000000000..5922846cb --- /dev/null +++ b/op-service/eigenda/encoding/constants.go @@ -0,0 +1,73 @@ +package encoding + +import ( + "fmt" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +const BYTES_PER_SYMBOL = 32 + +const NUMBER_FR_SECURITY_BYTES = 32 + +func init() { + initGlobals() +} + +func ToFr(v string) fr.Element { + var out fr.Element + _, err := out.SetString(v) + if err != nil { + fmt.Println("Failed to initialize Root of Unity") + panic(err) + } + return out +} + +var Scale2RootOfUnity []fr.Element +var ZERO, ONE, TWO fr.Element +var MODULUS_MINUS1, MODULUS_MINUS1_DIV2, MODULUS_MINUS2 fr.Element +var INVERSE_TWO fr.Element + +// copied from https://github.com/adjoint-io/pairing/blob/master/src/Data/Pairing/BN254.hs +func initGlobals() { + Scale2RootOfUnity = []fr.Element{ + ToFr("1"), + ToFr("21888242871839275222246405745257275088548364400416034343698204186575808495616"), + ToFr("21888242871839275217838484774961031246007050428528088939761107053157389710902"), + ToFr("19540430494807482326159819597004422086093766032135589407132600596362845576832"), + ToFr("14940766826517323942636479241147756311199852622225275649687664389641784935947"), + ToFr("4419234939496763621076330863786513495701855246241724391626358375488475697872"), + ToFr("9088801421649573101014283686030284801466796108869023335878462724291607593530"), + ToFr("10359452186428527605436343203440067497552205259388878191021578220384701716497"), + ToFr("3478517300119284901893091970156912948790432420133812234316178878452092729974"), + ToFr("6837567842312086091520287814181175430087169027974246751610506942214842701774"), + ToFr("3161067157621608152362653341354432744960400845131437947728257924963983317266"), + ToFr("1120550406532664055539694724667294622065367841900378087843176726913374367458"), + ToFr("4158865282786404163413953114870269622875596290766033564087307867933865333818"), + ToFr("197302210312744933010843010704445784068657690384188106020011018676818793232"), + ToFr("20619701001583904760601357484951574588621083236087856586626117568842480512645"), + ToFr("20402931748843538985151001264530049874871572933694634836567070693966133783803"), + ToFr("421743594562400382753388642386256516545992082196004333756405989743524594615"), + ToFr("12650941915662020058015862023665998998969191525479888727406889100124684769509"), + ToFr("11699596668367776675346610687704220591435078791727316319397053191800576917728"), + ToFr("15549849457946371566896172786938980432421851627449396898353380550861104573629"), + ToFr("17220337697351015657950521176323262483320249231368149235373741788599650842711"), + ToFr("13536764371732269273912573961853310557438878140379554347802702086337840854307"), + ToFr("12143866164239048021030917283424216263377309185099704096317235600302831912062"), + ToFr("934650972362265999028062457054462628285482693704334323590406443310927365533"), + ToFr("5709868443893258075976348696661355716898495876243883251619397131511003808859"), + ToFr("19200870435978225707111062059747084165650991997241425080699860725083300967194"), + ToFr("7419588552507395652481651088034484897579724952953562618697845598160172257810"), + ToFr("2082940218526944230311718225077035922214683169814847712455127909555749686340"), + ToFr("19103219067921713944291392827692070036145651957329286315305642004821462161904"), + } + + ZERO.SetZero() + ONE.SetOne() + TWO.SetInt64(int64(2)) + MODULUS_MINUS1.Sub(&ZERO, &ONE) + MODULUS_MINUS1_DIV2.Div(&MODULUS_MINUS1, &TWO) + MODULUS_MINUS2.Sub(&ZERO, &TWO) + INVERSE_TWO.Inverse(&TWO) +} diff --git a/op-service/eigenda/encoding/data.go b/op-service/eigenda/encoding/data.go new file mode 100644 index 000000000..5c9df9982 --- /dev/null +++ b/op-service/eigenda/encoding/data.go @@ -0,0 +1,63 @@ +package encoding + +import ( + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +// Commitment is a polynomial commitment (e.g. a kzg commitment) +type G1Commitment bn254.G1Affine + +// Commitment is a polynomial commitment (e.g. a kzg commitment) +type G2Commitment bn254.G2Affine + +// LengthProof is a polynomial commitment on G2 (e.g. a kzg commitment) used for low degree proof +type LengthProof = G2Commitment + +// Proof is used to open a commitment. In the case of Kzg, this is also a kzg commitment, and is different from a Commitment only semantically. +type Proof = bn254.G1Affine + +// Symbol is a symbol in the field used for polynomial commitments +type Symbol = fr.Element + +// BlomCommitments contains the blob's commitment, degree proof, and the actual degree. +type BlobCommitments struct { + Commitment *G1Commitment `json:"commitment"` + LengthCommitment *G2Commitment `json:"length_commitment"` + LengthProof *LengthProof `json:"length_proof"` + Length uint `json:"length"` +} + +// Frame is a chunk of data with the associated multi-reveal proof +type Frame struct { + // Proof is the multireveal proof corresponding to the chunk + Proof Proof + // Coeffs contains the coefficience of the interpolating polynomial of the chunk + Coeffs []Symbol +} + +func (f *Frame) Length() int { + return len(f.Coeffs) +} + +// Size return the size of chunks in bytes. +func (f *Frame) Size() uint64 { + return uint64(f.Length() * BYTES_PER_SYMBOL) +} + +// Sample is a chunk with associated metadata used by the Universal Batch Verifier +type Sample struct { + Commitment *G1Commitment + Chunk *Frame + AssignmentIndex ChunkNumber + BlobIndex int +} + +// SubBatch is a part of the whole Batch with identical Encoding Parameters, i.e. (ChunkLength, NumChunk) +// Blobs with the same encoding parameters are collected in a single subBatch +type SubBatch struct { + Samples []Sample + NumBlobs int +} + +type ChunkNumber = uint diff --git a/op-service/eigenda/encoding/encoding.go b/op-service/eigenda/encoding/encoding.go new file mode 100644 index 000000000..5acd0539a --- /dev/null +++ b/op-service/eigenda/encoding/encoding.go @@ -0,0 +1,30 @@ +package encoding + +type Decoder interface { + // Decode takes in the chunks, indices, and encoding parameters and returns the decoded blob + Decode(chunks []*Frame, indices []ChunkNumber, params EncodingParams, inputSize uint64) ([]byte, error) +} + +type Prover interface { + Decoder + // Encode takes in a blob and returns the commitments and encoded chunks. The encoding will satisfy the property that + // for any number M such that M*params.ChunkLength > BlobCommitments.Length, then any set of M chunks will be sufficient to + // reconstruct the blob. + EncodeAndProve(data []byte, params EncodingParams) (BlobCommitments, []*Frame, error) +} + +type Verifier interface { + Decoder + + // VerifyChunks takes in the chunks, indices, commitments, and encoding parameters and returns an error if the chunks are invalid. + VerifyFrames(chunks []*Frame, indices []ChunkNumber, commitments BlobCommitments, params EncodingParams) error + + // VerifyBatch takes in the encoding parameters, samples and the number of blobs and returns an error if a chunk in any sample is invalid. + UniversalVerifySubBatch(params EncodingParams, samples []Sample, numBlobs int) error + + // VerifyBlobLength takes in the commitments and returns an error if the blob length is invalid. + VerifyBlobLength(commitments BlobCommitments) error + + // VerifyCommitEquivalence takes in a list of commitments and returns an error if the commitment of G1 and G2 are inconsistent + VerifyCommitEquivalenceBatch(commitments []BlobCommitments) error +} diff --git a/op-service/eigenda/encoding/fft/errors.go b/op-service/eigenda/encoding/fft/errors.go new file mode 100644 index 000000000..ea7efeadc --- /dev/null +++ b/op-service/eigenda/encoding/fft/errors.go @@ -0,0 +1,40 @@ +// This code is sourced from the go-kzg Repository by protolambda. +// Original code: https://github.com/protolambda/go-kzg +// MIT License +// +// Copyright (c) 2020 @protolambda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package fft + +import ( + "errors" +) + +var ErrZeroPolyTooLarge = errors.New("ErrZeroPolyTooLarge") +var ErrDestNotPowerOfTwo = errors.New("ErrDestNotPowerOfTwo") +var ErrEmptyLeaves = errors.New("ErrEmptyLeaves") +var ErrEmptyPoly = errors.New("ErrEmptyPoly") +var ErrNotEnoughScratch = errors.New("ErrNotEnoughScratch") +var ErrInvalidDestinationLength = errors.New("ErrInvalidDestinationLength") +var ErrDomainTooSmall = errors.New("ErrDomainTooSmall") +var ErrLengthNotPowerOfTwo = errors.New("ErrLengthNotPowerOfTwo") +var ErrInvalidPolyLengthTooLarge = errors.New("ErrInvalidPolyLengthTooLarge") +var ErrInvalidPolyLengthPowerOfTwo = errors.New("ErrInvalidPolyLengthPowerOfTwo") diff --git a/op-service/eigenda/encoding/fft/fft.go b/op-service/eigenda/encoding/fft/fft.go new file mode 100644 index 000000000..d970d2d2c --- /dev/null +++ b/op-service/eigenda/encoding/fft/fft.go @@ -0,0 +1,90 @@ +// This code is sourced from the go-kzg Repository by protolambda. +// Original code: https://github.com/protolambda/go-kzg +// MIT License +// +// Copyright (c) 2020 @protolambda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// Original: https://github.com/ethereum/research/blob/master/mimc_stark/fft.py + +package fft + +import ( + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + + "math/bits" +) + +// if not already a power of 2, return the next power of 2 +func nextPowOf2(v uint64) uint64 { + if v == 0 { + return 1 + } + return uint64(1) << bits.Len64(v-1) +} + +// Expands the power circle for a given root of unity to WIDTH+1 values. +// The first entry will be 1, the last entry will also be 1, +// for convenience when reversing the array (useful for inverses) +func expandRootOfUnity(rootOfUnity *fr.Element) []fr.Element { + rootz := make([]fr.Element, 2) + rootz[0].SetOne() // some unused number in py code + rootz[1] = *rootOfUnity + + for i := 1; !rootz[i].IsOne(); { + rootz = append(rootz, fr.Element{}) + this := &rootz[i] + i++ + rootz[i].Mul(this, rootOfUnity) + //bls.MulModFr(&rootz[i], this, rootOfUnity) + } + return rootz +} + +type FFTSettings struct { + MaxWidth uint64 + // the generator used to get all roots of unity + RootOfUnity *fr.Element + // domain, starting and ending with 1 (duplicate!) + ExpandedRootsOfUnity []fr.Element + // reverse domain, same as inverse values of domain. Also starting and ending with 1. + ReverseRootsOfUnity []fr.Element +} + +func NewFFTSettings(maxScale uint8) *FFTSettings { + width := uint64(1) << maxScale + root := &encoding.Scale2RootOfUnity[maxScale] + rootz := expandRootOfUnity(&encoding.Scale2RootOfUnity[maxScale]) + + // reverse roots of unity + rootzReverse := make([]fr.Element, len(rootz)) + copy(rootzReverse, rootz) + for i, j := uint64(0), uint64(len(rootz)-1); i < j; i, j = i+1, j-1 { + rootzReverse[i], rootzReverse[j] = rootzReverse[j], rootzReverse[i] + } + + return &FFTSettings{ + MaxWidth: width, + RootOfUnity: root, + ExpandedRootsOfUnity: rootz, + ReverseRootsOfUnity: rootzReverse, + } +} diff --git a/op-service/eigenda/encoding/fft/fft_fr.go b/op-service/eigenda/encoding/fft/fft_fr.go new file mode 100644 index 000000000..2fae4af69 --- /dev/null +++ b/op-service/eigenda/encoding/fft/fft_fr.go @@ -0,0 +1,137 @@ +// This code is sourced from the go-kzg Repository by protolambda. +// Original code: https://github.com/protolambda/go-kzg +// MIT License +// +// Copyright (c) 2020 @protolambda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +package fft + +import ( + "fmt" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +func (fs *FFTSettings) simpleFT(vals []fr.Element, valsOffset uint64, valsStride uint64, rootsOfUnity []fr.Element, rootsOfUnityStride uint64, out []fr.Element) { + l := uint64(len(out)) + var v fr.Element + var tmp fr.Element + var last fr.Element + for i := uint64(0); i < l; i++ { + jv := &vals[valsOffset] + r := &rootsOfUnity[0] + v.Mul(jv, r) + last.Set(&v) + + for j := uint64(1); j < l; j++ { + jv := &vals[valsOffset+j*valsStride] + r := &rootsOfUnity[((i*j)%l)*rootsOfUnityStride] + v.Mul(jv, r) + tmp.Set(&last) + last.Add(&tmp, &v) + } + out[i].Set(&last) + } +} + +func (fs *FFTSettings) _fft(vals []fr.Element, valsOffset uint64, valsStride uint64, rootsOfUnity []fr.Element, rootsOfUnityStride uint64, out []fr.Element) { + if len(out) <= 4 { // if the value count is small, run the unoptimized version instead. // TODO tune threshold. + fs.simpleFT(vals, valsOffset, valsStride, rootsOfUnity, rootsOfUnityStride, out) + return + } + + half := uint64(len(out)) >> 1 + // L will be the left half of out + fs._fft(vals, valsOffset, valsStride<<1, rootsOfUnity, rootsOfUnityStride<<1, out[:half]) + // R will be the right half of out + fs._fft(vals, valsOffset+valsStride, valsStride<<1, rootsOfUnity, rootsOfUnityStride<<1, out[half:]) // just take even again + + var yTimesRoot fr.Element + var x, y fr.Element + for i := uint64(0); i < half; i++ { + // temporary copies, so that writing to output doesn't conflict with input + x.Set(&out[i]) + y.Set(&out[i+half]) + + root := &rootsOfUnity[i*rootsOfUnityStride] + yTimesRoot.Mul(&y, root) + out[i].Add(&x, &yTimesRoot) + out[i+half].Sub(&x, &yTimesRoot) + } +} + +func (fs *FFTSettings) FFT(vals []fr.Element, inv bool) ([]fr.Element, error) { + n := uint64(len(vals)) + if n > fs.MaxWidth { + return nil, fmt.Errorf("got %d values but only have %d roots of unity", n, fs.MaxWidth) + } + n = nextPowOf2(n) + // We make a copy so we can mutate it during the work. + valsCopy := make([]fr.Element, n) + for i := 0; i < len(vals); i++ { + valsCopy[i].Set(&vals[i]) + + } + for i := uint64(len(vals)); i < n; i++ { + valsCopy[i].SetZero() + } + out := make([]fr.Element, n) + if err := fs.InplaceFFT(valsCopy, out, inv); err != nil { + return nil, err + } + return out, nil +} + +func (fs *FFTSettings) InplaceFFT(vals []fr.Element, out []fr.Element, inv bool) error { + n := uint64(len(vals)) + if n > fs.MaxWidth { + return fmt.Errorf("got %d values but only have %d roots of unity", n, fs.MaxWidth) + } + if !IsPowerOfTwo(n) { + return fmt.Errorf("got %d values but not a power of two", n) + } + if inv { + var invLen fr.Element + + invLen.SetInt64(int64(n)) + + invLen.Inverse(&invLen) + rootz := fs.ReverseRootsOfUnity[:fs.MaxWidth] + stride := fs.MaxWidth / n + + fs._fft(vals, 0, 1, rootz, stride, out) + var tmp fr.Element + for i := 0; i < len(out); i++ { + tmp.Mul(&out[i], &invLen) + out[i].Set(&tmp) + } + return nil + } else { + rootz := fs.ExpandedRootsOfUnity[:fs.MaxWidth] + stride := fs.MaxWidth / n + // Regular FFT + fs._fft(vals, 0, 1, rootz, stride, out) + return nil + } +} + +func IsPowerOfTwo(v uint64) bool { + return v&(v-1) == 0 +} diff --git a/op-service/eigenda/encoding/fft/fft_fr_test.go b/op-service/eigenda/encoding/fft/fft_fr_test.go new file mode 100644 index 000000000..a9c60b1dd --- /dev/null +++ b/op-service/eigenda/encoding/fft/fft_fr_test.go @@ -0,0 +1,102 @@ +// This code is sourced from the go-kzg Repository by protolambda. +// Original code: https://github.com/protolambda/go-kzg +// MIT License +// +// Copyright (c) 2020 @protolambda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +package fft + +import ( + "testing" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFFTRoundtrip(t *testing.T) { + fs := NewFFTSettings(4) + data := make([]fr.Element, fs.MaxWidth) + for i := uint64(0); i < fs.MaxWidth; i++ { + data[i].SetInt64(int64(i)) + } + coeffs, err := fs.FFT(data, false) + require.Nil(t, err) + require.NotNil(t, coeffs) + + res, err := fs.FFT(coeffs, true) + require.Nil(t, err) + require.NotNil(t, coeffs) + + for i := range res { + assert.True(t, res[i].Equal(&data[i])) + } + +} + +func TestInvFFT(t *testing.T) { + fs := NewFFTSettings(4) + data := make([]fr.Element, fs.MaxWidth) + for i := uint64(0); i < fs.MaxWidth; i++ { + data[i].SetInt64(int64(i)) + } + + res, err := fs.FFT(data, true) + require.Nil(t, err) + require.NotNil(t, res) + + expected := make([]fr.Element, 16) + _, err = expected[0].SetString("10944121435919637611123202872628637544274182200208017171849102093287904247816") + require.Nil(t, err) + _, err = expected[1].SetString("1936030771851033959223912058450265953781825736913396623629635806885115007405") + require.Nil(t, err) + _, err = expected[2].SetString("16407567355707715082381689537916387329395994555403796510305004205827931381005") + require.Nil(t, err) + _, err = expected[3].SetString("10191068092603585790326358584923261075982428954421092317052884890230353083980") + require.Nil(t, err) + _, err = expected[4].SetString("21888242871839275220042445260109153167277707414472061641729655619866599103259") + require.Nil(t, err) + _, err = expected[5].SetString("21152419124866706061239949059012548909204540700669677175965090584889269743773") + require.Nil(t, err) + _, err = expected[6].SetString("16407567355707715086789610508212631171937308527291741914242101339246350165720") + require.Nil(t, err) + _, err = expected[7].SetString("12897381804114154238953344473132041472086565426937872290416035768380869236628") + require.Nil(t, err) + _, err = expected[8].SetString("10944121435919637611123202872628637544274182200208017171849102093287904247808") + require.Nil(t, err) + _, err = expected[9].SetString("8990861067725120983293061272125233616461798973478162053282168418194939258988") + require.Nil(t, err) + _, err = expected[10].SetString("5480675516131560135456795237044643916611055873124292429456102847329458329896") + require.Nil(t, err) + _, err = expected[11].SetString("735823746972569161006456686244726179343823699746357167733113601686538751843") + require.Nil(t, err) + _, err = expected[12].SetString("2203960485148121921270656985943972701968548566709209392357") + require.Nil(t, err) + _, err = expected[13].SetString("11697174779235689431920047160334014012565935445994942026645319296345455411636") + require.Nil(t, err) + _, err = expected[14].SetString("5480675516131560139864716207340887759152369845012237833393199980747877114611") + require.Nil(t, err) + _, err = expected[15].SetString("19952212099988241263022493686807009134766538663502637720068568379690693488211") + require.Nil(t, err) + + for i := range res { + assert.True(t, res[i].Equal(&expected[i])) + } +} diff --git a/op-service/eigenda/encoding/fft/fft_g1.go b/op-service/eigenda/encoding/fft/fft_g1.go new file mode 100644 index 000000000..28b55a07f --- /dev/null +++ b/op-service/eigenda/encoding/fft/fft_g1.go @@ -0,0 +1,137 @@ +// This code is sourced from the go-kzg Repository by protolambda. +// Original code: https://github.com/protolambda/go-kzg +// MIT License +// +// Copyright (c) 2020 @protolambda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//go:build !bignum_pure && !bignum_hol256 +// +build !bignum_pure,!bignum_hol256 + +package fft + +import ( + "fmt" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +func (fs *FFTSettings) simpleFTG1(vals []bn254.G1Affine, valsOffset uint64, valsStride uint64, rootsOfUnity []fr.Element, rootsOfUnityStride uint64, out []bn254.G1Affine) { + l := uint64(len(out)) + var v bn254.G1Affine + var tmp bn254.G1Affine + var last bn254.G1Affine + for i := uint64(0); i < l; i++ { + jv := &vals[valsOffset] + r := &rootsOfUnity[0] + + var t big.Int + r.BigInt(&t) + v.ScalarMultiplication(jv, &t) + + last.Set(&v) + + for j := uint64(1); j < l; j++ { + jv := &vals[valsOffset+j*valsStride] + r := &rootsOfUnity[((i*j)%l)*rootsOfUnityStride] + + var t big.Int + r.BigInt(&t) + v.ScalarMultiplication(jv, &t) + tmp.Set(&last) + last.Add(&tmp, &v) + } + out[i].Set(&last) + + } +} + +func (fs *FFTSettings) _fftG1(vals []bn254.G1Affine, valsOffset uint64, valsStride uint64, rootsOfUnity []fr.Element, rootsOfUnityStride uint64, out []bn254.G1Affine) { + if len(out) <= 4 { // if the value count is small, run the unoptimized version instead. // TODO tune threshold. (can be different for G1) + fs.simpleFTG1(vals, valsOffset, valsStride, rootsOfUnity, rootsOfUnityStride, out) + return + } + + half := uint64(len(out)) >> 1 + // L will be the left half of out + fs._fftG1(vals, valsOffset, valsStride<<1, rootsOfUnity, rootsOfUnityStride<<1, out[:half]) + // R will be the right half of out + fs._fftG1(vals, valsOffset+valsStride, valsStride<<1, rootsOfUnity, rootsOfUnityStride<<1, out[half:]) // just take even again + + var yTimesRoot bn254.G1Affine + var x, y bn254.G1Affine + for i := uint64(0); i < half; i++ { + // temporary copies, so that writing to output doesn't conflict with input + x.Set(&out[i]) + y.Set(&out[i+half]) + + root := &rootsOfUnity[i*rootsOfUnityStride] + + yTimesRoot.ScalarMultiplication(&y, root.BigInt(new(big.Int))) + + out[i].Add(&x, &yTimesRoot) + out[i+half].Sub(&x, &yTimesRoot) + + } +} + +func (fs *FFTSettings) FFTG1(vals []bn254.G1Affine, inv bool) ([]bn254.G1Affine, error) { + n := uint64(len(vals)) + if n > fs.MaxWidth { + return nil, fmt.Errorf("got %d values but only have %d roots of unity", n, fs.MaxWidth) + } + + if !IsPowerOfTwo(n) { + return nil, fmt.Errorf("got %d values but not a power of two", n) + } + // We make a copy so we can mutate it during the work. + valsCopy := make([]bn254.G1Affine, n) + for i := 0; i < len(vals); i++ { // TODO: maybe optimize this away, and write back to original input array? + + valsCopy[i].Set(&vals[i]) + } + if inv { + var invLen fr.Element + + invLen.SetUint64(n) + + invLen.Inverse(&invLen) + + rootz := fs.ReverseRootsOfUnity[:fs.MaxWidth] + stride := fs.MaxWidth / n + + out := make([]bn254.G1Affine, n) + fs._fftG1(valsCopy, 0, 1, rootz, stride, out) + + for i := 0; i < len(out); i++ { + out[i].ScalarMultiplication(&out[i], invLen.BigInt(new(big.Int))) + } + return out, nil + } else { + out := make([]bn254.G1Affine, n) + rootz := fs.ExpandedRootsOfUnity[:fs.MaxWidth] + stride := fs.MaxWidth / n + // Regular FFT + fs._fftG1(valsCopy, 0, 1, rootz, stride, out) + return out, nil + } +} diff --git a/op-service/eigenda/encoding/fft/recover_from_samples.go b/op-service/eigenda/encoding/fft/recover_from_samples.go new file mode 100644 index 000000000..102626b45 --- /dev/null +++ b/op-service/eigenda/encoding/fft/recover_from_samples.go @@ -0,0 +1,153 @@ +// This code is sourced from the go-kzg Repository by protolambda. +// Original code: https://github.com/protolambda/go-kzg +// MIT License +// +// Copyright (c) 2020 @protolambda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package fft + +import ( + "errors" + "fmt" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +// unshift poly, in-place. Multiplies each coeff with 1/shift_factor**i +func (fs *FFTSettings) ShiftPoly(poly []fr.Element) { + var shiftFactor fr.Element + shiftFactor.SetInt64(int64(5)) + var factorPower fr.Element + factorPower.SetOne() + var invFactor fr.Element + invFactor.Inverse(&shiftFactor) + var tmp fr.Element + for i := 0; i < len(poly); i++ { + + tmp.Set(&poly[i]) + + poly[i].Mul(&tmp, &factorPower) + + // TODO: pre-compute all these shift scalars + + tmp.Set(&factorPower) + + factorPower.Mul(&tmp, &invFactor) + } +} + +// unshift poly, in-place. Multiplies each coeff with shift_factor**i +func (fs *FFTSettings) UnshiftPoly(poly []fr.Element) { + var shiftFactor fr.Element + + shiftFactor.SetInt64(int64(5)) + var factorPower fr.Element + factorPower.SetOne() + + var tmp fr.Element + for i := 0; i < len(poly); i++ { + tmp.Set(&poly[i]) + + poly[i].Mul(&tmp, &factorPower) + + // TODO: pre-compute all these shift scalars + + tmp.Set(&factorPower) + + factorPower.Mul(&tmp, &shiftFactor) + } +} + +func (fs *FFTSettings) RecoverPolyFromSamples(samples []*fr.Element, zeroPolyFn ZeroPolyFn) ([]fr.Element, error) { + // TODO: using a single additional temporary array, all the FFTs can run in-place. + + missingIndices := make([]uint64, 0, len(samples)) + for i, s := range samples { + if s == nil { + missingIndices = append(missingIndices, uint64(i)) + } + } + + zeroEval, zeroPoly, err := zeroPolyFn(missingIndices, uint64(len(samples))) + if err != nil { + return nil, err + } + + for i, s := range samples { + if (s == nil) != zeroEval[i].IsZero() { + return nil, errors.New("bad zero eval") + } + } + + polyEvaluationsWithZero := make([]fr.Element, len(samples)) + for i, s := range samples { + if s == nil { + + polyEvaluationsWithZero[i].SetZero() + } else { + + polyEvaluationsWithZero[i].Mul(s, &zeroEval[i]) + } + } + polyWithZero, err := fs.FFT(polyEvaluationsWithZero, true) + if err != nil { + return nil, err + } + // shift in-place + fs.ShiftPoly(polyWithZero) + shiftedPolyWithZero := polyWithZero + + fs.ShiftPoly(zeroPoly) + shiftedZeroPoly := zeroPoly + + evalShiftedPolyWithZero, err := fs.FFT(shiftedPolyWithZero, false) + if err != nil { + return nil, err + } + evalShiftedZeroPoly, err := fs.FFT(shiftedZeroPoly, false) + if err != nil { + return nil, err + } + + evalShiftedReconstructedPoly := evalShiftedPolyWithZero + for i := 0; i < len(evalShiftedReconstructedPoly); i++ { + + evalShiftedReconstructedPoly[i].Div(&evalShiftedPolyWithZero[i], &evalShiftedZeroPoly[i]) + } + shiftedReconstructedPoly, err := fs.FFT(evalShiftedReconstructedPoly, true) + if err != nil { + return nil, err + } + fs.UnshiftPoly(shiftedReconstructedPoly) + reconstructedPoly := shiftedReconstructedPoly + + reconstructedData, err := fs.FFT(reconstructedPoly, false) + if err != nil { + return nil, err + } + + for i, s := range samples { + if s != nil && !reconstructedData[i].Equal(s) { + return nil, fmt.Errorf("failed to reconstruct data correctly, changed value at index %d. Expected: %s, got: %s", i, s.String(), reconstructedData[i].String()) + } + } + return reconstructedData, nil +} diff --git a/op-service/eigenda/encoding/fft/recover_from_samples_test.go b/op-service/eigenda/encoding/fft/recover_from_samples_test.go new file mode 100644 index 000000000..021f48fca --- /dev/null +++ b/op-service/eigenda/encoding/fft/recover_from_samples_test.go @@ -0,0 +1,145 @@ +// This code is sourced from the go-kzg Repository by protolambda. +// Original code: https://github.com/protolambda/go-kzg +// MIT License +// +// Copyright (c) 2020 @protolambda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package fft + +import ( + "fmt" + "math/rand" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFFTSettings_RecoverPolyFromSamples_Simple(t *testing.T) { + // Create some random data, with padding... + fs := NewFFTSettings(2) + poly := make([]fr.Element, fs.MaxWidth) + for i := uint64(0); i < fs.MaxWidth/2; i++ { + poly[i].SetInt64(int64(i)) + } + for i := fs.MaxWidth / 2; i < fs.MaxWidth; i++ { + poly[i].SetZero() + } + + // Get data for polynomial SLOW_INDICES + data, err := fs.FFT(poly, false) + require.Nil(t, err) + + subset := make([]*fr.Element, fs.MaxWidth) + subset[0] = &data[0] + subset[3] = &data[3] + + recovered, err := fs.RecoverPolyFromSamples(subset, fs.ZeroPolyViaMultiplication) + require.Nil(t, err) + + for i := range recovered { + assert.True(t, recovered[i].Equal(&data[i]), + "recovery at index %d got %s but expected %s", i, recovered[i].String(), data[i].String()) + } + + // And recover the original coeffs for good measure + back, err := fs.FFT(recovered, true) + require.Nil(t, err) + + for i := uint64(0); i < fs.MaxWidth/2; i++ { + assert.True(t, back[i].Equal(&poly[i]), + "coeff at index %d got %s but expected %s", i, back[i].String(), poly[i].String()) + } + + for i := fs.MaxWidth / 2; i < fs.MaxWidth; i++ { + assert.True(t, back[i].IsZero(), + "expected zero padding in index %d", i) + } +} + +func TestFFTSettings_RecoverPolyFromSamples(t *testing.T) { + // Create some random poly, with padding so we get redundant data + fs := NewFFTSettings(10) + poly := make([]fr.Element, fs.MaxWidth) + for i := uint64(0); i < fs.MaxWidth/2; i++ { + poly[i].SetInt64(int64(i)) + } + for i := fs.MaxWidth / 2; i < fs.MaxWidth; i++ { + poly[i].SetZero() + } + + // Get coefficients for polynomial SLOW_INDICES + data, err := fs.FFT(poly, false) + require.Nil(t, err) + + // Util to pick a random subnet of the values + randomSubset := func(known uint64, rngSeed uint64) []*fr.Element { + withMissingValues := make([]*fr.Element, fs.MaxWidth) + for i := range data { + withMissingValues[i] = &data[i] + } + rng := rand.New(rand.NewSource(int64(rngSeed))) + missing := fs.MaxWidth - known + pruned := rng.Perm(int(fs.MaxWidth))[:missing] + for _, i := range pruned { + withMissingValues[i] = nil + } + return withMissingValues + } + + // Try different amounts of known indices, and try it in multiple random ways + var lastKnown uint64 = 0 + for knownRatio := 0.7; knownRatio < 1.0; knownRatio += 0.05 { + known := uint64(float64(fs.MaxWidth) * knownRatio) + if known == lastKnown { + continue + } + lastKnown = known + for i := 0; i < 3; i++ { + t.Run(fmt.Sprintf("random_subset_%d_known_%d", i, known), func(t *testing.T) { + subset := randomSubset(known, uint64(i)) + + recovered, err := fs.RecoverPolyFromSamples(subset, fs.ZeroPolyViaMultiplication) + require.Nil(t, err) + + for i := range recovered { + assert.True(t, recovered[i].Equal(&data[i]), + "recovery at index %d got %s but expected %s", i, recovered[i].String(), data[i].String()) + } + + // And recover the original coeffs for good measure + back, err := fs.FFT(recovered, true) + require.Nil(t, err) + + half := uint64(len(back)) / 2 + for i := uint64(0); i < half; i++ { + assert.True(t, back[i].Equal(&poly[i]), + "coeff at index %d got %s but expected %s", i, back[i].String(), poly[i].String()) + } + for i := half; i < fs.MaxWidth; i++ { + assert.True(t, back[i].IsZero(), + "expected zero padding in index %d", i) + } + }) + } + } +} diff --git a/op-service/eigenda/encoding/fft/zero_poly.go b/op-service/eigenda/encoding/fft/zero_poly.go new file mode 100644 index 000000000..9394e240f --- /dev/null +++ b/op-service/eigenda/encoding/fft/zero_poly.go @@ -0,0 +1,303 @@ +// This code is sourced from the go-kzg Repository by protolambda. +// Original code: https://github.com/protolambda/go-kzg +// MIT License +// +// Copyright (c) 2020 @protolambda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// Original: https://github.com/ethereum/research/blob/master/polynomial_reconstruction/polynomial_reconstruction.py +// Changes: +// - flattened leaf construction, +// - no aggressive poly truncation +// - simplified merges +// - no heap allocations during reduction + +package fft + +import ( + "log" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +type ZeroPolyFn func(missingIndices []uint64, length uint64) ([]fr.Element, []fr.Element, error) + +func (fs *FFTSettings) makeZeroPolyMulLeaf(dst []fr.Element, indices []uint64, domainStride uint64) error { + if len(dst) < len(indices)+1 { + log.Printf("expected bigger destination length: %d, got: %d", len(indices)+1, len(dst)) + return ErrInvalidDestinationLength + } + // zero out the unused slots + for i := len(indices) + 1; i < len(dst); i++ { + dst[i].SetZero() + + } + + dst[len(indices)].SetOne() + var negDi fr.Element + + var frZero fr.Element + frZero.SetZero() + + for i, v := range indices { + + negDi.Sub(&frZero, &fs.ExpandedRootsOfUnity[v*domainStride]) + + dst[i].Set(&negDi) + if i > 0 { + + dst[i].Add(&dst[i], &dst[i-1]) + for j := i - 1; j > 0; j-- { + dst[j].Mul(&dst[j], &negDi) + + dst[j].Add(&dst[j], &dst[j-1]) + } + + dst[0].Mul(&dst[0], &negDi) + } + } + return nil +} + +// Copy all of the values of poly into out, and fill the remainder of out with zeroes. +func padPoly(out []fr.Element, poly []fr.Element) { + for i := 0; i < len(poly); i++ { + + out[i].Set(&poly[i]) + } + for i := len(poly); i < len(out); i++ { + + out[i].SetZero() + } +} + +// Calculate the product of the input polynomials via convolution. +// Pad the polynomials in ps, perform FFTs, point-wise multiply the results together, +// and apply an inverse FFT to the result. +// +// The scratch space must be at least 3 times the output space. +// The output must have a power of 2 length. +// The input polynomials must not be empty, and sum to no larger than the output. +func (fs *FFTSettings) reduceLeaves(scratch []fr.Element, dst []fr.Element, ps [][]fr.Element) ([]fr.Element, error) { + n := uint64(len(dst)) + if !IsPowerOfTwo(n) { + log.Println("destination must be a power of two") + return nil, ErrDestNotPowerOfTwo + } + if len(ps) == 0 { + log.Println("empty leaves") + return nil, ErrEmptyLeaves + } + // The degree of the output polynomial is the sum of the degrees of the input polynomials. + outDegree := uint64(0) + for _, p := range ps { + if len(p) == 0 { + log.Println("empty input poly") + return nil, ErrEmptyPoly + } + outDegree += uint64(len(p)) - 1 + } + if min := outDegree + 1; min > n { + log.Printf("expected larger destination length: %d, got: %d", min, n) + return nil, ErrInvalidDestinationLength + } + if uint64(len(scratch)) < 3*n { + log.Println("not enough scratch space") + return nil, ErrNotEnoughScratch + } + // Split `scratch` up into three equally sized working arrays + pPadded := scratch[:n] + mulEvalPs := scratch[n : 2*n] + pEval := scratch[2*n : 3*n] + + // Do the last partial first: it is no longer than the others and the padding can remain in place for the rest. + last := uint64(len(ps) - 1) + padPoly(pPadded, ps[last]) + if err := fs.InplaceFFT(pPadded, mulEvalPs, false); err != nil { + return nil, err + } + for i := uint64(0); i < last; i++ { + p := ps[i] + for j := 0; j < len(p); j++ { + + pPadded[j].Set(&p[j]) + } + if err := fs.InplaceFFT(pPadded, pEval, false); err != nil { + return nil, err + } + for j := uint64(0); j < n; j++ { + mulEvalPs[j].Mul(&mulEvalPs[j], &pEval[j]) + + } + } + if err := fs.InplaceFFT(mulEvalPs, dst, true); err != nil { + return nil, err + } + return dst[:outDegree+1], nil +} + +// Calculate the minimal polynomial that evaluates to zero for powers of roots of unity that correspond to missing +// indices. +// +// This is done simply by multiplying together `(x - r^i)` for all the `i` that are missing indices, using a combination +// of direct multiplication (makeZeroPolyMulLeaf) and iterated multiplication via convolution (reduceLeaves) +// +// Also calculates the FFT (the "evaluation polynomial"). +func (fs *FFTSettings) ZeroPolyViaMultiplication(missingIndices []uint64, length uint64) ([]fr.Element, []fr.Element, error) { + if len(missingIndices) == 0 { + return make([]fr.Element, length), make([]fr.Element, length), nil + } + if length > fs.MaxWidth { + log.Println("domain too small for requested length") + return nil, nil, ErrDomainTooSmall + } + if !IsPowerOfTwo(length) { + log.Println("length not a power of two") + return nil, nil, ErrLengthNotPowerOfTwo + } + domainStride := fs.MaxWidth / length + perLeafPoly := uint64(64) + // just under a power of two, since the leaf gets 1 bigger after building a poly for it + perLeaf := perLeafPoly - 1 + + // If the work is as small as a single leaf, don't bother with tree reduction + if uint64(len(missingIndices)) <= perLeaf { + zeroPoly := make([]fr.Element, len(missingIndices)+1, length) + err := fs.makeZeroPolyMulLeaf(zeroPoly, missingIndices, domainStride) + if err != nil { + return nil, nil, err + } + // pad with zeroes (capacity is already there) + zeroPoly = zeroPoly[:length] + zeroEval, err := fs.FFT(zeroPoly, false) + if err != nil { + return nil, nil, err + } + return zeroEval, zeroPoly, nil + } + + leafCount := (uint64(len(missingIndices)) + perLeaf - 1) / perLeaf + n := nextPowOf2(leafCount * perLeafPoly) + + // The assumption here is that if the output is a power of two length, matching the sum of child leaf lengths, + // then the space can be reused. + out := make([]fr.Element, n) + + // Build the leaves. + + // Just the headers, a leaf re-uses the output space. + // Combining leaves can be done mostly in-place, using a scratchpad. + leaves := make([][]fr.Element, leafCount) + + offset := uint64(0) + outOffset := uint64(0) + max := uint64(len(missingIndices)) + for i := uint64(0); i < leafCount; i++ { + end := offset + perLeaf + if end > max { + end = max + } + leaves[i] = out[outOffset : outOffset+perLeafPoly] + err := fs.makeZeroPolyMulLeaf(leaves[i], missingIndices[offset:end], domainStride) + if err != nil { + return nil, nil, err + } + offset += perLeaf + outOffset += perLeafPoly + } + + // Now reduce all the leaves to a single poly + + // must be a power of 2 + reductionFactor := uint64(4) + scratch := make([]fr.Element, n*3) + + // from bottom to top, start reducing leaves. + for len(leaves) > 1 { + reducedCount := (uint64(len(leaves)) + reductionFactor - 1) / reductionFactor + // all the leaves are the same. Except possibly the last leaf, but that's ok. + leafSize := nextPowOf2(uint64(len(leaves[0]))) + for i := uint64(0); i < reducedCount; i++ { + start := i * reductionFactor + end := start + reductionFactor + // E.g. if we *started* with 2 leaves, we won't have more than that since it is already a power of 2. + // If we had 3, it would have been rounded up anyway. So just pick the end + outEnd := end * leafSize + if outEnd > uint64(len(out)) { + outEnd = uint64(len(out)) + } + reduced := out[start*leafSize : outEnd] + // unlike reduced output, input may be smaller than the amount that aligns with powers of two + if end > uint64(len(leaves)) { + end = uint64(len(leaves)) + } + leavesSlice := leaves[start:end] + var err error + if end > start+1 { + reduced, err = fs.reduceLeaves(scratch, reduced, leavesSlice) + if err != nil { + return nil, nil, err + } + } + leaves[i] = reduced + } + leaves = leaves[:reducedCount] + } + zeroPoly := leaves[0] + if zl := uint64(len(zeroPoly)); zl < length { + zeroPoly = append(zeroPoly, make([]fr.Element, length-zl)...) + } else if zl > length { + log.Println("expected output smaller or equal to input length") + return nil, nil, ErrZeroPolyTooLarge + } + + zeroEval, err := fs.FFT(zeroPoly, false) + if err != nil { + return nil, nil, err + } + + return zeroEval, zeroPoly, nil +} + +func EvalPolyAt(dst *fr.Element, coeffs []fr.Element, x *fr.Element) { + if len(coeffs) == 0 { + + dst.SetZero() + return + } + if x.IsZero() { + + dst.Set(&coeffs[0]) + return + } + // Horner's method: work backwards, avoid doing more than N multiplications + // https://en.wikipedia.org/wiki/Horner%27s_method + var last fr.Element + + last.Set(&coeffs[len(coeffs)-1]) + var tmp fr.Element + for i := len(coeffs) - 2; i >= 0; i-- { + tmp.Mul(&last, x) + + last.Add(&tmp, &coeffs[i]) + } + + dst.Set(&last) +} diff --git a/op-service/eigenda/encoding/fft/zero_poly_test.go b/op-service/eigenda/encoding/fft/zero_poly_test.go new file mode 100644 index 000000000..04dd8dda2 --- /dev/null +++ b/op-service/eigenda/encoding/fft/zero_poly_test.go @@ -0,0 +1,290 @@ +// This code is sourced from the go-kzg Repository by protolambda. +// Original code: https://github.com/protolambda/go-kzg +// MIT License +// +// Copyright (c) 2020 @protolambda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package fft + +import ( + "fmt" + "math/rand" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + + "github.com/stretchr/testify/assert" +) + +func TestFFTSettings_reduceLeaves(t *testing.T) { + fs := NewFFTSettings(4) + + var fromTreeReduction []fr.Element + { + // prepare some leaves + leaves := [][]fr.Element{make([]fr.Element, 3), make([]fr.Element, 3), make([]fr.Element, 3), make([]fr.Element, 3)} + leafIndices := [][]uint64{{1, 3}, {7, 8}, {9, 10}, {12, 13}} + for i := 0; i < 4; i++ { + err := fs.makeZeroPolyMulLeaf(leaves[i], leafIndices[i], 1) + assert.Nil(t, err) + } + + dst := make([]fr.Element, 16) + scratch := make([]fr.Element, 16*3) + _, err := fs.reduceLeaves(scratch, dst, leaves) + if err != nil { + assert.Nil(t, err) + } + fromTreeReduction = dst[:2*4+1] + } + + var fromDirect []fr.Element + { + dst := make([]fr.Element, 9) + indices := []uint64{1, 3, 7, 8, 9, 10, 12, 13} + err := fs.makeZeroPolyMulLeaf(dst, indices, 1) + if err != nil { + assert.Nil(t, err) + } + fromDirect = dst + } + assert.Equal(t, len(fromDirect), len(fromTreeReduction), "length mismatch") + + for i := 0; i < len(fromDirect); i++ { + a, b := &fromDirect[i], &fromTreeReduction[i] + if !a.Equal(b) { + t.Errorf("zero poly coeff %d is different. direct: %s, tree: %s", i, a.String(), b.String()) + } + assert.True(t, a.Equal(b), + "zero poly coeff %d is different. direct: %s, tree: %s", i, a.String(), b.String()) + } +} + +func TestFFTSettings_reduceLeaves_parametrized(t *testing.T) { + ratios := []float64{0.01, 0.1, 0.2, 0.4, 0.5, 0.7, 0.9, 0.99} + for scale := uint8(5); scale < 13; scale++ { + t.Run(fmt.Sprintf("scale_%d", scale), func(t *testing.T) { + for i, ratio := range ratios { + t.Run(fmt.Sprintf("ratio_%.3f", ratio), func(t *testing.T) { + seed := int64(1000*int(scale) + i) + testReduceLeaves(scale, ratio, seed, t) + }) + } + }) + } +} + +func testReduceLeaves(scale uint8, missingRatio float64, seed int64, t *testing.T) { + fs := NewFFTSettings(scale) + rng := rand.New(rand.NewSource(seed)) + pointCount := uint64(1) << scale + missingCount := uint64(int(float64(pointCount) * missingRatio)) + if missingCount == 0 { + return // nothing missing + } + + // select the missing points + missing := make([]uint64, pointCount) + for i := uint64(0); i < pointCount; i++ { + missing[i] = i + } + rng.Shuffle(int(pointCount), func(i, j int) { + missing[i], missing[j] = missing[j], missing[i] + }) + missing = missing[:missingCount] + + // build the leaves + pointsPerLeaf := uint64(63) + leafCount := (missingCount + pointsPerLeaf - 1) / pointsPerLeaf + leaves := make([][]fr.Element, leafCount) + for i := uint64(0); i < leafCount; i++ { + start := i * pointsPerLeaf + end := start + pointsPerLeaf + if end > missingCount { + end = missingCount + } + leafSize := end - start + leaf := make([]fr.Element, leafSize+1) + indices := make([]uint64, leafSize) + for j := uint64(0); j < leafSize; j++ { + indices[j] = missing[i*pointsPerLeaf+j] + } + err := fs.makeZeroPolyMulLeaf(leaf, indices, 1) + assert.Nil(t, err) + leaves[i] = leaf + } + + var fromTreeReduction []fr.Element + { + dst := make([]fr.Element, pointCount) + scratch := make([]fr.Element, pointCount*3) + _, err := fs.reduceLeaves(scratch, dst, leaves) + if err != nil { + assert.Nil(t, err) + } + fromTreeReduction = dst[:missingCount+1] + } + + var fromDirect []fr.Element + { + dst := make([]fr.Element, missingCount+1) + err := fs.makeZeroPolyMulLeaf(dst, missing, fs.MaxWidth/pointCount) + assert.Nil(t, err) + fromDirect = dst + } + assert.Equal(t, len(fromDirect), len(fromTreeReduction), "length mismatch") + + for i := 0; i < len(fromDirect); i++ { + a, b := &fromDirect[i], &fromTreeReduction[i] + assert.True(t, a.Equal(b), + "zero poly coeff %d is different. direct: %s, tree: %s", i, a.String(), b.String()) + } +} + +// TODO: Make pass +// func TestFFTSettings_ZeroPolyViaMultiplication_Python(t *testing.T) { +// fs := NewFFTSettings(4) + +// exists := []bool{ +// true, false, false, true, +// false, true, true, false, +// false, false, true, true, +// false, true, false, true, +// } +// var missingIndices []uint64 +// for i, v := range exists { +// if !v { +// missingIndices = append(missingIndices, uint64(i)) +// } +// } + +// zeroEval, zeroPoly, _ := fs.ZeroPolyViaMultiplication(missingIndices, uint64(len(exists))) + +// // produced from python implementation, check it's exactly correct. +// expectedEval := []fr.Element{ +// bls.ToFr("40868503138626303263713448452028063093974861640573380501185290423282553381059"), +// bls.ToFr("0"), +// bls.ToFr("0"), +// bls.ToFr("9059493333851894280622930192031068801018187410981018272280547403745554404951"), +// bls.ToFr("0"), +// bls.ToFr("589052107338478098858761185551735055781651813398303959420821217298541933174"), +// bls.ToFr("1980700778768058987161339158728243463014673552245301202287722613196911807966"), +// bls.ToFr("0"), +// bls.ToFr("0"), +// bls.ToFr("0"), +// bls.ToFr("48588946696503834689243119316363329218956542308951664733900338765742108388091"), +// bls.ToFr("17462668815085674001076443909983570919844170615339489499875900337907893054793"), +// bls.ToFr("0"), +// bls.ToFr("32986316229085390499922301497961243665601583888595873281538162159212447231217"), +// bls.ToFr("0"), +// bls.ToFr("31340620128536760059637470141592017333700483773455661424257920684057136952965"), +// } + +// for i := range zeroEval { +// fmt.Println(expectedEval[i]) +// assert.True(t, bls.EqualFr(&expectedEval[i], &zeroEval[i]), +// "at eval %d, expected: %s, got: %s", i, fr.ElementStr(&expectedEval[i]), fr.ElementStr(&zeroEval[i])) +// } + +// expectedPoly := []fr.Element{ +// bls.ToFr("37647706414300369857238608619982937390838535937985112215973498325246987289395"), +// bls.ToFr("2249310547870908874251949653552971443359134481191188461034956129255788965773"), +// bls.ToFr("14214218681578879810156974734536988864583938194339599855352132142401756507144"), +// bls.ToFr("11562429031388751544281783289945994468702719673309534612868555280828261838388"), +// bls.ToFr("38114263339263944057999429128256535679768370097817780187577397655496877536510"), +// bls.ToFr("21076784030567214561538347586500535789557219054084066119912281151549494675620"), +// bls.ToFr("9111875896859243625633322505516518368332415340935654725595105138403527134249"), +// bls.ToFr("11763665547049371891508513950107512764213633861965719968078681999977021803005"), +// bls.ToFr("1"), +// bls.ToFr("0"), +// bls.ToFr("0"), +// bls.ToFr("0"), +// bls.ToFr("0"), +// bls.ToFr("0"), +// bls.ToFr("0"), +// bls.ToFr("0"), +// } + +// for i := range zeroPoly { +// assert.True(t, bls.EqualFr(&expectedPoly[i], &zeroPoly[i]), +// "at poly %d, expected: %s, got: %s", i, fr.ElementStr(&expectedPoly[i]), fr.ElementStr(&zeroPoly[i])) +// } +// } + +func testZeroPoly(t *testing.T, scale uint8, seed int64) { + fs := NewFFTSettings(scale) + + rng := rand.New(rand.NewSource(seed)) + + exists := make([]bool, fs.MaxWidth) + var missingIndices []uint64 + missingStr := "" + for i := 0; i < len(exists); i++ { + if rng.Intn(2) == 0 { + exists[i] = true + } else { + missingIndices = append(missingIndices, uint64(i)) + missingStr += fmt.Sprintf(" %d", i) + } + } + + zeroEval, zeroPoly, _ := fs.ZeroPolyViaMultiplication(missingIndices, uint64(len(exists))) + + for i, v := range exists { + if !v { + var at fr.Element + //xbls.CopyFr(&at, &fs.ExpandedRootsOfUnity[i]) + at.Set(&fs.ExpandedRootsOfUnity[i]) + var out fr.Element + EvalPolyAt(&out, zeroPoly, &at) + if !out.IsZero() { + t.Errorf("expected zero at %d, but got: %s", i, out.String()) + } + } + } + + p, err := fs.FFT(zeroEval, true) + if err != nil { + t.Fatal(err) + } + for i := 0; i < len(zeroPoly); i++ { + if !p[i].Equal(&zeroPoly[i]) { + t.Errorf("fft not correct, i: %v, a: %s, b: %s", i, p[i].String(), zeroPoly[i].String()) + } + } + for i := len(zeroPoly); i < len(p); i++ { + if !p[i].IsZero() { + t.Errorf("fft not correct, i: %v, a: %s, b: 0", i, p[i].String()) + } + } +} + +func TestFFTSettings_ZeroPolyViaMultiplication_Parametrized(t *testing.T) { + for i := uint8(3); i < 12; i++ { + t.Run(fmt.Sprintf("scale_%d", i), func(t *testing.T) { + for j := int64(0); j < 3; j++ { + t.Run(fmt.Sprintf("case_%d", j), func(t *testing.T) { + testZeroPoly(t, i, int64(i)*1000+j) + }) + } + }) + } +} diff --git a/op-service/eigenda/encoding/kzg/cli.go b/op-service/eigenda/encoding/kzg/cli.go new file mode 100644 index 000000000..f030f0356 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/cli.go @@ -0,0 +1,102 @@ +package kzg + +import ( + "runtime" + + opservice "github.com/ethereum-optimism/optimism/op-service" + "github.com/urfave/cli" +) + +const ( + G1PathFlagName = "kzg.g1-path" + G2PathFlagName = "kzg.g2-path" + CachePathFlagName = "kzg.cache-path" + SRSOrderFlagName = "kzg.srs-order" + NumWorkerFlagName = "kzg.num-workers" + VerboseFlagName = "kzg.verbose" + PreloadEncoderFlagName = "kzg.preload-encoder" + CacheEncodedBlobsFlagName = "cache-encoded-blobs" + SRSLoadingNumberFlagName = "kzg.srs-load" + G2PowerOf2PathFlagName = "kzg.g2-power-of-2-path" +) + +func CLIFlags(envPrefix string) []cli.Flag { + return []cli.Flag{ + cli.StringFlag{ + Name: G1PathFlagName, + Usage: "Path to G1 SRS", + Required: true, + EnvVar: opservice.PrefixEnvVar(envPrefix, "G1_PATH"), + }, + cli.StringFlag{ + Name: G2PathFlagName, + Usage: "Path to G2 SRS. Either this flag or G2_POWER_OF_2_PATH needs to be specified. For operator node, if both are specified, the node uses G2_POWER_OF_2_PATH first, if failed then tries to G2_PATH", + Required: false, + EnvVar: opservice.PrefixEnvVar(envPrefix, "G2_PATH"), + }, + cli.StringFlag{ + Name: CachePathFlagName, + Usage: "Path to SRS Table directory", + Required: true, + EnvVar: opservice.PrefixEnvVar(envPrefix, "CACHE_PATH"), + }, + cli.Uint64Flag{ + Name: SRSOrderFlagName, + Usage: "Order of the SRS", + Required: true, + EnvVar: opservice.PrefixEnvVar(envPrefix, "SRS_ORDER"), + }, + cli.Uint64Flag{ + Name: SRSLoadingNumberFlagName, + Usage: "Number of SRS points to load into memory", + Required: true, + EnvVar: opservice.PrefixEnvVar(envPrefix, "SRS_LOAD"), + }, + cli.Uint64Flag{ + Name: NumWorkerFlagName, + Usage: "Number of workers for multithreading", + Required: false, + EnvVar: opservice.PrefixEnvVar(envPrefix, "NUM_WORKERS"), + Value: uint64(runtime.GOMAXPROCS(0)), + }, + cli.BoolFlag{ + Name: VerboseFlagName, + Usage: "Enable to see verbose output for encoding/decoding", + Required: false, + EnvVar: opservice.PrefixEnvVar(envPrefix, "VERBOSE"), + }, + cli.BoolFlag{ + Name: CacheEncodedBlobsFlagName, + Usage: "Enable to cache encoded results", + Required: false, + EnvVar: opservice.PrefixEnvVar(envPrefix, "CACHE_ENCODED_BLOBS"), + }, + cli.BoolFlag{ + Name: PreloadEncoderFlagName, + Usage: "Set to enable Encoder PreLoading", + Required: false, + EnvVar: opservice.PrefixEnvVar(envPrefix, "PRELOAD_ENCODER"), + }, + cli.StringFlag{ + Name: G2PowerOf2PathFlagName, + Usage: "Path to G2 SRS points that are on power of 2. Either this flag or G2_PATH needs to be specified. For operator node, if both are specified, the node uses G2_POWER_OF_2_PATH first, if failed then tries to G2_PATH", + Required: false, + EnvVar: opservice.PrefixEnvVar(envPrefix, "G2_POWER_OF_2_PATH"), + }, + } +} + +func ReadCLIConfig(ctx *cli.Context) KzgConfig { + cfg := KzgConfig{} + cfg.G1Path = ctx.GlobalString(G1PathFlagName) + cfg.G2Path = ctx.GlobalString(G2PathFlagName) + cfg.CacheDir = ctx.GlobalString(CachePathFlagName) + cfg.SRSOrder = ctx.GlobalUint64(SRSOrderFlagName) + cfg.SRSNumberToLoad = ctx.GlobalUint64(SRSLoadingNumberFlagName) + cfg.NumWorker = ctx.GlobalUint64(NumWorkerFlagName) + cfg.Verbose = ctx.GlobalBool(VerboseFlagName) + cfg.PreloadEncoder = ctx.GlobalBool(PreloadEncoderFlagName) + cfg.G2PowerOf2Path = ctx.GlobalString(G2PowerOf2PathFlagName) + + return cfg +} diff --git a/op-service/eigenda/encoding/kzg/constants.go b/op-service/eigenda/encoding/kzg/constants.go new file mode 100644 index 000000000..81b2db31a --- /dev/null +++ b/op-service/eigenda/encoding/kzg/constants.go @@ -0,0 +1,40 @@ +package kzg + +import ( + "github.com/consensys/gnark-crypto/ecc/bn254" +) + +func init() { + initG1G2() +} + +var GenG1 bn254.G1Affine +var GenG2 bn254.G2Affine + +var ZeroG1 bn254.G1Affine +var ZeroG2 bn254.G2Affine + +func initG1G2() { + + _, _, genG1, genG2 := bn254.Generators() + + GenG1 = *(*bn254.G1Affine)(&genG1) + GenG2 = *(*bn254.G2Affine)(&genG2) + + var g1Jac bn254.G1Jac + g1Jac.X.SetZero() + g1Jac.Y.SetOne() + g1Jac.Z.SetZero() + + var g1Aff bn254.G1Affine + g1Aff.FromJacobian(&g1Jac) + ZeroG1 = *(*bn254.G1Affine)(&g1Aff) + + var g2Jac bn254.G2Jac + g2Jac.X.SetZero() + g2Jac.Y.SetOne() + g2Jac.Z.SetZero() + var g2Aff bn254.G2Affine + g2Aff.FromJacobian(&g2Jac) + ZeroG2 = *(*bn254.G2Affine)(&g2Aff) +} diff --git a/op-service/eigenda/encoding/kzg/kzg.go b/op-service/eigenda/encoding/kzg/kzg.go new file mode 100644 index 000000000..8d50b0dbb --- /dev/null +++ b/op-service/eigenda/encoding/kzg/kzg.go @@ -0,0 +1,67 @@ +// This code is sourced from the go-kzg Repository by protolambda. +// Original code: https://github.com/protolambda/go-kzg +// MIT License +// +// Copyright (c) 2020 @protolambda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//go:build !bignum_pure && !bignum_hol256 +// +build !bignum_pure,!bignum_hol256 + +package kzg + +import ( + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/fft" +) + +type KZGSettings struct { + *fft.FFTSettings + + Srs *SRS + // setup values +} + +func NewKZGSettings(fs *fft.FFTSettings, srs *SRS) (*KZGSettings, error) { + + ks := &KZGSettings{ + FFTSettings: fs, + Srs: srs, + } + + return ks, nil +} + +// KZG commitment to polynomial in coefficient form +func (ks *KZGSettings) CommitToPoly(coeffs []fr.Element) (*bn254.G1Affine, error) { + var commit bn254.G1Affine + _, err := commit.MultiExp(ks.Srs.G1[:len(coeffs)], coeffs, ecc.MultiExpConfig{}) + return &commit, err +} + +func HashToSingleField(dst *fr.Element, msg []byte) error { + DST := []byte("-") + randomFr, err := fr.Hash(msg, DST, 1) + randomFrBytes := (randomFr[0]).Bytes() + dst.SetBytes(randomFrBytes[:]) + return err +} diff --git a/op-service/eigenda/encoding/kzg/kzgrs.go b/op-service/eigenda/encoding/kzg/kzgrs.go new file mode 100644 index 000000000..ac52cc6aa --- /dev/null +++ b/op-service/eigenda/encoding/kzg/kzgrs.go @@ -0,0 +1,14 @@ +package kzg + +type KzgConfig struct { + G1Path string + G2Path string + G1PowerOf2Path string + G2PowerOf2Path string + CacheDir string + NumWorker uint64 + SRSOrder uint64 // Order is the total size of SRS + SRSNumberToLoad uint64 // Number of points to be loaded from the beginning + Verbose bool + PreloadEncoder bool +} diff --git a/op-service/eigenda/encoding/kzg/pointsIO.go b/op-service/eigenda/encoding/kzg/pointsIO.go new file mode 100644 index 000000000..a7f8a1b60 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/pointsIO.go @@ -0,0 +1,398 @@ +package kzg + +import ( + "bufio" + "errors" + "fmt" + "io" + "log" + "math" + "os" + "time" + + "github.com/consensys/gnark-crypto/ecc/bn254" +) + +const ( + // Num of bytes per G1 point in serialized format in file. + G1PointBytes = 32 + // Num of bytes per G2 point in serialized format in file. + G2PointBytes = 64 +) + +type EncodeParams struct { + NumNodeE uint64 + ChunkLenE uint64 +} + +// ReadDesiredBytes reads exactly numBytesToRead bytes from the reader and returns +// the result. +func ReadDesiredBytes(reader *bufio.Reader, numBytesToRead uint64) ([]byte, error) { + buf := make([]byte, numBytesToRead) + _, err := io.ReadFull(reader, buf) + // Note that ReadFull() guarantees the bytes read is len(buf) IFF err is nil. + // See https://pkg.go.dev/io#ReadFull. + if err != nil { + return nil, fmt.Errorf("cannot read file %w", err) + } + return buf, nil +} + +// Read the n-th G1 point from SRS. +func ReadG1Point(n uint64, g *KzgConfig) (bn254.G1Affine, error) { + if n >= g.SRSOrder { + return bn254.G1Affine{}, fmt.Errorf("requested power %v is larger than SRSOrder %v", n, g.SRSOrder) + } + + g1point, err := ReadG1PointSection(g.G1Path, n, n+1, 1) + if err != nil { + return bn254.G1Affine{}, fmt.Errorf("error read g1 point section %w", err) + } + + return g1point[0], nil +} + +// Read the n-th G2 point from SRS. +func ReadG2Point(n uint64, g *KzgConfig) (bn254.G2Affine, error) { + if n >= g.SRSOrder { + return bn254.G2Affine{}, fmt.Errorf("requested power %v is larger than SRSOrder %v", n, g.SRSOrder) + } + + g2point, err := ReadG2PointSection(g.G2Path, n, n+1, 1) + if err != nil { + return bn254.G2Affine{}, fmt.Errorf("error read g2 point section %w", err) + } + return g2point[0], nil +} + +// Read g2 points from power of 2 file +func ReadG2PointOnPowerOf2(exponent uint64, g *KzgConfig) (bn254.G2Affine, error) { + + // the powerOf2 file, only [tau^exp] are stored. + // exponent 0, 1, 2, , .. + // actual pow [tau],[tau^2],[tau^4],.. (stored in the file) + // In our convention SRSOrder contains the total number of series of g1, g2 starting with generator + // i.e. [1] [tau] [tau^2].. + // So the actual power of tau is SRSOrder - 1 + // The mainnet SRS, the max power is 2^28-1, so the last power in powerOf2 file is [tau^(2^27)] + // For test case of 3000 SRS, the max power is 2999, so last power in powerOf2 file is [tau^2048] + // if a actual SRS order is 15, the file will contain four symbols (1,2,4,8) with indices [0,1,2,3] + // if a actual SRS order is 16, the file will contain five symbols (1,2,4,8,16) with indices [0,1,2,3,4] + + actualPowerOfTau := g.SRSOrder - 1 + largestPowerofSRS := uint64(math.Log2(float64(actualPowerOfTau))) + if exponent > largestPowerofSRS { + return bn254.G2Affine{}, fmt.Errorf("requested power %v is larger than largest power of SRS %v", + uint64(math.Pow(2, float64(exponent))), largestPowerofSRS) + } + + if len(g.G2PowerOf2Path) == 0 { + return bn254.G2Affine{}, errors.New("G2PathPowerOf2 path is empty") + } + + g2point, err := ReadG2PointSection(g.G2PowerOf2Path, exponent, exponent+1, 1) + if err != nil { + return bn254.G2Affine{}, fmt.Errorf("error read g2 point on power of 2 %w", err) + } + return g2point[0], nil +} + +func ReadG1Points(filepath string, n uint64, numWorker uint64) ([]bn254.G1Affine, error) { + g1f, err := os.Open(filepath) + if err != nil { + log.Println("Cannot ReadG1Points", filepath, err) + return nil, fmt.Errorf("error cannot open g1 points file %w", err) + } + + defer func() { + if err := g1f.Close(); err != nil { + log.Printf("G1 close error %v\n", err) + } + }() + + startTimer := time.Now() + g1r := bufio.NewReaderSize(g1f, int(n*G1PointBytes)) + + if n < numWorker { + numWorker = n + } + + buf, err := ReadDesiredBytes(g1r, n*G1PointBytes) + if err != nil { + return nil, err + } + + // measure reading time + t := time.Now() + elapsed := t.Sub(startTimer) + log.Printf(" Reading G1 points (%v bytes) takes %v\n", (n * G1PointBytes), elapsed) + startTimer = time.Now() + + s1Outs := make([]bn254.G1Affine, n) + + start := uint64(0) + end := uint64(0) + size := n / numWorker + + results := make(chan error, numWorker) + + for i := uint64(0); i < numWorker; i++ { + start = i * size + + if i == numWorker-1 { + end = n + } else { + end = (i + 1) * size + } + //fmt.Printf("worker %v start %v end %v. size %v\n", i, start, end, end - start) + + go readG1Worker(buf, s1Outs, start, end, G1PointBytes, results) + } + + for w := uint64(0); w < numWorker; w++ { + err := <-results + if err != nil { + return nil, err + } + } + + // measure parsing time + t = time.Now() + elapsed = t.Sub(startTimer) + log.Println(" Parsing takes", elapsed) + return s1Outs, nil +} + +// from is inclusive, to is exclusive +func ReadG1PointSection(filepath string, from, to uint64, numWorker uint64) ([]bn254.G1Affine, error) { + if to <= from { + return nil, fmt.Errorf("the range to read is invalid, from: %v, to: %v", from, to) + } + g1f, err := os.Open(filepath) + if err != nil { + log.Println("ReadG1PointSection.ERR.0", err) + return nil, err + } + + defer func() { + if err := g1f.Close(); err != nil { + log.Printf("g1 close error %v\n", err) + } + }() + + n := to - from + + g1r := bufio.NewReaderSize(g1f, int(n*G1PointBytes)) + + _, err = g1f.Seek(int64(from)*G1PointBytes, 0) + if err != nil { + return nil, err + } + + if n < numWorker { + numWorker = n + } + + buf, err := ReadDesiredBytes(g1r, n*G1PointBytes) + if err != nil { + return nil, err + } + + s1Outs := make([]bn254.G1Affine, n) + + start := uint64(0) + end := uint64(0) + size := n / numWorker + + results := make(chan error, numWorker) + + for i := uint64(0); i < numWorker; i++ { + start = i * size + + if i == numWorker-1 { + end = n + } else { + end = (i + 1) * size + } + + go readG1Worker(buf, s1Outs, start, end, G1PointBytes, results) + } + + for w := uint64(0); w < numWorker; w++ { + err := <-results + if err != nil { + return nil, err + } + } + + return s1Outs, nil +} + +func readG1Worker( + buf []byte, + outs []bn254.G1Affine, + start uint64, // in element, not in byte + end uint64, + step uint64, + results chan<- error, +) { + for i := start; i < end; i++ { + g1 := buf[i*step : (i+1)*step] + _, err := outs[i].SetBytes(g1[:]) + if err != nil { + results <- err + return + } + } + results <- nil +} + +func readG2Worker( + buf []byte, + outs []bn254.G2Affine, + start uint64, // in element, not in byte + end uint64, + step uint64, + results chan<- error, +) { + for i := start; i < end; i++ { + g1 := buf[i*step : (i+1)*step] + _, err := outs[i].SetBytes(g1[:]) + if err != nil { + results <- err + log.Println("Unmarshalling error:", err) + return + } + } + results <- nil +} + +func ReadG2Points(filepath string, n uint64, numWorker uint64) ([]bn254.G2Affine, error) { + g1f, err := os.Open(filepath) + if err != nil { + log.Println("Cannot ReadG2Points", filepath) + log.Println("ReadG2Points.ERR.0", err) + return nil, err + } + + defer func() { + if err := g1f.Close(); err != nil { + log.Printf("g2 close error %v\n", err) + } + }() + + startTimer := time.Now() + g1r := bufio.NewReaderSize(g1f, int(n*G2PointBytes)) + + if n < numWorker { + numWorker = n + } + + buf, err := ReadDesiredBytes(g1r, n*G2PointBytes) + if err != nil { + return nil, err + } + + // measure reading time + t := time.Now() + elapsed := t.Sub(startTimer) + log.Printf(" Reading G2 points (%v bytes) takes %v\n", (n * G2PointBytes), elapsed) + + startTimer = time.Now() + + s2Outs := make([]bn254.G2Affine, n) + + results := make(chan error, numWorker) + + start := uint64(0) + end := uint64(0) + size := n / numWorker + for i := uint64(0); i < numWorker; i++ { + start = i * size + + if i == numWorker-1 { + end = n + } else { + end = (i + 1) * size + } + + go readG2Worker(buf, s2Outs, start, end, G2PointBytes, results) + } + + for w := uint64(0); w < numWorker; w++ { + err := <-results + if err != nil { + return nil, err + } + } + + // measure parsing time + t = time.Now() + elapsed = t.Sub(startTimer) + log.Println(" Parsing takes", elapsed) + return s2Outs, nil +} + +// from is inclusive, to is exclusive +func ReadG2PointSection(filepath string, from, to uint64, numWorker uint64) ([]bn254.G2Affine, error) { + if to <= from { + return nil, fmt.Errorf("The range to read is invalid, from: %v, to: %v", from, to) + } + g2f, err := os.Open(filepath) + if err != nil { + log.Println("ReadG2PointSection.ERR.0", err) + return nil, err + } + + defer func() { + if err := g2f.Close(); err != nil { + log.Printf("error %v\n", err) + } + }() + + n := to - from + + g2r := bufio.NewReaderSize(g2f, int(n*G2PointBytes)) + + _, err = g2f.Seek(int64(from*G2PointBytes), 0) + if err != nil { + return nil, err + } + + if n < numWorker { + numWorker = n + } + + buf, err := ReadDesiredBytes(g2r, n*G2PointBytes) + if err != nil { + return nil, err + } + + s2Outs := make([]bn254.G2Affine, n) + + results := make(chan error, numWorker) + + start := uint64(0) + end := uint64(0) + size := n / numWorker + + for i := uint64(0); i < numWorker; i++ { + start = i * size + + if i == numWorker-1 { + end = n + } else { + end = (i + 1) * size + } + //todo: handle error? + go readG2Worker(buf, s2Outs, start, end, G2PointBytes, results) + } + for w := uint64(0); w < numWorker; w++ { + err := <-results + if err != nil { + return nil, err + } + } + + return s2Outs, nil +} diff --git a/op-service/eigenda/encoding/kzg/prover/decode.go b/op-service/eigenda/encoding/kzg/prover/decode.go new file mode 100644 index 000000000..b204316c5 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/prover/decode.go @@ -0,0 +1,15 @@ +package prover + +import ( + enc "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" +) + +func (g *ParametrizedProver) Decode(frames []enc.Frame, indices []uint64, maxInputSize uint64) ([]byte, error) { + rsFrames := make([]rs.Frame, len(frames)) + for ind, frame := range frames { + rsFrames[ind] = rs.Frame{Coeffs: frame.Coeffs} + } + + return g.Encoder.Decode(rsFrames, indices, maxInputSize) +} diff --git a/op-service/eigenda/encoding/kzg/prover/decode_test.go b/op-service/eigenda/encoding/kzg/prover/decode_test.go new file mode 100644 index 000000000..98389c9c8 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/prover/decode_test.go @@ -0,0 +1,36 @@ +package prover_test + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/prover" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEncodeDecodeFrame_AreInverses(t *testing.T) { + + group, _ := prover.NewProver(kzgConfig, true) + + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(gettysburgAddressBytes))) + + p, err := group.GetKzgEncoder(params) + + require.Nil(t, err) + require.NotNil(t, p) + + _, _, _, frames, _, err := p.EncodeBytes(gettysburgAddressBytes) + require.Nil(t, err) + require.NotNil(t, frames, err) + + b, err := frames[0].Encode() + require.Nil(t, err) + require.NotNil(t, b) + + frame, err := encoding.Decode(b) + require.Nil(t, err) + require.NotNil(t, frame) + + assert.Equal(t, frame, frames[0]) +} diff --git a/op-service/eigenda/encoding/kzg/prover/parametrized_prover.go b/op-service/eigenda/encoding/kzg/prover/parametrized_prover.go new file mode 100644 index 000000000..6e3c210b5 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/prover/parametrized_prover.go @@ -0,0 +1,273 @@ +package prover + +import ( + "fmt" + "log" + "math" + "time" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/fft" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/utils/toeplitz" +) + +type ParametrizedProver struct { + *rs.Encoder + + *kzg.KzgConfig + Srs *kzg.SRS + G2Trailing []bn254.G2Affine + + Fs *fft.FFTSettings + Ks *kzg.KZGSettings + SFs *fft.FFTSettings // fft used for submatrix product helper + FFTPointsT [][]bn254.G1Affine // transpose of FFTPoints +} + +type WorkerResult struct { + points []bn254.G1Affine + err error +} + +// just a wrapper to take bytes not Fr Element +func (g *ParametrizedProver) EncodeBytes(inputBytes []byte) (*bn254.G1Affine, *bn254.G2Affine, *bn254.G2Affine, []encoding.Frame, []uint32, error) { + inputFr, err := rs.ToFrArray(inputBytes) + if err != nil { + return nil, nil, nil, nil, nil, fmt.Errorf("cannot convert bytes to field elements, %w", err) + } + return g.Encode(inputFr) +} + +func (g *ParametrizedProver) Encode(inputFr []fr.Element) (*bn254.G1Affine, *bn254.G2Affine, *bn254.G2Affine, []encoding.Frame, []uint32, error) { + + startTime := time.Now() + poly, frames, indices, err := g.Encoder.Encode(inputFr) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + if len(poly.Coeffs) > int(g.KzgConfig.SRSNumberToLoad) { + return nil, nil, nil, nil, nil, fmt.Errorf("poly Coeff length %v is greater than Loaded SRS points %v", len(poly.Coeffs), int(g.KzgConfig.SRSNumberToLoad)) + } + + // compute commit for the full poly + commit, err := g.Commit(poly.Coeffs) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + config := ecc.MultiExpConfig{} + + var lengthCommitment bn254.G2Affine + _, err = lengthCommitment.MultiExp(g.Srs.G2[:len(poly.Coeffs)], poly.Coeffs, config) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + intermediate := time.Now() + + chunkLength := uint64(len(inputFr)) + + if g.Verbose { + log.Printf(" Commiting takes %v\n", time.Since(intermediate)) + intermediate = time.Now() + + log.Printf("shift %v\n", g.SRSOrder-chunkLength) + log.Printf("order %v\n", len(g.Srs.G2)) + log.Println("low degree verification info") + } + + shiftedSecret := g.G2Trailing[g.KzgConfig.SRSNumberToLoad-chunkLength:] + + //The proof of low degree is commitment of the polynomial shifted to the largest srs degree + var lengthProof bn254.G2Affine + _, err = lengthProof.MultiExp(shiftedSecret, poly.Coeffs, config) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + if g.Verbose { + log.Printf(" Generating Length Proof takes %v\n", time.Since(intermediate)) + intermediate = time.Now() + } + + // compute proofs + paddedCoeffs := make([]fr.Element, g.NumEvaluations()) + copy(paddedCoeffs, poly.Coeffs) + + proofs, err := g.ProveAllCosetThreads(paddedCoeffs, g.NumChunks, g.ChunkLength, g.NumWorker) + if err != nil { + return nil, nil, nil, nil, nil, fmt.Errorf("could not generate proofs: %v", err) + } + + if g.Verbose { + log.Printf(" Proving takes %v\n", time.Since(intermediate)) + } + + kzgFrames := make([]encoding.Frame, len(frames)) + for i, index := range indices { + kzgFrames[i] = encoding.Frame{ + Proof: proofs[index], + Coeffs: frames[i].Coeffs, + } + } + + if g.Verbose { + log.Printf("Total encoding took %v\n", time.Since(startTime)) + } + return &commit, &lengthCommitment, &lengthProof, kzgFrames, indices, nil +} + +func (g *ParametrizedProver) Commit(polyFr []fr.Element) (bn254.G1Affine, error) { + commit, err := g.Ks.CommitToPoly(polyFr) + return *commit, err +} + +func (p *ParametrizedProver) ProveAllCosetThreads(polyFr []fr.Element, numChunks, chunkLen, numWorker uint64) ([]bn254.G1Affine, error) { + begin := time.Now() + // Robert: Standardizing this to use the same math used in precomputeSRS + dimE := numChunks + l := chunkLen + + sumVec := make([]bn254.G1Affine, dimE*2) + + jobChan := make(chan uint64, numWorker) + results := make(chan WorkerResult, numWorker) + + // create storage for intermediate fft outputs + coeffStore := make([][]fr.Element, dimE*2) + for i := range coeffStore { + coeffStore[i] = make([]fr.Element, l) + } + + for w := uint64(0); w < numWorker; w++ { + go p.proofWorker(polyFr, jobChan, l, dimE, coeffStore, results) + } + + for j := uint64(0); j < l; j++ { + jobChan <- j + } + close(jobChan) + + // return last error + var err error + for w := uint64(0); w < numWorker; w++ { + wr := <-results + if wr.err != nil { + err = wr.err + } + } + + if err != nil { + return nil, fmt.Errorf("proof worker error: %v", err) + } + + t0 := time.Now() + + // compute proof by multi scaler multiplication + msmErrors := make(chan error, dimE*2) + for i := uint64(0); i < dimE*2; i++ { + + go func(k uint64) { + _, err := sumVec[k].MultiExp(p.FFTPointsT[k], coeffStore[k], ecc.MultiExpConfig{}) + // handle error + msmErrors <- err + }(i) + } + + for i := uint64(0); i < dimE*2; i++ { + err := <-msmErrors + if err != nil { + fmt.Println("Error. MSM while adding points", err) + return nil, err + } + } + + t1 := time.Now() + + // only 1 ifft is needed + sumVecInv, err := p.Fs.FFTG1(sumVec, true) + if err != nil { + return nil, fmt.Errorf("fft error: %v", err) + } + + t2 := time.Now() + + // outputs is out of order - buttefly + proofs, err := p.Fs.FFTG1(sumVecInv[:dimE], false) + if err != nil { + return nil, err + } + + t3 := time.Now() + + fmt.Printf("mult-th %v, msm %v,fft1 %v, fft2 %v,\n", t0.Sub(begin), t1.Sub(t0), t2.Sub(t1), t3.Sub(t2)) + + return proofs, nil +} + +func (p *ParametrizedProver) proofWorker( + polyFr []fr.Element, + jobChan <-chan uint64, + l uint64, + dimE uint64, + coeffStore [][]fr.Element, + results chan<- WorkerResult, +) { + + for j := range jobChan { + coeffs, err := p.GetSlicesCoeff(polyFr, dimE, j, l) + if err != nil { + results <- WorkerResult{ + points: nil, + err: err, + } + } else { + for i := 0; i < len(coeffs); i++ { + coeffStore[i][j] = coeffs[i] + } + } + } + + results <- WorkerResult{ + err: nil, + } +} + +// output is in the form see primeField toeplitz +// +// phi ^ (coset size ) = 1 +// +// implicitly pad slices to power of 2 +func (p *ParametrizedProver) GetSlicesCoeff(polyFr []fr.Element, dimE, j, l uint64) ([]fr.Element, error) { + // there is a constant term + m := uint64(len(polyFr)) - 1 + dim := (m - j) / l + + toeV := make([]fr.Element, 2*dimE-1) + for i := uint64(0); i < dim; i++ { + + toeV[i].Set(&polyFr[m-(j+i*l)]) + } + + // use precompute table + tm, err := toeplitz.NewToeplitz(toeV, p.SFs) + if err != nil { + return nil, err + } + return tm.GetFFTCoeff() +} + +/* +returns the power of 2 which is immediately bigger than the input +*/ +func CeilIntPowerOf2Num(d uint64) uint64 { + nextPower := math.Ceil(math.Log2(float64(d))) + return uint64(math.Pow(2.0, nextPower)) +} diff --git a/op-service/eigenda/encoding/kzg/prover/parametrized_prover_test.go b/op-service/eigenda/encoding/kzg/prover/parametrized_prover_test.go new file mode 100644 index 000000000..14ab078f7 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/prover/parametrized_prover_test.go @@ -0,0 +1,46 @@ +package prover_test + +import ( + "fmt" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/prover" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/verifier" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestProveAllCosetThreads(t *testing.T) { + + group, _ := prover.NewProver(kzgConfig, true) + + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(gettysburgAddressBytes))) + enc, err := group.GetKzgEncoder(params) + require.Nil(t, err) + + inputFr, err := rs.ToFrArray(gettysburgAddressBytes) + assert.Nil(t, err) + + commit, _, _, frames, fIndices, err := enc.Encode(inputFr) + require.Nil(t, err) + + for i := 0; i < len(frames); i++ { + f := frames[i] + j := fIndices[i] + + q, err := rs.GetLeadingCosetIndex(uint64(i), numSys+numPar) + require.Nil(t, err) + + assert.Equal(t, j, q, "leading coset inconsistency") + + fmt.Printf("frame %v leading coset %v\n", i, j) + lc := enc.Fs.ExpandedRootsOfUnity[uint64(j)] + + g2Atn, err := kzg.ReadG2Point(uint64(len(f.Coeffs)), kzgConfig) + require.Nil(t, err) + assert.Nil(t, verifier.VerifyFrame(&f, enc.Ks, commit, &lc, &g2Atn), "Proof %v failed\n", i) + } +} diff --git a/op-service/eigenda/encoding/kzg/prover/precompute.go b/op-service/eigenda/encoding/kzg/prover/precompute.go new file mode 100644 index 000000000..22d95736c --- /dev/null +++ b/op-service/eigenda/encoding/kzg/prover/precompute.go @@ -0,0 +1,333 @@ +package prover + +import ( + "bufio" + "fmt" + "io" + "log" + "math" + "os" + "path" + "strconv" + "strings" + "sync" + "time" + + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/fft" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" +) + +type SubTable struct { + //SizeLow uint64 + //SizeUp uint64 + FilePath string +} + +type TableParam struct { + DimE uint64 + CosetSize uint64 +} + +type SRSTable struct { + Tables map[TableParam]SubTable + TableDir string + NumWorker uint64 + s1 []bn254.G1Affine +} + +func NewSRSTable(tableDir string, s1 []bn254.G1Affine, numWorker uint64) (*SRSTable, error) { + + err := os.MkdirAll(tableDir, os.ModePerm) + if err != nil { + log.Println("NEWSRSTABLE.ERR.1", err) + return nil, err + } + + files, err := os.ReadDir(tableDir) + if err != nil { + log.Println("NEWSRSTABLE.ERR.2", err) + return nil, err + } + + tables := make(map[TableParam]SubTable) + for _, file := range files { + filename := file.Name() + + tokens := strings.Split(filename, ".") + + dimEValue, err := strconv.Atoi(tokens[0][4:]) + if err != nil { + log.Println("NEWSRSTABLE.ERR.3", err) + return nil, err + } + cosetSizeValue, err := strconv.Atoi(tokens[1][5:]) + if err != nil { + log.Println("NEWSRSTABLE.ERR.4", err) + return nil, err + } + + param := TableParam{ + DimE: uint64(dimEValue), + CosetSize: uint64(cosetSizeValue), + } + + filePath := path.Join(tableDir, filename) + tables[param] = SubTable{FilePath: filePath} + } + + return &SRSTable{ + Tables: tables, + TableDir: tableDir, + NumWorker: numWorker, + s1: s1, // g1 points + }, nil +} + +func (p *SRSTable) GetSubTables( + numChunks uint64, + chunkLen uint64, +) ([][]bn254.G1Affine, error) { + cosetSize := chunkLen + dimE := numChunks + m := numChunks*chunkLen - 1 + dim := m / cosetSize + + param := TableParam{ + DimE: dimE, + CosetSize: cosetSize, + } + + start := time.Now() + table, ok := p.Tables[param] + if !ok { + log.Printf("Table with params: DimE=%v CosetSize=%v does not exist\n", dimE, cosetSize) + log.Printf("Generating the table. May take a while\n") + log.Printf("... ...\n") + filename := fmt.Sprintf("dimE%v.coset%v", dimE, cosetSize) + dstFilePath := path.Join(p.TableDir, filename) + fftPoints := p.Precompute(dim, dimE, cosetSize, m, dstFilePath, p.NumWorker) + + elapsed := time.Since(start) + log.Printf(" Precompute finishes using %v\n", elapsed) + + return fftPoints, nil + } else { + log.Printf("Detected Precomputed FFT sliced G1 table\n") + fftPoints, err := p.TableReaderThreads(table.FilePath, dimE, cosetSize, p.NumWorker) + if err != nil { + log.Println("GetSubTables.ERR.0", err) + return nil, err + } + + elapsed := time.Since(start) + log.Printf(" Loading Table uses %v\n", elapsed) + + return fftPoints, nil + } +} + +type DispatchReturn struct { + points []bn254.G1Affine + j uint64 +} + +// m = len(poly) - 1, which is deg +func (p *SRSTable) Precompute(dim, dimE, l, m uint64, filePath string, numWorker uint64) [][]bn254.G1Affine { + order := dimE * l + if l == 1 { + order = dimE * 2 + } + // TODO, create function only read g1 points + //s1 := ReadG1Points(p.SrsFilePath, order) + n := uint8(math.Log2(float64(order))) + fs := fft.NewFFTSettings(n) + + fftPoints := make([][]bn254.G1Affine, l) + + numJob := l + jobChan := make(chan uint64, numJob) + results := make(chan DispatchReturn, l) + + for w := uint64(0); w < numWorker; w++ { + go p.precomputeWorker(fs, m, dim, dimE, jobChan, l, results) + } + + for j := uint64(0); j < l; j++ { + jobChan <- j + } + close(jobChan) + + for w := uint64(0); w < l; w++ { + computeResult := <-results + fftPoints[computeResult.j] = computeResult.points + } + + err := p.TableWriter(fftPoints, dimE, filePath) + if err != nil { + log.Println("Precompute error:", err) + } + return fftPoints +} + +func (p *SRSTable) precomputeWorker(fs *fft.FFTSettings, m, dim, dimE uint64, jobChan <-chan uint64, l uint64, results chan DispatchReturn) { + for j := range jobChan { + dr, err := p.PrecomputeSubTable(fs, m, dim, dimE, j, l) + if err != nil { + log.Println("precomputeWorker.ERR.1", err) + return + } + results <- dr + } +} + +func (p *SRSTable) PrecomputeSubTable(fs *fft.FFTSettings, m, dim, dimE, j, l uint64) (DispatchReturn, error) { + // there is a constant term + points := make([]bn254.G1Affine, 2*dimE) + k := m - l - j + + for i := uint64(0); i < dim; i++ { + points[i].Set(&p.s1[k]) + k -= l + } + for i := dim; i < 2*dimE; i++ { + points[i].Set(&kzg.ZeroG1) + } + + y, err := fs.FFTG1(points, false) + if err != nil { + log.Println("PrecomputeSubTable.ERR.1", err) + return DispatchReturn{}, err + } + + return DispatchReturn{ + points: y, + j: j, + }, nil + +} + +type Boundary struct { + start uint64 + end uint64 // informational + sliceAt uint64 +} + +func (p *SRSTable) TableReaderThreads(filePath string, dimE, l uint64, numWorker uint64) ([][]bn254.G1Affine, error) { + g1f, err := os.Open(filePath) + if err != nil { + log.Println("TableReaderThreads.ERR.0", err) + return nil, err + } + + // 2 due to circular FFT mul + subTableSize := dimE * 2 * kzg.G1PointBytes + totalSubTableSize := subTableSize * l + + if numWorker > l { + numWorker = l + } + + reader := bufio.NewReaderSize(g1f, int(totalSubTableSize+l)) + buf := make([]byte, totalSubTableSize+l) + if _, err := io.ReadFull(reader, buf); err != nil { + log.Println("TableReaderThreads.ERR.1", err, "file path:", filePath) + return nil, err + } + + boundaries := make([]Boundary, l) + for i := uint64(0); i < uint64(l); i++ { + start := (subTableSize + 1) * i + end := (subTableSize+1)*(i+1) - 1 // exclude \n + boundary := Boundary{ + start: start, + end: end, + sliceAt: i, + } + boundaries[i] = boundary + } + + fftPoints := make([][]bn254.G1Affine, l) + + jobChan := make(chan Boundary, l) + + var wg sync.WaitGroup + wg.Add(int(numWorker)) + for i := uint64(0); i < numWorker; i++ { + go p.readWorker(buf, fftPoints, jobChan, dimE, &wg) + } + + for i := uint64(0); i < l; i++ { + jobChan <- boundaries[i] + } + close(jobChan) + wg.Wait() + + if err := g1f.Close(); err != nil { + return nil, err + } + + return fftPoints, nil +} + +func (p *SRSTable) readWorker( + buf []byte, + fftPoints [][]bn254.G1Affine, + jobChan <-chan Boundary, + dimE uint64, + wg *sync.WaitGroup, +) { + for b := range jobChan { + slicePoints := make([]bn254.G1Affine, dimE*2) + for i := uint64(0); i < dimE*2; i++ { + g1 := buf[b.start+i*kzg.G1PointBytes : b.start+(i+1)*kzg.G1PointBytes] + _, err := slicePoints[i].SetBytes(g1[:]) //UnmarshalText(g1[:]) + if err != nil { + log.Printf("Error. From %v to %v. %v", b.start, b.end, err) + log.Println() + log.Println("readWorker.ERR.0", err) + return + } + } + fftPoints[b.sliceAt] = slicePoints + } + wg.Done() +} + +func (p *SRSTable) TableWriter(fftPoints [][]bn254.G1Affine, dimE uint64, filePath string) error { + wf, err := os.Create(filePath) + if err != nil { + log.Println("TableWriter.ERR.0", err) + return err + } + + writer := bufio.NewWriter(wf) + l := uint64(len(fftPoints)) + + delimiter := [1]byte{'\n'} + + for j := uint64(0); j < l; j++ { + for i := uint64(0); i < dimE*2; i++ { + + g1Bytes := fftPoints[j][i].Bytes() + if _, err := writer.Write(g1Bytes[:]); err != nil { + log.Println("TableWriter.ERR.2", err) + return err + } + } + // every line for each slice + if _, err := writer.Write(delimiter[:]); err != nil { + log.Println("TableWriter.ERR.3", err) + return err + } + } + + if err = writer.Flush(); err != nil { + log.Println("TableWriter.ERR.4", err) + return err + } + + err = wf.Close() + + return err +} diff --git a/op-service/eigenda/encoding/kzg/prover/precompute_test.go b/op-service/eigenda/encoding/kzg/prover/precompute_test.go new file mode 100644 index 000000000..75e6d87dd --- /dev/null +++ b/op-service/eigenda/encoding/kzg/prover/precompute_test.go @@ -0,0 +1,45 @@ +package prover_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/prover" +) + +func TestNewSRSTable_PreComputeWorks(t *testing.T) { + + kzgConfig.CacheDir = "./data/SRSTable" + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(gettysburgAddressBytes))) + require.NotNil(t, params) + + s1, err := kzg.ReadG1Points(kzgConfig.G1Path, kzgConfig.SRSOrder, kzgConfig.NumWorker) + require.Nil(t, err) + require.NotNil(t, s1) + + _, err = kzg.ReadG2Points(kzgConfig.G2Path, kzgConfig.SRSOrder, kzgConfig.NumWorker) + require.Nil(t, err) + + subTable1, err := prover.NewSRSTable(kzgConfig.CacheDir, s1, kzgConfig.NumWorker) + require.Nil(t, err) + require.NotNil(t, subTable1) + + fftPoints1, err := subTable1.GetSubTables(params.NumChunks, params.ChunkLength) + require.Nil(t, err) + require.NotNil(t, fftPoints1) + + subTable2, err := prover.NewSRSTable(kzgConfig.CacheDir, s1, kzgConfig.NumWorker) + require.Nil(t, err) + require.NotNil(t, subTable2) + + fftPoints2, err := subTable2.GetSubTables(params.NumChunks, params.ChunkLength) + require.Nil(t, err) + require.NotNil(t, fftPoints2) + + // Result of non precomputed GetSubTables should equal precomputed GetSubTables + assert.Equal(t, fftPoints1, fftPoints2) +} diff --git a/op-service/eigenda/encoding/kzg/prover/prover.go b/op-service/eigenda/encoding/kzg/prover/prover.go new file mode 100644 index 000000000..a86f33b8d --- /dev/null +++ b/op-service/eigenda/encoding/kzg/prover/prover.go @@ -0,0 +1,315 @@ +package prover + +import ( + "errors" + "fmt" + "log" + "math" + "os" + "runtime" + "strconv" + "strings" + "sync" + + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/fft" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" + //_ "go.uber.org/automaxprocs" +) + +type Prover struct { + *kzg.KzgConfig + Srs *kzg.SRS + G2Trailing []bn254.G2Affine + mu sync.Mutex + LoadG2Points bool + + ParametrizedProvers map[encoding.EncodingParams]*ParametrizedProver +} + +var _ encoding.Prover = &Prover{} + +func NewProver(config *kzg.KzgConfig, loadG2Points bool) (*Prover, error) { + + if config.SRSNumberToLoad > config.SRSOrder { + return nil, errors.New("SRSOrder is less than srsNumberToLoad") + } + + // read the whole order, and treat it as entire SRS for low degree proof + s1, err := kzg.ReadG1Points(config.G1Path, config.SRSNumberToLoad, config.NumWorker) + if err != nil { + log.Println("failed to read G1 points", err) + return nil, err + } + + s2 := make([]bn254.G2Affine, 0) + g2Trailing := make([]bn254.G2Affine, 0) + + // PreloadEncoder is by default not used by operator node, PreloadEncoder + if loadG2Points { + if len(config.G2Path) == 0 { + return nil, errors.New("G2Path is empty. However, object needs to load G2Points") + } + + s2, err = kzg.ReadG2Points(config.G2Path, config.SRSNumberToLoad, config.NumWorker) + if err != nil { + log.Println("failed to read G2 points", err) + return nil, err + } + + g2Trailing, err = kzg.ReadG2PointSection( + config.G2Path, + config.SRSOrder-config.SRSNumberToLoad, + config.SRSOrder, // last exclusive + config.NumWorker, + ) + if err != nil { + return nil, err + } + } else { + // todo, there are better ways to handle it + if len(config.G2PowerOf2Path) == 0 { + return nil, errors.New("G2PowerOf2Path is empty. However, object needs to load G2Points") + } + } + + srs, err := kzg.NewSrs(s1, s2) + if err != nil { + log.Println("Could not create srs", err) + return nil, err + } + + fmt.Println("numthread", runtime.GOMAXPROCS(0)) + + encoderGroup := &Prover{ + KzgConfig: config, + Srs: srs, + G2Trailing: g2Trailing, + ParametrizedProvers: make(map[encoding.EncodingParams]*ParametrizedProver), + LoadG2Points: loadG2Points, + } + + if config.PreloadEncoder { + // create table dir if not exist + err := os.MkdirAll(config.CacheDir, os.ModePerm) + if err != nil { + log.Println("Cannot make CacheDir", err) + return nil, err + } + + err = encoderGroup.PreloadAllEncoders() + if err != nil { + return nil, err + } + } + + return encoderGroup, nil + +} + +func (g *Prover) PreloadAllEncoders() error { + paramsAll, err := GetAllPrecomputedSrsMap(g.CacheDir) + if err != nil { + return err + } + fmt.Printf("detect %v srs maps\n", len(paramsAll)) + for i := 0; i < len(paramsAll); i++ { + fmt.Printf(" %v. NumChunks: %v ChunkLength: %v\n", i, paramsAll[i].NumChunks, paramsAll[i].ChunkLength) + } + + if len(paramsAll) == 0 { + return nil + } + + for _, params := range paramsAll { + // get those encoders and store them + enc, err := g.GetKzgEncoder(params) + if err != nil { + return err + } + g.ParametrizedProvers[params] = enc + } + + return nil +} + +func (e *Prover) EncodeAndProve(data []byte, params encoding.EncodingParams) (encoding.BlobCommitments, []*encoding.Frame, error) { + + enc, err := e.GetKzgEncoder(params) + if err != nil { + return encoding.BlobCommitments{}, nil, err + } + + commit, lengthCommit, lengthProof, kzgFrames, _, err := enc.EncodeBytes(data) + if err != nil { + return encoding.BlobCommitments{}, nil, err + } + + chunks := make([]*encoding.Frame, len(kzgFrames)) + for ind, frame := range kzgFrames { + + chunks[ind] = &encoding.Frame{ + Coeffs: frame.Coeffs, + Proof: frame.Proof, + } + } + + symbols, err := rs.ToFrArray(data) + if err != nil { + return encoding.BlobCommitments{}, nil, err + } + + length := uint(len(symbols)) + commitments := encoding.BlobCommitments{ + Commitment: (*encoding.G1Commitment)(commit), + LengthCommitment: (*encoding.G2Commitment)(lengthCommit), + LengthProof: (*encoding.G2Commitment)(lengthProof), + Length: length, + } + + return commitments, chunks, nil +} + +func (g *Prover) GetKzgEncoder(params encoding.EncodingParams) (*ParametrizedProver, error) { + g.mu.Lock() + defer g.mu.Unlock() + enc, ok := g.ParametrizedProvers[params] + if ok { + return enc, nil + } + + enc, err := g.newProver(params) + if err == nil { + g.ParametrizedProvers[params] = enc + } + + return enc, err +} + +func (g *Prover) newProver(params encoding.EncodingParams) (*ParametrizedProver, error) { + + // Check that the parameters are valid with respect to the SRS. + if params.ChunkLength*params.NumChunks >= g.SRSOrder { + return nil, fmt.Errorf("the supplied encoding parameters are not valid with respect to the SRS. ChunkLength: %d, NumChunks: %d, SRSOrder: %d", params.ChunkLength, params.NumChunks, g.SRSOrder) + } + + encoder, err := rs.NewEncoder(params, g.Verbose) + if err != nil { + log.Println("Could not create encoder: ", err) + return nil, err + } + + subTable, err := NewSRSTable(g.CacheDir, g.Srs.G1, g.NumWorker) + if err != nil { + log.Println("Could not create srs table:", err) + return nil, err + } + + fftPoints, err := subTable.GetSubTables(encoder.NumChunks, encoder.ChunkLength) + if err != nil { + log.Println("could not get sub tables", err) + return nil, err + } + + fftPointsT := make([][]bn254.G1Affine, len(fftPoints[0])) + for i := range fftPointsT { + fftPointsT[i] = make([]bn254.G1Affine, len(fftPoints)) + for j := uint64(0); j < encoder.ChunkLength; j++ { + fftPointsT[i][j] = fftPoints[j][i] + } + } + _ = fftPoints + n := uint8(math.Log2(float64(encoder.NumEvaluations()))) + if encoder.ChunkLength == 1 { + n = uint8(math.Log2(float64(2 * encoder.NumChunks))) + } + fs := fft.NewFFTSettings(n) + + ks, err := kzg.NewKZGSettings(fs, g.Srs) + if err != nil { + return nil, err + } + + t := uint8(math.Log2(float64(2 * encoder.NumChunks))) + sfs := fft.NewFFTSettings(t) + + return &ParametrizedProver{ + Encoder: encoder, + KzgConfig: g.KzgConfig, + Srs: g.Srs, + G2Trailing: g.G2Trailing, + Fs: fs, + Ks: ks, + SFs: sfs, + FFTPointsT: fftPointsT, + }, nil +} + +// Detect the precomputed table from the specified directory +// the file name follow the name convention of +// +// dimE*.coset& +// +// where the first * specifies the dimension of the matrix which +// equals to the number of chunks +// where the second & specifies the length of each chunk +func GetAllPrecomputedSrsMap(tableDir string) ([]encoding.EncodingParams, error) { + files, err := os.ReadDir(tableDir) + if err != nil { + log.Println("Error to list SRS Table directory", err) + return nil, err + } + + tables := make([]encoding.EncodingParams, 0) + for _, file := range files { + filename := file.Name() + + tokens := strings.Split(filename, ".") + + dimEValue, err := strconv.Atoi(tokens[0][4:]) + if err != nil { + log.Println("Error to parse Dimension part of the Table", err) + return nil, err + } + cosetSizeValue, err := strconv.Atoi(tokens[1][5:]) + if err != nil { + log.Println("Error to parse Coset size of the Table", err) + return nil, err + } + + params := encoding.EncodingParams{ + NumChunks: uint64(cosetSizeValue), + ChunkLength: uint64(dimEValue), + } + tables = append(tables, params) + } + return tables, nil +} + +// Decode takes in the chunks, indices, and encoding parameters and returns the decoded blob +// The result is trimmed to the given maxInputSize. +func (p *Prover) Decode(chunks []*encoding.Frame, indices []encoding.ChunkNumber, params encoding.EncodingParams, maxInputSize uint64) ([]byte, error) { + frames := make([]encoding.Frame, len(chunks)) + for i := range chunks { + frames[i] = encoding.Frame{ + Proof: chunks[i].Proof, + Coeffs: chunks[i].Coeffs, + } + } + encoder, err := p.GetKzgEncoder(params) + if err != nil { + return nil, err + } + + return encoder.Decode(frames, toUint64Array(indices), maxInputSize) +} + +func toUint64Array(chunkIndices []encoding.ChunkNumber) []uint64 { + res := make([]uint64, len(chunkIndices)) + for i, d := range chunkIndices { + res[i] = uint64(d) + } + return res +} diff --git a/op-service/eigenda/encoding/kzg/prover/prover_fuzz_test.go b/op-service/eigenda/encoding/kzg/prover/prover_fuzz_test.go new file mode 100644 index 000000000..5b49da0eb --- /dev/null +++ b/op-service/eigenda/encoding/kzg/prover/prover_fuzz_test.go @@ -0,0 +1,44 @@ +package prover_test + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/prover" + "github.com/stretchr/testify/assert" +) + +func FuzzOnlySystematic(f *testing.F) { + + f.Add(gettysburgAddressBytes) + f.Fuzz(func(t *testing.T, input []byte) { + + group, _ := prover.NewProver(kzgConfig, true) + + params := encoding.ParamsFromSysPar(10, 3, uint64(len(input))) + enc, err := group.GetKzgEncoder(params) + if err != nil { + t.Errorf("Error making rs: %q", err) + } + + //encode the data + _, _, _, frames, _, err := enc.EncodeBytes(input) + + for _, frame := range frames { + assert.NotEqual(t, len(frame.Coeffs), 0) + } + + if err != nil { + t.Errorf("Error Encoding:\n Data:\n %q \n Err: %q", input, err) + } + + //sample the correct systematic frames + samples, indices := sampleFrames(frames, uint64(len(frames))) + + data, err := enc.Decode(samples, indices, uint64(len(input))) + if err != nil { + t.Errorf("Error Decoding:\n Data:\n %q \n Err: %q", input, err) + } + assert.Equal(t, input, data, "Input data was not equal to the decoded data") + }) +} diff --git a/op-service/eigenda/encoding/kzg/prover/prover_test.go b/op-service/eigenda/encoding/kzg/prover/prover_test.go new file mode 100644 index 000000000..d35c867cf --- /dev/null +++ b/op-service/eigenda/encoding/kzg/prover/prover_test.go @@ -0,0 +1,144 @@ +package prover_test + +import ( + cryptorand "crypto/rand" + "log" + "math/rand" + "os" + "runtime" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/prover" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/verifier" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/utils/codec" + + "github.com/stretchr/testify/assert" +) + +var ( + gettysburgAddressBytes = codec.ConvertByPaddingEmptyByte([]byte("Fourscore and seven years ago our fathers brought forth, on this continent, a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived, and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting-place for those who here gave their lives, that that nation might live. It is altogether fitting and proper that we should do this. But, in a larger sense, we cannot dedicate, we cannot consecrate—we cannot hallow—this ground. The brave men, living and dead, who struggled here, have consecrated it far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they here gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom, and that government of the people, by the people, for the people, shall not perish from the earth.")) + kzgConfig *kzg.KzgConfig + numNode uint64 + numSys uint64 + numPar uint64 +) + +func TestMain(m *testing.M) { + setup() + result := m.Run() + teardown() + os.Exit(result) +} + +func setup() { + log.Println("Setting up suite") + + kzgConfig = &kzg.KzgConfig{ + G1Path: "../../../inabox/resources/kzg/g1.point", + G2Path: "../../../inabox/resources/kzg/g2.point", + G2PowerOf2Path: "../../../inabox/resources/kzg/g2.point.powerOf2", + CacheDir: "../../../inabox/resources/kzg/SRSTables", + SRSOrder: 3000, + SRSNumberToLoad: 2900, + NumWorker: uint64(runtime.GOMAXPROCS(0)), + } + + numNode = uint64(4) + numSys = uint64(3) + numPar = numNode - numSys + +} + +func teardown() { + log.Println("Tearing down suite") + + // Some test may want to create a new SRS table so this should clean it up. + os.RemoveAll("./data") +} + +func sampleFrames(frames []encoding.Frame, num uint64) ([]encoding.Frame, []uint64) { + samples := make([]encoding.Frame, num) + indices := rand.Perm(len(frames)) + indices = indices[:num] + + frameIndices := make([]uint64, num) + for i, j := range indices { + samples[i] = frames[j] + frameIndices[i] = uint64(j) + } + return samples, frameIndices +} + +func TestEncoder(t *testing.T) { + + p, _ := prover.NewProver(kzgConfig, true) + v, _ := verifier.NewVerifier(kzgConfig, true) + + params := encoding.ParamsFromMins(5, 5) + commitments, chunks, err := p.EncodeAndProve(gettysburgAddressBytes, params) + assert.NoError(t, err) + + indices := []encoding.ChunkNumber{ + 0, 1, 2, 3, 4, 5, 6, 7, + } + err = v.VerifyFrames(chunks, indices, commitments, params) + assert.NoError(t, err) + err = v.VerifyFrames(chunks, []encoding.ChunkNumber{ + 7, 6, 5, 4, 3, 2, 1, 0, + }, commitments, params) + assert.Error(t, err) + + maxInputSize := uint64(len(gettysburgAddressBytes)) + decoded, err := p.Decode(chunks, indices, params, maxInputSize) + assert.NoError(t, err) + assert.Equal(t, gettysburgAddressBytes, decoded) + + // shuffle chunks + tmp := chunks[2] + chunks[2] = chunks[5] + chunks[5] = tmp + indices = []encoding.ChunkNumber{ + 0, 1, 5, 3, 4, 2, 6, 7, + } + + err = v.VerifyFrames(chunks, indices, commitments, params) + assert.NoError(t, err) + + decoded, err = p.Decode(chunks, indices, params, maxInputSize) + assert.NoError(t, err) + assert.Equal(t, gettysburgAddressBytes, decoded) +} + +// Ballpark number for 400KiB blob encoding +// +// goos: darwin +// goarch: arm64 +// pkg: github.com/Layr-Labs/eigenda/core/encoding +// BenchmarkEncode-12 1 2421900583 ns/op +func BenchmarkEncode(b *testing.B) { + + p, _ := prover.NewProver(kzgConfig, true) + + params := encoding.EncodingParams{ + ChunkLength: 512, + NumChunks: 256, + } + blobSize := 400 * 1024 + numSamples := 30 + blobs := make([][]byte, numSamples) + for i := 0; i < numSamples; i++ { + blob := make([]byte, blobSize) + _, _ = cryptorand.Read(blob) + blobs[i] = blob + } + + // Warm up the encoder: ensures that all SRS tables are loaded so these aren't included in the benchmark. + _, _, _ = p.EncodeAndProve(blobs[0], params) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, _, _ = p.EncodeAndProve(blobs[i%numSamples], params) + } +} diff --git a/op-service/eigenda/encoding/kzg/setup.go b/op-service/eigenda/encoding/kzg/setup.go new file mode 100644 index 000000000..3a6eba8b7 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/setup.go @@ -0,0 +1,134 @@ +// This code is sourced from the go-kzg Repository by protolambda. +// Original code: https://github.com/protolambda/go-kzg +// MIT License +// +// Copyright (c) 2020 @protolambda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//go:build !bignum_pure && !bignum_hol256 +// +build !bignum_pure,!bignum_hol256 + +package kzg + +import ( + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + + "bufio" + "fmt" + "log" + "os" + "strconv" + "time" +) + +// GenerateTestingSetup creates a setup of n values from the given secret. **for testing purposes only** +func GenerateTestingSetup(secret string, n uint64) ([]bn254.G1Affine, []bn254.G2Affine, error) { + var s fr.Element + _, err := s.SetString(secret) + if err != nil { + return nil, nil, err + } + + var sPow fr.Element + sPow.SetOne() + + s1Out := make([]bn254.G1Affine, n) + s2Out := make([]bn254.G2Affine, n) + for i := uint64(0); i < n; i++ { + + s1Out[i].ScalarMultiplication(&GenG1, sPow.BigInt(new(big.Int))) + + + s2Out[i].ScalarMultiplication(&GenG2, sPow.BigInt(new(big.Int))) + + sPow.Mul(&sPow, &s) + } + return s1Out, s2Out, nil +} + +func WriteGeneratorPoints(n uint64) error { + secret := "1927409816240961209460912649125" + ns := strconv.Itoa(int(n)) + + var s fr.Element + _, err := s.SetString(secret) + if err != nil { + return err + } + + + var sPow fr.Element + sPow.SetOne() + + + g1f, err := os.Create("g1.point." + ns) + if err != nil { + log.Println("WriteGeneratorPoints.ERR.0", err) + return err + } + + g1w := bufio.NewWriter(g1f) + g2f, err := os.Create("g2.point." + ns) + if err != nil { + log.Println("WriteGeneratorPoints.ERR.1", err) + return err + } + g2w := bufio.NewWriter(g2f) + + + + start := time.Now() + for i := uint64(0); i < n; i++ { + var s1Out bn254.G1Affine + var s2Out bn254.G2Affine + s1Out.ScalarMultiplication(&GenG1, sPow.BigInt(new(big.Int))) + + s2Out.ScalarMultiplication(&GenG2, sPow.BigInt(new(big.Int))) + + g1Byte := s1Out.Bytes() + if _, err := g1w.Write(g1Byte[:]); err != nil { + log.Println("WriteGeneratorPoints.ERR.3", err) + return err + } + + g2Byte := s2Out.Bytes() + if _, err := g2w.Write(g2Byte[:]); err != nil { + log.Println("WriteGeneratorPoints.ERR.5", err) + return err + } + sPow.Mul(&sPow, &s) + } + + if err = g1w.Flush(); err != nil { + log.Println("WriteGeneratorPoints.ERR.6", err) + return err + } + if err = g2w.Flush(); err != nil { + log.Println("WriteGeneratorPoints.ERR.7", err) + return err + } + t := time.Now() + elapsed := t.Sub(start) + fmt.Println("Generating takes", elapsed) + return nil +} diff --git a/op-service/eigenda/encoding/kzg/srs.go b/op-service/eigenda/encoding/kzg/srs.go new file mode 100644 index 000000000..25a6c1685 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/srs.go @@ -0,0 +1,45 @@ +// This code is sourced from the go-kzg Repository by protolambda. +// Original code: https://github.com/protolambda/go-kzg +// MIT License +// +// Copyright (c) 2020 @protolambda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package kzg + +import ( + "github.com/consensys/gnark-crypto/ecc/bn254" +) + +type SRS struct { + + // [b.multiply(b.G1, pow(s, i, MODULUS)) for i in range(WIDTH+1)], + G1 []bn254.G1Affine + // [b.multiply(b.G2, pow(s, i, MODULUS)) for i in range(WIDTH+1)], + G2 []bn254.G2Affine +} + +func NewSrs(G1 []bn254.G1Affine, G2 []bn254.G2Affine) (*SRS, error) { + + return &SRS{ + G1: G1, + G2: G2, + }, nil +} diff --git a/op-service/eigenda/encoding/kzg/verifier/batch_commit_equivalence.go b/op-service/eigenda/encoding/kzg/verifier/batch_commit_equivalence.go new file mode 100644 index 000000000..bcc904459 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/verifier/batch_commit_equivalence.go @@ -0,0 +1,100 @@ +package verifier + +import ( + "crypto/rand" + "errors" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +type CommitmentPair struct { + Commitment bn254.G1Affine + LengthCommitment bn254.G2Affine +} + +// Create a random number with crypto/rand. +// Gnark provides SetRandom() function, but the implementation below is for explicity +func GetRandomFr() (fr.Element, error) { + r, err := rand.Int(rand.Reader, fr.Modulus()) + if err != nil { + return fr.Element{}, err + } + var rElement fr.Element + rElement.SetBigInt(r) + return rElement, nil +} + +func CreateRandomnessVector(n int) ([]fr.Element, error) { + if n <= 0 { + return nil, errors.New("the length of vector must be positive") + } + r, err := GetRandomFr() + if err != nil { + return nil, err + } + + randomsFr := make([]fr.Element, n) + + randomsFr[0].Set(&r) + + // power of r + for j := 0; j < n-1; j++ { + randomsFr[j+1].Mul(&randomsFr[j], &r) + } + + return randomsFr, nil +} + +func (v *Verifier) VerifyCommitEquivalenceBatch(commitments []encoding.BlobCommitments) error { + commitmentsPair := make([]CommitmentPair, len(commitments)) + + for i, c := range commitments { + commitmentsPair[i] = CommitmentPair{ + Commitment: (bn254.G1Affine)(*c.Commitment), + LengthCommitment: (bn254.G2Affine)(*c.LengthCommitment), + } + } + return v.BatchVerifyCommitEquivalence(commitmentsPair) +} + +func (group *Verifier) BatchVerifyCommitEquivalence(commitmentsPair []CommitmentPair) error { + + g1commits := make([]bn254.G1Affine, len(commitmentsPair)) + g2commits := make([]bn254.G2Affine, len(commitmentsPair)) + for i := 0; i < len(commitmentsPair); i++ { + g1commits[i] = commitmentsPair[i].Commitment + g2commits[i] = commitmentsPair[i].LengthCommitment + } + + randomsFr, err := CreateRandomnessVector(len(g1commits)) + if err != nil { + return err + } + + var lhsG1 bn254.G1Affine + _, err = lhsG1.MultiExp(g1commits, randomsFr, ecc.MultiExpConfig{}) + if err != nil { + return err + } + + lhsG2 := &kzg.GenG2 + + var rhsG2 bn254.G2Affine + _, err = rhsG2.MultiExp(g2commits, randomsFr, ecc.MultiExpConfig{}) + if err != nil { + return err + } + rhsG1 := &kzg.GenG1 + + err = PairingsVerify(&lhsG1, lhsG2, rhsG1, &rhsG2) + if err == nil { + return nil + } else { + return errors.New("incorrect universal batch verification") + } +} diff --git a/op-service/eigenda/encoding/kzg/verifier/batch_commit_equivalence_test.go b/op-service/eigenda/encoding/kzg/verifier/batch_commit_equivalence_test.go new file mode 100644 index 000000000..e53391677 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/verifier/batch_commit_equivalence_test.go @@ -0,0 +1,62 @@ +package verifier_test + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/prover" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/verifier" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" + + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBatchEquivalence(t *testing.T) { + + group, _ := prover.NewProver(kzgConfig, true) + v, _ := verifier.NewVerifier(kzgConfig, true) + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(gettysburgAddressBytes))) + enc, err := group.GetKzgEncoder(params) + require.Nil(t, err) + + inputFr, err := rs.ToFrArray(gettysburgAddressBytes) + require.Nil(t, err) + commit, g2commit, _, _, _, err := enc.Encode(inputFr) + require.Nil(t, err) + + numBlob := 5 + commitPairs := make([]verifier.CommitmentPair, numBlob) + for z := 0; z < numBlob; z++ { + commitPairs[z] = verifier.CommitmentPair{ + Commitment: *commit, + LengthCommitment: *g2commit, + } + } + + assert.NoError(t, v.BatchVerifyCommitEquivalence(commitPairs), "batch equivalence negative test failed\n") + + var modifiedCommit bn254.G1Affine + modifiedCommit.Add(commit, commit) + + for z := 0; z < numBlob; z++ { + commitPairs[z] = verifier.CommitmentPair{ + Commitment: modifiedCommit, + LengthCommitment: *g2commit, + } + } + + assert.Error(t, v.BatchVerifyCommitEquivalence(commitPairs), "batch equivalence negative test failed\n") + + for z := 0; z < numBlob; z++ { + commitPairs[z] = verifier.CommitmentPair{ + Commitment: *commit, + LengthCommitment: *g2commit, + } + } + + commitPairs[numBlob/2].Commitment.Add(&commitPairs[numBlob/2].Commitment, &commitPairs[numBlob/2].Commitment) + + assert.Error(t, v.BatchVerifyCommitEquivalence(commitPairs), "batch equivalence negative test failed in outer loop\n") +} diff --git a/op-service/eigenda/encoding/kzg/verifier/frame_test.go b/op-service/eigenda/encoding/kzg/verifier/frame_test.go new file mode 100644 index 000000000..964cf0b16 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/verifier/frame_test.go @@ -0,0 +1,43 @@ +package verifier_test + +import ( + "math" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/fft" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/prover" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/verifier" +) + +func TestVerify(t *testing.T) { + + group, _ := prover.NewProver(kzgConfig, true) + + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(gettysburgAddressBytes))) + + enc, err := group.GetKzgEncoder(params) + require.Nil(t, err) + require.NotNil(t, enc) + + commit, _, _, frames, _, err := enc.EncodeBytes(gettysburgAddressBytes) + require.Nil(t, err) + require.NotNil(t, commit) + require.NotNil(t, frames) + + n := uint8(math.Log2(float64(params.NumEvaluations()))) + fs := fft.NewFFTSettings(n) + require.NotNil(t, fs) + + lc := enc.Fs.ExpandedRootsOfUnity[uint64(0)] + require.NotNil(t, lc) + + g2Atn, err := kzg.ReadG2Point(uint64(len(frames[0].Coeffs)), kzgConfig) + require.Nil(t, err) + assert.Nil(t, verifier.VerifyFrame(&frames[0], enc.Ks, commit, &lc, &g2Atn)) +} diff --git a/op-service/eigenda/encoding/kzg/verifier/length_test.go b/op-service/eigenda/encoding/kzg/verifier/length_test.go new file mode 100644 index 000000000..176f413e2 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/verifier/length_test.go @@ -0,0 +1,38 @@ +package verifier_test + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/prover" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/verifier" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLengthProof(t *testing.T) { + + group, _ := prover.NewProver(kzgConfig, true) + v, _ := verifier.NewVerifier(kzgConfig, true) + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(gettysburgAddressBytes))) + enc, err := group.GetKzgEncoder(params) + require.Nil(t, err) + + numBlob := 5 + for z := 0; z < numBlob; z++ { + extra := make([]byte, z*32*2) + inputBytes := append(gettysburgAddressBytes, extra...) + inputFr, err := rs.ToFrArray(inputBytes) + require.Nil(t, err) + + _, lengthCommitment, lengthProof, _, _, err := enc.Encode(inputFr) + require.Nil(t, err) + + length := len(inputFr) + assert.NoError(t, v.VerifyCommit(lengthCommitment, lengthProof, uint64(length)), "low degree verification failed\n") + + length = len(inputFr) - 10 + assert.Error(t, v.VerifyCommit(lengthCommitment, lengthProof, uint64(length)), "low degree verification failed\n") + } +} diff --git a/op-service/eigenda/encoding/kzg/verifier/multiframe.go b/op-service/eigenda/encoding/kzg/verifier/multiframe.go new file mode 100644 index 000000000..6a7357248 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/verifier/multiframe.go @@ -0,0 +1,245 @@ +package verifier + +import ( + "errors" + "fmt" + "math" + "math/big" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" +) + +// Sample is the basic unit for a verification +// A blob may contain multiple Samples +type Sample struct { + Commitment bn254.G1Affine + Proof bn254.G1Affine + RowIndex int // corresponds to a row in the verification matrix + Coeffs []fr.Element + X uint // X is the evaluating index which corresponds to the leading coset +} + +// the rhsG1 consists of three terms, see https://ethresear.ch/t/a-universal-verification-equation-for-data-availability-sampling/13240/1 +func genRhsG1(samples []Sample, randomsFr []fr.Element, m int, params encoding.EncodingParams, ks *kzg.KZGSettings, proofs []bn254.G1Affine) (*bn254.G1Affine, error) { + n := len(samples) + commits := make([]bn254.G1Affine, m) + D := params.ChunkLength + + var tmp fr.Element + + // first term + // get coeffs to compute the aggregated commitment + // note the coeff is affected by how many chunks are validated per blob + // if x chunks are sampled from one blob, we need to compute the sum of all x random field element corresponding to each sample + aggCommitCoeffs := make([]fr.Element, m) + setCommit := make([]bool, m) + for k := 0; k < n; k++ { + s := samples[k] + row := s.RowIndex + + aggCommitCoeffs[row].Add(&aggCommitCoeffs[row], &randomsFr[k]) + + if !setCommit[row] { + commits[row].Set(&s.Commitment) + + setCommit[row] = true + } else { + + if !commits[row].Equal(&s.Commitment) { + return nil, errors.New("samples of the same row has different commitments") + } + } + } + + var aggCommit bn254.G1Affine + _, err := aggCommit.MultiExp(commits, aggCommitCoeffs, ecc.MultiExpConfig{}) + if err != nil { + return nil, err + } + + // second term + // compute the aggregated interpolation polynomial + aggPolyCoeffs := make([]fr.Element, D) + + // we sum over the weighted coefficients (by the random field element) over all D monomial in all n samples + for k := 0; k < n; k++ { + coeffs := samples[k].Coeffs + + rk := randomsFr[k] + // for each monomial in a given polynomial, multiply its coefficient with the corresponding random field, + // then sum it with others. Given ChunkLen (D) is identical for all samples in a subBatch. + // The operation is always valid. + for j := uint64(0); j < D; j++ { + tmp.Mul(&coeffs[j], &rk) + //bls.MulModFr(&tmp, &coeffs[j], &rk) + //bls.AddModFr(&aggPolyCoeffs[j], &aggPolyCoeffs[j], &tmp) + aggPolyCoeffs[j].Add(&aggPolyCoeffs[j], &tmp) + } + } + + // All samples in a subBatch has identical chunkLen + var aggPolyG1 bn254.G1Affine + _, err = aggPolyG1.MultiExp(ks.Srs.G1[:D], aggPolyCoeffs, ecc.MultiExpConfig{}) + if err != nil { + return nil, err + } + + // third term + // leading coset is an evaluation index, here we compute the weighted leading coset evaluation by random fields + lcCoeffs := make([]fr.Element, n) + + // get leading coset powers + leadingDs := make([]fr.Element, n) + bigD := big.NewInt(int64(D)) + + for k := 0; k < n; k++ { + + // got the leading coset field element + h := ks.ExpandedRootsOfUnity[samples[k].X] + var hPow fr.Element + hPow.Exp(h, bigD) + leadingDs[k].Set(&hPow) + } + + // applying the random weights to leading coset elements + for k := 0; k < n; k++ { + rk := randomsFr[k] + + lcCoeffs[k].Mul(&rk, &leadingDs[k]) + } + + var offsetG1 bn254.G1Affine + _, err = offsetG1.MultiExp(proofs, lcCoeffs, ecc.MultiExpConfig{}) + if err != nil { + return nil, err + } + + var rhsG1 bn254.G1Affine + + rhsG1.Sub(&aggCommit, &aggPolyG1) + + rhsG1.Add(&rhsG1, &offsetG1) + return &rhsG1, nil +} + +// TODO(mooselumph): Cleanup this function +func (v *Verifier) UniversalVerifySubBatch(params encoding.EncodingParams, samplesCore []encoding.Sample, numBlobs int) error { + + samples := make([]Sample, len(samplesCore)) + + for i, sc := range samplesCore { + x, err := rs.GetLeadingCosetIndex( + uint64(sc.AssignmentIndex), + params.NumChunks, + ) + if err != nil { + return err + } + + sample := Sample{ + Commitment: (bn254.G1Affine)(*sc.Commitment), + Proof: sc.Chunk.Proof, + RowIndex: sc.BlobIndex, + Coeffs: sc.Chunk.Coeffs, + X: uint(x), + } + samples[i] = sample + } + + return v.UniversalVerify(params, samples, numBlobs) +} + +// UniversalVerify implements batch verification on a set of chunks given the same chunk dimension (chunkLen, numChunk). +// The details is given in Ethereum Research post whose authors are George Kadianakis, Ansgar Dietrichs, Dankrad Feist +// https://ethresear.ch/t/a-universal-verification-equation-for-data-availability-sampling/13240 +// +// m is number of blob, samples is a list of chunks +// +// The order of samples do not matter. +// Each sample need not have unique row, it is possible that multiple chunks of the same blob are validated altogether +func (v *Verifier) UniversalVerify(params encoding.EncodingParams, samples []Sample, m int) error { + // precheck + for i, s := range samples { + if s.RowIndex >= m { + fmt.Printf("sample %v has %v Row, but there are only %v blobs\n", i, s.RowIndex, m) + return errors.New("sample.RowIndex and numBlob are inconsistent") + } + } + + verifier, err := v.GetKzgVerifier(params) + if err != nil { + return err + } + ks := verifier.Ks + + D := params.ChunkLength + + if D > v.SRSNumberToLoad { + return fmt.Errorf("requested chunkLen %v is larger than Loaded SRS points %v", D, v.SRSNumberToLoad) + } + + n := len(samples) + fmt.Printf("Batch verify %v frames of %v symbols out of %v blobs \n", n, params.ChunkLength, m) + if n == 0 { + return errors.New("the number of samples (i.e. chunks) must not be empty") + } + + // generate random field elements to aggregate equality check + randomsFr, err := CreateRandomnessVector(n) + if err != nil { + return err + } + + // array of proofs + proofs := make([]bn254.G1Affine, n) + for i := 0; i < n; i++ { + + proofs[i].Set(&samples[i].Proof) + } + + // lhs g1 + + var lhsG1 bn254.G1Affine + _, err = lhsG1.MultiExp(proofs, randomsFr, ecc.MultiExpConfig{}) + if err != nil { + return err + } + // lhs g2 + exponent := uint64(math.Log2(float64(D))) + G2atD, err := kzg.ReadG2PointOnPowerOf2(exponent, v.KzgConfig) + + if err != nil { + // then try to access if there is a full list of g2 srs + G2atD, err = kzg.ReadG2Point(D, v.KzgConfig) + if err != nil { + return err + } + fmt.Println("Accessed the entire G2") + } + + lhsG2 := &G2atD + + // rhs g2 + rhsG2 := &kzg.GenG2 + + // rhs g1 + rhsG1, err := genRhsG1( + samples, + randomsFr, + m, + params, + ks, + proofs, + ) + if err != nil { + return err + } + + return PairingsVerify(&lhsG1, lhsG2, rhsG1, rhsG2) +} diff --git a/op-service/eigenda/encoding/kzg/verifier/multiframe_test.go b/op-service/eigenda/encoding/kzg/verifier/multiframe_test.go new file mode 100644 index 000000000..9651739e8 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/verifier/multiframe_test.go @@ -0,0 +1,101 @@ +package verifier_test + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/prover" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/verifier" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUniversalVerify(t *testing.T) { + + group, _ := prover.NewProver(kzgConfig, true) + v, _ := verifier.NewVerifier(kzgConfig, true) + + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(gettysburgAddressBytes))) + enc, err := group.GetKzgEncoder(params) + require.Nil(t, err) + + numBlob := 5 + samples := make([]verifier.Sample, 0) + for z := 0; z < numBlob; z++ { + inputFr, err := rs.ToFrArray(gettysburgAddressBytes) + require.Nil(t, err) + + commit, _, _, frames, fIndices, err := enc.Encode(inputFr) + require.Nil(t, err) + + // create samples + for i := 0; i < len(frames); i++ { + f := frames[i] + j := fIndices[i] + + q, err := rs.GetLeadingCosetIndex(uint64(i), numSys+numPar) + require.Nil(t, err) + + assert.Equal(t, j, q, "leading coset inconsistency") + + sample := verifier.Sample{ + Commitment: *commit, + Proof: f.Proof, + RowIndex: z, + Coeffs: f.Coeffs, + X: uint(q), + } + samples = append(samples, sample) + } + } + + assert.True(t, v.UniversalVerify(params, samples, numBlob) == nil, "universal batch verification failed\n") +} + +func TestUniversalVerifyWithPowerOf2G2(t *testing.T) { + + kzgConfigCopy := *kzgConfig + group, err := prover.NewProver(&kzgConfigCopy, true) + assert.NoError(t, err) + group.KzgConfig.G2Path = "" + + v, err := verifier.NewVerifier(kzgConfig, true) + assert.NoError(t, err) + + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(gettysburgAddressBytes))) + enc, err := group.GetKzgEncoder(params) + assert.NoError(t, err) + + numBlob := 5 + samples := make([]verifier.Sample, 0) + for z := 0; z < numBlob; z++ { + inputFr, err := rs.ToFrArray(gettysburgAddressBytes) + require.Nil(t, err) + + commit, _, _, frames, fIndices, err := enc.Encode(inputFr) + require.Nil(t, err) + + // create samples + for i := 0; i < len(frames); i++ { + f := frames[i] + j := fIndices[i] + + q, err := rs.GetLeadingCosetIndex(uint64(i), numSys+numPar) + require.Nil(t, err) + + assert.Equal(t, j, q, "leading coset inconsistency") + + sample := verifier.Sample{ + Commitment: *commit, + Proof: f.Proof, + RowIndex: z, + Coeffs: f.Coeffs, + X: uint(q), + } + samples = append(samples, sample) + } + } + + assert.True(t, v.UniversalVerify(params, samples, numBlob) == nil, "universal batch verification failed\n") +} diff --git a/op-service/eigenda/encoding/kzg/verifier/verifier.go b/op-service/eigenda/encoding/kzg/verifier/verifier.go new file mode 100644 index 000000000..f2b562270 --- /dev/null +++ b/op-service/eigenda/encoding/kzg/verifier/verifier.go @@ -0,0 +1,339 @@ +package verifier + +import ( + "errors" + "fmt" + "log" + "math" + "math/big" + "runtime" + "sync" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/fft" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +type Verifier struct { + *kzg.KzgConfig + Srs *kzg.SRS + G2Trailing []bn254.G2Affine + mu sync.Mutex + LoadG2Points bool + + ParametrizedVerifiers map[encoding.EncodingParams]*ParametrizedVerifier +} + +var _ encoding.Verifier = &Verifier{} + +func NewVerifier(config *kzg.KzgConfig, loadG2Points bool) (*Verifier, error) { + + if config.SRSNumberToLoad > config.SRSOrder { + return nil, errors.New("SRSOrder is less than srsNumberToLoad") + } + + // read the whole order, and treat it as entire SRS for low degree proof + s1, err := kzg.ReadG1Points(config.G1Path, config.SRSNumberToLoad, config.NumWorker) + if err != nil { + log.Println("failed to read G1 points", err) + return nil, err + } + + s2 := make([]bn254.G2Affine, 0) + g2Trailing := make([]bn254.G2Affine, 0) + + // PreloadEncoder is by default not used by operator node, PreloadEncoder + if loadG2Points { + if len(config.G2Path) == 0 { + return nil, errors.New("G2Path is empty. However, object needs to load G2Points") + } + + s2, err = kzg.ReadG2Points(config.G2Path, config.SRSNumberToLoad, config.NumWorker) + if err != nil { + log.Println("failed to read G2 points", err) + return nil, err + } + + g2Trailing, err = kzg.ReadG2PointSection( + config.G2Path, + config.SRSOrder-config.SRSNumberToLoad, + config.SRSOrder, // last exclusive + config.NumWorker, + ) + if err != nil { + return nil, err + } + } else { + if len(config.G2PowerOf2Path) == 0 && len(config.G2Path) == 0 { + return nil, errors.New("both G2Path and G2PowerOf2Path are empty. However, object needs to load G2Points") + } + + if len(config.G2PowerOf2Path) != 0 { + if config.SRSOrder == 0 { + return nil, errors.New("SRS order cannot be 0") + } + + maxPower := uint64(math.Log2(float64(config.SRSOrder))) + _, err := kzg.ReadG2PointSection(config.G2PowerOf2Path, 0, maxPower, 1) + if err != nil { + return nil, fmt.Errorf("file located at %v is invalid", config.G2PowerOf2Path) + } + } else { + log.Println("verifier requires accesses to entire g2 points. It is a legacy usage. For most operators, it is likely because G2_POWER_OF_2_PATH is improperly configured.") + } + } + srs, err := kzg.NewSrs(s1, s2) + if err != nil { + log.Println("Could not create srs", err) + return nil, err + } + + fmt.Println("numthread", runtime.GOMAXPROCS(0)) + + encoderGroup := &Verifier{ + KzgConfig: config, + Srs: srs, + G2Trailing: g2Trailing, + ParametrizedVerifiers: make(map[encoding.EncodingParams]*ParametrizedVerifier), + LoadG2Points: loadG2Points, + } + + return encoderGroup, nil + +} + +type ParametrizedVerifier struct { + *kzg.KzgConfig + Srs *kzg.SRS + + *rs.Encoder + + Fs *fft.FFTSettings + Ks *kzg.KZGSettings +} + +func (g *Verifier) GetKzgVerifier(params encoding.EncodingParams) (*ParametrizedVerifier, error) { + g.mu.Lock() + defer g.mu.Unlock() + + if err := params.Validate(); err != nil { + return nil, err + } + + ver, ok := g.ParametrizedVerifiers[params] + if ok { + return ver, nil + } + + ver, err := g.newKzgVerifier(params) + if err == nil { + g.ParametrizedVerifiers[params] = ver + } + + return ver, err +} + +func (g *Verifier) NewKzgVerifier(params encoding.EncodingParams) (*ParametrizedVerifier, error) { + g.mu.Lock() + defer g.mu.Unlock() + return g.newKzgVerifier(params) +} + +func (g *Verifier) newKzgVerifier(params encoding.EncodingParams) (*ParametrizedVerifier, error) { + + if err := params.Validate(); err != nil { + return nil, err + } + + n := uint8(math.Log2(float64(params.NumEvaluations()))) + fs := fft.NewFFTSettings(n) + ks, err := kzg.NewKZGSettings(fs, g.Srs) + + if err != nil { + return nil, err + } + + encoder, err := rs.NewEncoder(params, g.Verbose) + if err != nil { + log.Println("Could not create encoder: ", err) + return nil, err + } + + return &ParametrizedVerifier{ + KzgConfig: g.KzgConfig, + Srs: g.Srs, + Encoder: encoder, + Fs: fs, + Ks: ks, + }, nil +} + +func (v *Verifier) VerifyBlobLength(commitments encoding.BlobCommitments) error { + return v.VerifyCommit((*bn254.G2Affine)(commitments.LengthCommitment), (*bn254.G2Affine)(commitments.LengthProof), uint64(commitments.Length)) + +} + +// VerifyCommit verifies the low degree proof; since it doesn't depend on the encoding parameters +// we leave it as a method of the KzgEncoderGroup +func (v *Verifier) VerifyCommit(lengthCommit *bn254.G2Affine, lengthProof *bn254.G2Affine, length uint64) error { + + g1Challenge, err := kzg.ReadG1Point(v.SRSOrder-length, v.KzgConfig) + if err != nil { + return err + } + + err = VerifyLengthProof(lengthCommit, lengthProof, &g1Challenge) + if err != nil { + return fmt.Errorf("%v . %v ", "low degree proof fails", err) + } else { + return nil + } +} + +// The function verify low degree proof against a poly commitment +// We wish to show x^shift poly = shiftedPoly, with +// With shift = SRSOrder - length and +// proof = commit(shiftedPoly) on G1 +// so we can verify by checking +// e( commit_1, [x^shift]_2) = e( proof_1, G_2 ) +func VerifyLengthProof(lengthCommit *bn254.G2Affine, proof *bn254.G2Affine, g1Challenge *bn254.G1Affine) error { + return PairingsVerify(g1Challenge, lengthCommit, &kzg.GenG1, proof) +} + +func (v *Verifier) VerifyFrames(frames []*encoding.Frame, indices []encoding.ChunkNumber, commitments encoding.BlobCommitments, params encoding.EncodingParams) error { + + verifier, err := v.GetKzgVerifier(params) + if err != nil { + return err + } + + for ind := range frames { + err = verifier.VerifyFrame( + (*bn254.G1Affine)(commitments.Commitment), + frames[ind], + uint64(indices[ind]), + ) + + if err != nil { + return err + } + } + + return nil + +} + +func (v *ParametrizedVerifier) VerifyFrame(commit *bn254.G1Affine, f *encoding.Frame, index uint64) error { + + j, err := rs.GetLeadingCosetIndex( + uint64(index), + v.NumChunks, + ) + if err != nil { + return err + } + + g2Atn, err := kzg.ReadG2Point(uint64(len(f.Coeffs)), v.KzgConfig) + if err != nil { + return err + } + + err = VerifyFrame(f, v.Ks, commit, &v.Ks.ExpandedRootsOfUnity[j], &g2Atn) + + if err != nil { + return fmt.Errorf("%v . %v ", "VerifyFrame Error", err) + } else { + return nil + } +} + +// Verify function assumes the Data stored is coefficients of coset's interpolating poly +func VerifyFrame(f *encoding.Frame, ks *kzg.KZGSettings, commitment *bn254.G1Affine, x *fr.Element, g2Atn *bn254.G2Affine) error { + var xPow fr.Element + xPow.SetOne() + + for i := 0; i < len(f.Coeffs); i++ { + xPow.Mul(&xPow, x) + } + + var xPowBigInt big.Int + + // [x^n]_2 + var xn2 bn254.G2Affine + + xn2.ScalarMultiplication(&kzg.GenG2, xPow.BigInt(&xPowBigInt)) + + // [s^n - x^n]_2 + var xnMinusYn bn254.G2Affine + xnMinusYn.Sub(g2Atn, &xn2) + + // [interpolation_polynomial(s)]_1 + var is1 bn254.G1Affine + config := ecc.MultiExpConfig{} + _, err := is1.MultiExp(ks.Srs.G1[:len(f.Coeffs)], f.Coeffs, config) + if err != nil { + return err + } + + // [commitment - interpolation_polynomial(s)]_1 = [commit]_1 - [interpolation_polynomial(s)]_1 + var commitMinusInterpolation bn254.G1Affine + commitMinusInterpolation.Sub(commitment, &is1) + + // Verify the pairing equation + // + // e([commitment - interpolation_polynomial(s)], [1]) = e([proof], [s^n - x^n]) + // equivalent to + // e([commitment - interpolation_polynomial]^(-1), [1]) * e([proof], [s^n - x^n]) = 1_T + // + + return PairingsVerify(&commitMinusInterpolation, &kzg.GenG2, &f.Proof, &xnMinusYn) +} + +// Decode takes in the chunks, indices, and encoding parameters and returns the decoded blob +// The result is trimmed to the given maxInputSize. +func (v *Verifier) Decode(chunks []*encoding.Frame, indices []encoding.ChunkNumber, params encoding.EncodingParams, maxInputSize uint64) ([]byte, error) { + frames := make([]rs.Frame, len(chunks)) + for i := range chunks { + frames[i] = rs.Frame{ + Coeffs: chunks[i].Coeffs, + } + } + encoder, err := v.GetKzgVerifier(params) + if err != nil { + return nil, err + } + + return encoder.Decode(frames, toUint64Array(indices), maxInputSize) +} + +func toUint64Array(chunkIndices []encoding.ChunkNumber) []uint64 { + res := make([]uint64, len(chunkIndices)) + for i, d := range chunkIndices { + res[i] = uint64(d) + } + return res +} + +func PairingsVerify(a1 *bn254.G1Affine, a2 *bn254.G2Affine, b1 *bn254.G1Affine, b2 *bn254.G2Affine) error { + var negB1 bn254.G1Affine + negB1.Neg(b1) + + P := [2]bn254.G1Affine{*a1, negB1} + Q := [2]bn254.G2Affine{*a2, *b2} + + ok, err := bn254.PairingCheck(P[:], Q[:]) + if err != nil { + return err + } + if !ok { + return errors.New("PairingCheck pairing not ok. SRS is invalid") + } + + return nil +} diff --git a/op-service/eigenda/encoding/kzg/verifier/verifier_test.go b/op-service/eigenda/encoding/kzg/verifier/verifier_test.go new file mode 100644 index 000000000..fa1451c7e --- /dev/null +++ b/op-service/eigenda/encoding/kzg/verifier/verifier_test.go @@ -0,0 +1,142 @@ +package verifier_test + +import ( + "crypto/rand" + "fmt" + "log" + "os" + "runtime" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/prover" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/verifier" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/utils/codec" + "github.com/stretchr/testify/assert" +) + +var ( + gettysburgAddressBytes = codec.ConvertByPaddingEmptyByte([]byte("Fourscore and seven years ago our fathers brought forth, on this continent, a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived, and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting-place for those who here gave their lives, that that nation might live. It is altogether fitting and proper that we should do this. But, in a larger sense, we cannot dedicate, we cannot consecrate—we cannot hallow—this ground. The brave men, living and dead, who struggled here, have consecrated it far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they here gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom, and that government of the people, by the people, for the people, shall not perish from the earth.")) + kzgConfig *kzg.KzgConfig + numNode uint64 + numSys uint64 + numPar uint64 +) + +func TestMain(m *testing.M) { + setup() + result := m.Run() + teardown() + os.Exit(result) +} + +func setup() { + log.Println("Setting up suite") + + kzgConfig = &kzg.KzgConfig{ + G1Path: "../../../inabox/resources/kzg/g1.point", + G2Path: "../../../inabox/resources/kzg/g2.point", + G2PowerOf2Path: "../../../inabox/resources/kzg/g2.point.powerOf2", + CacheDir: "../../../inabox/resources/kzg/SRSTables", + SRSOrder: 3000, + SRSNumberToLoad: 2900, + NumWorker: uint64(runtime.GOMAXPROCS(0)), + } + + numNode = uint64(4) + numSys = uint64(3) + numPar = numNode - numSys + +} + +func teardown() { + log.Println("Tearing down") + os.RemoveAll("./data") +} + +// var control interface{ Stop() } + +func TestBenchmarkVerifyChunks(t *testing.T) { + t.Skip("This test is meant to be run manually, not as part of the test suite") + + p, _ := prover.NewProver(kzgConfig, true) + v, _ := verifier.NewVerifier(kzgConfig, true) + + chunkLengths := []uint64{64, 128, 256, 512, 1024, 2048, 4096, 8192} + chunkCounts := []int{4, 8, 16} + + file, err := os.Create("benchmark_results.csv") + if err != nil { + t.Fatalf("Failed to open file for writing: %v", err) + } + defer file.Close() + + fmt.Fprintln(file, "numChunks,chunkLength,ns/op,allocs/op") + + for _, chunkLength := range chunkLengths { + + blobSize := chunkLength * 32 * 2 + params := encoding.EncodingParams{ + ChunkLength: chunkLength, + NumChunks: 16, + } + blob := make([]byte, blobSize) + _, err = rand.Read(blob) + assert.NoError(t, err) + + commitments, chunks, err := p.EncodeAndProve(blob, params) + assert.NoError(t, err) + + indices := make([]encoding.ChunkNumber, params.NumChunks) + for i := range indices { + indices[i] = encoding.ChunkNumber(i) + } + + for _, numChunks := range chunkCounts { + + result := testing.Benchmark(func(b *testing.B) { + for i := 0; i < b.N; i++ { + // control = profile.Start(profile.ProfilePath(".")) + err := v.VerifyFrames(chunks[:numChunks], indices[:numChunks], commitments, params) + assert.NoError(t, err) + // control.Stop() + } + }) + // Print results in CSV format + fmt.Fprintf(file, "%d,%d,%d,%d\n", numChunks, chunkLength, result.NsPerOp(), result.AllocsPerOp()) + + } + } + +} + +func BenchmarkVerifyBlob(b *testing.B) { + + p, _ := prover.NewProver(kzgConfig, true) + v, _ := verifier.NewVerifier(kzgConfig, true) + + params := encoding.EncodingParams{ + ChunkLength: 256, + NumChunks: 8, + } + blobSize := 8 * 256 + numSamples := 30 + blobs := make([][]byte, numSamples) + for i := 0; i < numSamples; i++ { + blob := make([]byte, blobSize) + _, _ = rand.Read(blob) + blobs[i] = blob + } + + commitments, _, err := p.EncodeAndProve(blobs[0], params) + assert.NoError(b, err) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + err = v.VerifyBlobLength(commitments) + assert.NoError(b, err) + } + +} diff --git a/op-service/eigenda/encoding/mock/encoder.go b/op-service/eigenda/encoding/mock/encoder.go new file mode 100644 index 000000000..4f41da377 --- /dev/null +++ b/op-service/eigenda/encoding/mock/encoder.go @@ -0,0 +1,54 @@ +package encoding + +import ( + "time" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/stretchr/testify/mock" +) + +type MockEncoder struct { + mock.Mock + + Delay time.Duration +} + +var _ encoding.Prover = &MockEncoder{} + +var _ encoding.Verifier = &MockEncoder{} + +func (e *MockEncoder) EncodeAndProve(data []byte, params encoding.EncodingParams) (encoding.BlobCommitments, []*encoding.Frame, error) { + args := e.Called(data, params) + time.Sleep(e.Delay) + return args.Get(0).(encoding.BlobCommitments), args.Get(1).([]*encoding.Frame), args.Error(2) +} + +func (e *MockEncoder) VerifyFrames(chunks []*encoding.Frame, indices []encoding.ChunkNumber, commitments encoding.BlobCommitments, params encoding.EncodingParams) error { + args := e.Called(chunks, indices, commitments, params) + time.Sleep(e.Delay) + return args.Error(0) +} + +func (e *MockEncoder) UniversalVerifySubBatch(params encoding.EncodingParams, samples []encoding.Sample, numBlobs int) error { + args := e.Called(params, samples, numBlobs) + time.Sleep(e.Delay) + return args.Error(0) +} +func (e *MockEncoder) VerifyCommitEquivalenceBatch(commitments []encoding.BlobCommitments) error { + args := e.Called(commitments) + time.Sleep(e.Delay) + return args.Error(0) +} + +func (e *MockEncoder) VerifyBlobLength(commitments encoding.BlobCommitments) error { + + args := e.Called(commitments) + time.Sleep(e.Delay) + return args.Error(0) +} + +func (e *MockEncoder) Decode(chunks []*encoding.Frame, indices []encoding.ChunkNumber, params encoding.EncodingParams, maxInputSize uint64) ([]byte, error) { + args := e.Called(chunks, indices, params, maxInputSize) + time.Sleep(e.Delay) + return args.Get(0).([]byte), args.Error(1) +} diff --git a/op-service/eigenda/encoding/params.go b/op-service/eigenda/encoding/params.go new file mode 100644 index 000000000..e334fb896 --- /dev/null +++ b/op-service/eigenda/encoding/params.go @@ -0,0 +1,75 @@ +package encoding + +import ( + "errors" + "fmt" + + "golang.org/x/exp/constraints" +) + +var ( + ErrInvalidParams = errors.New("invalid encoding params") +) + +type EncodingParams struct { + ChunkLength uint64 // ChunkSize is the length of the chunk in symbols + NumChunks uint64 +} + +func (p EncodingParams) ChunkDegree() uint64 { + return p.ChunkLength - 1 +} + +func (p EncodingParams) NumEvaluations() uint64 { + return p.NumChunks * p.ChunkLength +} + +func (p EncodingParams) Validate() error { + + if NextPowerOf2(p.NumChunks) != p.NumChunks { + return ErrInvalidParams + } + + if NextPowerOf2(p.ChunkLength) != p.ChunkLength { + return ErrInvalidParams + } + + return nil +} + +func ParamsFromMins[T constraints.Integer](minChunkLength, minNumChunks T) EncodingParams { + return EncodingParams{ + NumChunks: NextPowerOf2(uint64(minNumChunks)), + ChunkLength: NextPowerOf2(uint64(minChunkLength)), + } +} + +func ParamsFromSysPar(numSys, numPar, dataSize uint64) EncodingParams { + + numNodes := numSys + numPar + dataLen := roundUpDivide(dataSize, BYTES_PER_SYMBOL) + chunkLen := roundUpDivide(dataLen, numSys) + return ParamsFromMins(chunkLen, numNodes) + +} + +func GetNumSys(dataSize uint64, chunkLen uint64) uint64 { + dataLen := roundUpDivide(dataSize, BYTES_PER_SYMBOL) + numSys := dataLen / chunkLen + return numSys +} + +// ValidateEncodingParams takes in the encoding parameters and returns an error if they are invalid. +func ValidateEncodingParams(params EncodingParams, blobLength, SRSOrder int) error { + + if int(params.ChunkLength*params.NumChunks) >= SRSOrder { + return fmt.Errorf("the supplied encoding parameters are not valid with respect to the SRS. ChunkLength: %d, NumChunks: %d, SRSOrder: %d", params.ChunkLength, params.NumChunks, SRSOrder) + } + + if int(params.ChunkLength*params.NumChunks) < blobLength { + return errors.New("the supplied encoding parameters are not sufficient for the size of the data input") + } + + return nil + +} diff --git a/op-service/eigenda/encoding/rs/decode.go b/op-service/eigenda/encoding/rs/decode.go new file mode 100644 index 000000000..a32b3befe --- /dev/null +++ b/op-service/eigenda/encoding/rs/decode.go @@ -0,0 +1,79 @@ +package rs + +import ( + "errors" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +// Decode data when some chunks from systematic nodes are lost. It first uses FFT to recover +// the whole polynomial. Then it extracts only the systematic chunks. +// It takes a list of available frame, and return the original encoded data +// storing the evaluation points, since it is where RS is applied. The input frame contains +// the coefficient of the interpolating polynomina, hence interpolation is needed before +// recovery. +// maxInputSize is the upper bound of the original data size. This is needed because +// the frames and indices don't encode the length of the original data. If maxInputSize +// is smaller than the original input size, decoded data will be trimmed to fit the maxInputSize. +func (g *Encoder) Decode(frames []Frame, indices []uint64, maxInputSize uint64) ([]byte, error) { + numSys := encoding.GetNumSys(maxInputSize, g.ChunkLength) + + if uint64(len(frames)) < numSys { + return nil, errors.New("number of frame must be sufficient") + } + + samples := make([]*fr.Element, g.NumEvaluations()) + // copy evals based on frame coeffs into samples + for i, d := range indices { + f := frames[i] + e, err := GetLeadingCosetIndex(d, g.NumChunks) + if err != nil { + return nil, err + } + + evals, err := g.GetInterpolationPolyEval(f.Coeffs, uint32(e)) + if err != nil { + return nil, err + } + + // Some pattern i butterfly swap. Find the leading coset, then increment by number of coset + for j := uint64(0); j < g.ChunkLength; j++ { + p := j*g.NumChunks + uint64(e) + samples[p] = new(fr.Element) + + samples[p].Set(&evals[j]) + } + } + + reconstructedData := make([]fr.Element, g.NumEvaluations()) + missingIndices := false + for i, s := range samples { + if s == nil { + missingIndices = true + break + } + reconstructedData[i] = *s + } + + if missingIndices { + var err error + reconstructedData, err = g.Fs.RecoverPolyFromSamples( + samples, + g.Fs.ZeroPolyViaMultiplication, + ) + if err != nil { + return nil, err + } + } + + reconstructedPoly, err := g.Fs.FFT(reconstructedData, true) + if err != nil { + return nil, err + } + + data := ToByteArray(reconstructedPoly, maxInputSize) + + return data, nil +} diff --git a/op-service/eigenda/encoding/rs/encode.go b/op-service/eigenda/encoding/rs/encode.go new file mode 100644 index 000000000..6931c4a6d --- /dev/null +++ b/op-service/eigenda/encoding/rs/encode.go @@ -0,0 +1,179 @@ +package rs + +import ( + "errors" + "fmt" + "log" + "time" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + rb "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/utils/reverseBits" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +type GlobalPoly struct { + Coeffs []fr.Element + Values []fr.Element +} + +// just a wrapper to take bytes not Fr Element +func (g *Encoder) EncodeBytes(inputBytes []byte) (*GlobalPoly, []Frame, []uint32, error) { + inputFr, err := ToFrArray(inputBytes) + if err != nil { + return nil, nil, nil, fmt.Errorf("cannot convert bytes to field elements, %w", err) + } + return g.Encode(inputFr) +} + +// Encode function takes input in unit of Fr Element, creates a kzg commit and a list of frames +// which contains a list of multireveal interpolating polynomial coefficients, a G1 proof and a +// low degree proof corresponding to the interpolating polynomial. Each frame is an independent +// group of data verifiable to the kzg commitment. The encoding functions ensures that in each +// frame, the multireveal interpolating coefficients are identical to the part of input bytes +// in the form of field element. The extra returned integer list corresponds to which leading +// coset root of unity, the frame is proving against, which can be deduced from a frame's index +func (g *Encoder) Encode(inputFr []fr.Element) (*GlobalPoly, []Frame, []uint32, error) { + start := time.Now() + intermediate := time.Now() + + polyCoeffs := inputFr + + // extend data based on Sys, Par ratio. The returned fullCoeffsPoly is padded with 0 to ease proof + polyEvals, _, err := g.ExtendPolyEval(polyCoeffs) + if err != nil { + return nil, nil, nil, err + } + + poly := &GlobalPoly{ + Values: polyEvals, + Coeffs: polyCoeffs, + } + + if g.verbose { + log.Printf(" Extending evaluation takes %v\n", time.Since(intermediate)) + } + + // create frames to group relevant info + frames, indices, err := g.MakeFrames(polyEvals) + if err != nil { + return nil, nil, nil, err + } + + log.Printf(" SUMMARY: RSEncode %v byte among %v numChunks with chunkLength %v takes %v\n", + len(inputFr)*encoding.BYTES_PER_SYMBOL, g.NumChunks, g.ChunkLength, time.Since(start)) + + return poly, frames, indices, nil +} + +// MakeFrames function takes extended evaluation data and bundles relevant information into Frame. +// Every frame is verifiable to the commitment. +func (g *Encoder) MakeFrames( + polyEvals []fr.Element, +) ([]Frame, []uint32, error) { + // reverse dataFr making easier to sample points + err := rb.ReverseBitOrderFr(polyEvals) + if err != nil { + return nil, nil, err + } + + indices := make([]uint32, 0) + frames := make([]Frame, g.NumChunks) + + numWorker := uint64(g.NumRSWorker) + + if numWorker > g.NumChunks { + numWorker = g.NumChunks + } + + jobChan := make(chan JobRequest, numWorker) + results := make(chan error, numWorker) + + for w := uint64(0); w < numWorker; w++ { + go g.interpolyWorker( + polyEvals, + jobChan, + results, + frames, + ) + } + + for i := uint64(0); i < g.NumChunks; i++ { + j := rb.ReverseBitsLimited(uint32(g.NumChunks), uint32(i)) + jr := JobRequest{ + Index: i, + } + jobChan <- jr + indices = append(indices, j) + } + close(jobChan) + + for w := uint64(0); w < numWorker; w++ { + interPolyErr := <-results + if interPolyErr != nil { + err = interPolyErr + } + } + + if err != nil { + return nil, nil, fmt.Errorf("proof worker error: %v", err) + } + + return frames, indices, nil +} + +// Encoding Reed Solomon using FFT +func (g *Encoder) ExtendPolyEval(coeffs []fr.Element) ([]fr.Element, []fr.Element, error) { + + if len(coeffs) > int(g.NumEvaluations()) { + return nil, nil, errors.New("the provided encoding parameters are not sufficient for the size of the data input") + } + + pdCoeffs := make([]fr.Element, g.NumEvaluations()) + for i := 0; i < len(coeffs); i++ { + pdCoeffs[i].Set(&coeffs[i]) + } + for i := len(coeffs); i < len(pdCoeffs); i++ { + pdCoeffs[i].SetZero() + } + + evals, err := g.Fs.FFT(pdCoeffs, false) + if err != nil { + return nil, nil, err + } + + return evals, pdCoeffs, nil +} + +type JobRequest struct { + Index uint64 +} + +func (g *Encoder) interpolyWorker( + polyEvals []fr.Element, + jobChan <-chan JobRequest, + results chan<- error, + frames []Frame, +) { + + for jr := range jobChan { + i := jr.Index + j := rb.ReverseBitsLimited(uint32(g.NumChunks), uint32(i)) + ys := polyEvals[g.ChunkLength*i : g.ChunkLength*(i+1)] + err := rb.ReverseBitOrderFr(ys) + if err != nil { + results <- err + continue + } + coeffs, err := g.GetInterpolationPolyCoeff(ys, uint32(j)) + if err != nil { + results <- err + continue + } + + frames[i].Coeffs = coeffs + } + + results <- nil + +} diff --git a/op-service/eigenda/encoding/rs/encode_test.go b/op-service/eigenda/encoding/rs/encode_test.go new file mode 100644 index 000000000..6dfb7101b --- /dev/null +++ b/op-service/eigenda/encoding/rs/encode_test.go @@ -0,0 +1,84 @@ +package rs_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" +) + +func TestEncodeDecode_InvertsWhenSamplingAllFrames(t *testing.T) { + teardownSuite := setupSuite(t) + defer teardownSuite(t) + + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(GETTYSBURG_ADDRESS_BYTES))) + + enc, _ := rs.NewEncoder(params, true) + require.NotNil(t, enc) + + inputFr, err := rs.ToFrArray(GETTYSBURG_ADDRESS_BYTES) + assert.Nil(t, err) + _, frames, _, err := enc.Encode(inputFr) + assert.Nil(t, err) + + // sample some frames + samples, indices := sampleFrames(frames, uint64(len(frames))) + data, err := enc.Decode(samples, indices, uint64(len(GETTYSBURG_ADDRESS_BYTES))) + + require.Nil(t, err) + require.NotNil(t, data) + + assert.Equal(t, data, GETTYSBURG_ADDRESS_BYTES) +} + +func TestEncodeDecode_InvertsWhenSamplingMissingFrame(t *testing.T) { + teardownSuite := setupSuite(t) + defer teardownSuite(t) + + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(GETTYSBURG_ADDRESS_BYTES))) + enc, _ := rs.NewEncoder(params, true) + require.NotNil(t, enc) + + inputFr, err := rs.ToFrArray(GETTYSBURG_ADDRESS_BYTES) + assert.Nil(t, err) + _, frames, _, err := enc.Encode(inputFr) + assert.Nil(t, err) + + // sample some frames + samples, indices := sampleFrames(frames, uint64(len(frames)-1)) + data, err := enc.Decode(samples, indices, uint64(len(GETTYSBURG_ADDRESS_BYTES))) + + require.Nil(t, err) + require.NotNil(t, data) + + assert.Equal(t, data, GETTYSBURG_ADDRESS_BYTES) +} + +func TestEncodeDecode_ErrorsWhenNotEnoughSampledFrames(t *testing.T) { + teardownSuite := setupSuite(t) + defer teardownSuite(t) + + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(GETTYSBURG_ADDRESS_BYTES))) + enc, _ := rs.NewEncoder(params, true) + require.NotNil(t, enc) + + fmt.Println("Num Chunks: ", enc.NumChunks) + + inputFr, err := rs.ToFrArray(GETTYSBURG_ADDRESS_BYTES) + assert.Nil(t, err) + _, frames, _, err := enc.Encode(inputFr) + assert.Nil(t, err) + + // sample some frames + samples, indices := sampleFrames(frames, uint64(len(frames)-2)) + data, err := enc.Decode(samples, indices, uint64(len(GETTYSBURG_ADDRESS_BYTES))) + + require.Nil(t, data) + require.NotNil(t, err) + + assert.EqualError(t, err, "number of frame must be sufficient") +} diff --git a/op-service/eigenda/encoding/rs/encoder.go b/op-service/eigenda/encoding/rs/encoder.go new file mode 100644 index 000000000..09793cf50 --- /dev/null +++ b/op-service/eigenda/encoding/rs/encoder.go @@ -0,0 +1,46 @@ +package rs + +import ( + "math" + "runtime" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/fft" +) + +type Encoder struct { + encoding.EncodingParams + + Fs *fft.FFTSettings + + verbose bool + + NumRSWorker int +} + +// The function creates a high level struct that determines the encoding the a data of a +// specific length under (num systematic node, num parity node) setup. A systematic node +// stores a systematic data chunk that contains part of the original data. A parity node +// stores a parity data chunk which is an encoding of the original data. A receiver that +// collects all systematic chunks can simply stitch data together to reconstruct the +// original data. When some systematic chunks are missing but identical parity chunk are +// available, the receive can go through a Reed Solomon decoding to reconstruct the +// original data. +func NewEncoder(params encoding.EncodingParams, verbose bool) (*Encoder, error) { + + err := params.Validate() + if err != nil { + return nil, err + } + + n := uint8(math.Log2(float64(params.NumEvaluations()))) + fs := fft.NewFFTSettings(n) + + return &Encoder{ + EncodingParams: params, + Fs: fs, + verbose: verbose, + NumRSWorker: runtime.GOMAXPROCS(0), + }, nil + +} diff --git a/op-service/eigenda/encoding/rs/encoder_fuzz_test.go b/op-service/eigenda/encoding/rs/encoder_fuzz_test.go new file mode 100644 index 000000000..0d548dacd --- /dev/null +++ b/op-service/eigenda/encoding/rs/encoder_fuzz_test.go @@ -0,0 +1,37 @@ +package rs_test + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" + "github.com/stretchr/testify/assert" +) + +func FuzzOnlySystematic(f *testing.F) { + + f.Add(GETTYSBURG_ADDRESS_BYTES) + f.Fuzz(func(t *testing.T, input []byte) { + + params := encoding.ParamsFromSysPar(10, 3, uint64(len(input))) + enc, err := rs.NewEncoder(params, true) + if err != nil { + t.Errorf("Error making rs: %q", err) + } + + //encode the data + _, frames, _, err := enc.EncodeBytes(input) + if err != nil { + t.Errorf("Error Encoding:\n Data:\n %q \n Err: %q", input, err) + } + + //sample the correct systematic frames + samples, indices := sampleFrames(frames, uint64(len(frames))) + + data, err := enc.Decode(samples, indices, uint64(len(input))) + if err != nil { + t.Errorf("Error Decoding:\n Data:\n %q \n Err: %q", input, err) + } + assert.Equal(t, input, data, "Input data was not equal to the decoded data") + }) +} diff --git a/op-service/eigenda/encoding/rs/encoder_suite_test.go b/op-service/eigenda/encoding/rs/encoder_suite_test.go new file mode 100644 index 000000000..aa28fa26e --- /dev/null +++ b/op-service/eigenda/encoding/rs/encoder_suite_test.go @@ -0,0 +1,45 @@ +package rs_test + +import ( + "log" + "math/rand" + "os" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/utils/codec" +) + +var ( + GETTYSBURG_ADDRESS_BYTES = codec.ConvertByPaddingEmptyByte([]byte("Fourscore and seven years ago our fathers brought forth, on this continent, a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived, and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting-place for those who here gave their lives, that that nation might live. It is altogether fitting and proper that we should do this. But, in a larger sense, we cannot dedicate, we cannot consecrate—we cannot hallow—this ground. The brave men, living and dead, who struggled here, have consecrated it far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they here gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom, and that government of the people, by the people, for the people, shall not perish from the earth.")) + numNode uint64 + numSys uint64 + numPar uint64 +) + +func setupSuite(t *testing.T) func(t *testing.T) { + log.Println("Setting up suite") + + numNode = uint64(4) + numSys = uint64(3) + numPar = numNode - numSys + + return func(t *testing.T) { + log.Println("Tearing down suite") + + os.RemoveAll("./data") + } +} + +func sampleFrames(frames []rs.Frame, num uint64) ([]rs.Frame, []uint64) { + samples := make([]rs.Frame, num) + indices := rand.Perm(len(frames)) + indices = indices[:num] + + frameIndices := make([]uint64, num) + for i, j := range indices { + samples[i] = frames[j] + frameIndices[i] = uint64(j) + } + return samples, frameIndices +} diff --git a/op-service/eigenda/encoding/rs/frame.go b/op-service/eigenda/encoding/rs/frame.go new file mode 100644 index 000000000..4b4ba0bed --- /dev/null +++ b/op-service/eigenda/encoding/rs/frame.go @@ -0,0 +1,35 @@ +package rs + +import ( + "bytes" + "encoding/gob" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +// Proof is the multireveal proof +// Coeffs is identical to input data converted into Fr element +type Frame struct { + Coeffs []fr.Element +} + +func (f *Frame) Encode() ([]byte, error) { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + err := enc.Encode(f) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func Decode(b []byte) (Frame, error) { + var f Frame + buf := bytes.NewBuffer(b) + dec := gob.NewDecoder(buf) + err := dec.Decode(&f) + if err != nil { + return Frame{}, err + } + return f, nil +} diff --git a/op-service/eigenda/encoding/rs/frame_test.go b/op-service/eigenda/encoding/rs/frame_test.go new file mode 100644 index 000000000..c63468e8b --- /dev/null +++ b/op-service/eigenda/encoding/rs/frame_test.go @@ -0,0 +1,33 @@ +package rs_test + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEncodeDecodeFrame_AreInverses(t *testing.T) { + teardownSuite := setupSuite(t) + defer teardownSuite(t) + + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(GETTYSBURG_ADDRESS_BYTES))) + enc, _ := rs.NewEncoder(params, true) + require.NotNil(t, enc) + + _, frames, _, err := enc.EncodeBytes(GETTYSBURG_ADDRESS_BYTES) + require.Nil(t, err) + require.NotNil(t, frames, err) + + b, err := frames[0].Encode() + require.Nil(t, err) + require.NotNil(t, b) + + frame, err := rs.Decode(b) + require.Nil(t, err) + require.NotNil(t, frame) + + assert.Equal(t, frame, frames[0]) +} diff --git a/op-service/eigenda/encoding/rs/interpolation.go b/op-service/eigenda/encoding/rs/interpolation.go new file mode 100644 index 000000000..405799d0d --- /dev/null +++ b/op-service/eigenda/encoding/rs/interpolation.go @@ -0,0 +1,82 @@ +package rs + +import ( + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +// Consider input data as the polynomial Coefficients, c +// This functions computes the evaluations of the such the interpolation polynomial +// Passing through input data, evaluated at series of root of unity. +// Consider the following points (w, d[0]), (wφ, d[1]), (wφ^2, d[2]), (wφ^3, d[3]) +// Suppose F be the fft matrix, then the systamtic equation that going through those points is +// d = W F c, where each row corresponds to equation being evaluated at [1, φ, φ^2, φ^3] +// where W is a diagonal matrix with diagonal [1 w w^2 w^3] for shifting the evaluation points + +// The index is transformed using FFT, for example 001 => 100, 110 => 011 +// The reason behind is because Reed Solomon extension using FFT insert evaluation within original +// Data. i.e. [o_1, o_2, o_3..] with coding ratio 0.5 becomes [o_1, p_1, o_2, p_2...] + +func (g *Encoder) GetInterpolationPolyEval( + interpolationPoly []fr.Element, + j uint32, +) ([]fr.Element, error) { + evals := make([]fr.Element, g.ChunkLength) + w := g.Fs.ExpandedRootsOfUnity[uint64(j)] + shiftedInterpolationPoly := make([]fr.Element, len(interpolationPoly)) + + //multiply each term of the polynomial by x^i so the fourier transform results in the desired evaluations + //The fourier matrix looks like + // ___ ___ + // | 1 1 1 1 . . . . | + // | 1 φ φ^2 φ^3 | + // | 1 φ^2 φ^4 φ^6 | + // | 1 φ^3 φ^6 φ^9 | = F + // | . . . | + // | . . . | + // | . . . | + // |__ __| + + // + // F * p = [p(1), p(φ), p(φ^2), ...] + // + // but we want + // + // [p(w), p(wφ), p(wφ^2), ...] + // + // we can do this by computing shiftedInterpolationPoly = q = p(wx) and then doing + // + // F * q = [p(w), p(wφ), p(wφ^2), ...] + // + // to get our desired evaluations + // cool idea protolambda :) + var wPow fr.Element + wPow.SetOne() + //var tmp, tmp2 fr.Element + for i := 0; i < len(interpolationPoly); i++ { + shiftedInterpolationPoly[i].Mul(&interpolationPoly[i], &wPow) + wPow.Mul(&wPow, &w) + } + + err := g.Fs.InplaceFFT(shiftedInterpolationPoly, evals, false) + return evals, err +} + +// Since both F W are invertible, c = W^-1 F^-1 d, convert it back. F W W^-1 F^-1 d = c +func (g *Encoder) GetInterpolationPolyCoeff(chunk []fr.Element, k uint32) ([]fr.Element, error) { + coeffs := make([]fr.Element, g.ChunkLength) + shiftedInterpolationPoly := make([]fr.Element, len(chunk)) + err := g.Fs.InplaceFFT(chunk, shiftedInterpolationPoly, true) + if err != nil { + return coeffs, err + } + + mod := int32(len(g.Fs.ExpandedRootsOfUnity) - 1) + + for i := 0; i < len(chunk); i++ { + // We can lookup the inverse power by counting RootOfUnity backward + j := (-int32(k)*int32(i))%mod + mod + coeffs[i].Mul(&shiftedInterpolationPoly[i], &g.Fs.ExpandedRootsOfUnity[j]) + } + + return coeffs, nil +} diff --git a/op-service/eigenda/encoding/rs/params.go b/op-service/eigenda/encoding/rs/params.go new file mode 100644 index 000000000..1fc955a6e --- /dev/null +++ b/op-service/eigenda/encoding/rs/params.go @@ -0,0 +1,64 @@ +package rs + +import ( + "errors" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" +) + +var ( + ErrInvalidParams = errors.New("invalid encoding params") +) + +type EncodingParams struct { + NumChunks uint64 // number of total chunks that are padded to power of 2 + ChunkLen uint64 // number of Fr symbol stored inside a chunk +} + +func (p EncodingParams) ChunkDegree() uint64 { + return p.ChunkLen - 1 +} + +func (p EncodingParams) NumEvaluations() uint64 { + return p.NumChunks * p.ChunkLen +} + +func (p EncodingParams) Validate() error { + + if NextPowerOf2(p.NumChunks) != p.NumChunks { + return ErrInvalidParams + } + + if NextPowerOf2(p.ChunkLen) != p.ChunkLen { + return ErrInvalidParams + } + + return nil +} + +func GetNumSys(dataSize uint64, chunkLen uint64) uint64 { + dataLen := RoundUpDivision(dataSize, encoding.BYTES_PER_SYMBOL) + numSys := dataLen / chunkLen + return numSys +} + +func ParamsFromMins(numChunks, chunkLen uint64) EncodingParams { + + chunkLen = NextPowerOf2(chunkLen) + numChunks = NextPowerOf2(numChunks) + + return EncodingParams{ + NumChunks: numChunks, + ChunkLen: chunkLen, + } + +} + +func GetEncodingParams(numSys, numPar, dataSize uint64) EncodingParams { + + numNodes := numSys + numPar + dataLen := RoundUpDivision(dataSize, encoding.BYTES_PER_SYMBOL) + chunkLen := RoundUpDivision(dataLen, numSys) + return ParamsFromMins(numNodes, chunkLen) + +} diff --git a/op-service/eigenda/encoding/rs/utils.go b/op-service/eigenda/encoding/rs/utils.go new file mode 100644 index 000000000..05381b86b --- /dev/null +++ b/op-service/eigenda/encoding/rs/utils.go @@ -0,0 +1,87 @@ +package rs + +import ( + "errors" + "math" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + rb "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/utils/reverseBits" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +func ToFrArray(data []byte) ([]fr.Element, error) { + numEle := GetNumElement(uint64(len(data)), encoding.BYTES_PER_SYMBOL) + eles := make([]fr.Element, numEle) + + for i := uint64(0); i < numEle; i++ { + start := i * uint64(encoding.BYTES_PER_SYMBOL) + end := (i + 1) * uint64(encoding.BYTES_PER_SYMBOL) + if end >= uint64(len(data)) { + padded := make([]byte, encoding.BYTES_PER_SYMBOL) + copy(padded, data[start:]) + err := eles[i].SetBytesCanonical(padded) + if err != nil { + return nil, err + } + } else { + err := eles[i].SetBytesCanonical(data[start:end]) + if err != nil { + return nil, err + } + } + } + + return eles, nil +} + +// ToByteArray converts a list of Fr to a byte array +func ToByteArray(dataFr []fr.Element, maxDataSize uint64) []byte { + n := len(dataFr) + dataSize := int(math.Min( + float64(n*encoding.BYTES_PER_SYMBOL), + float64(maxDataSize), + )) + data := make([]byte, dataSize) + for i := 0; i < n; i++ { + v := dataFr[i].Bytes() + + start := i * encoding.BYTES_PER_SYMBOL + end := (i + 1) * encoding.BYTES_PER_SYMBOL + + if uint64(end) > maxDataSize { + copy(data[start:maxDataSize], v[:]) + break + } else { + copy(data[start:end], v[:]) + } + } + + return data +} + +func GetNumElement(dataLen uint64, CS int) uint64 { + numEle := int(math.Ceil(float64(dataLen) / float64(CS))) + return uint64(numEle) +} + +// helper function +func RoundUpDivision(a, b uint64) uint64 { + return uint64(math.Ceil(float64(a) / float64(b))) +} + +func NextPowerOf2(d uint64) uint64 { + nextPower := math.Ceil(math.Log2(float64(d))) + return uint64(math.Pow(2.0, nextPower)) +} + +// This function is used by user to get the leading coset for a frame, where i is frame index +func GetLeadingCosetIndex(i uint64, numChunks uint64) (uint32, error) { + + if i < numChunks { + j := rb.ReverseBitsLimited(uint32(numChunks), uint32(i)) + return j, nil + } else { + return 0, errors.New("cannot create number of frame higher than possible") + } +} diff --git a/op-service/eigenda/encoding/rs/utils_test.go b/op-service/eigenda/encoding/rs/utils_test.go new file mode 100644 index 000000000..4496161ef --- /dev/null +++ b/op-service/eigenda/encoding/rs/utils_test.go @@ -0,0 +1,58 @@ +package rs_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" +) + +func TestGetEncodingParams(t *testing.T) { + params := encoding.ParamsFromSysPar(1, 4, 1000) + + require.NotNil(t, params) + assert.Equal(t, params.ChunkLength, uint64(32)) // 1000/32/1 => 32 + // assert.Equal(t, params.DataLen, uint64(1000)) + assert.Equal(t, params.NumChunks, uint64(8)) + assert.Equal(t, params.NumEvaluations(), uint64(256)) +} + +func TestGetLeadingCoset(t *testing.T) { + a, err := rs.GetLeadingCosetIndex(0, 10) + require.Nil(t, err, "err not nil") + assert.Equal(t, a, uint32(0)) +} + +func TestGetNumElement(t *testing.T) { + numEle := rs.GetNumElement(1000, encoding.BYTES_PER_SYMBOL) + assert.Equal(t, numEle, uint64(32)) +} + +func TestToFrArrayAndToByteArray_AreInverses(t *testing.T) { + teardownSuite := setupSuite(t) + defer teardownSuite(t) + + numEle := rs.GetNumElement(1000, encoding.BYTES_PER_SYMBOL) + assert.Equal(t, numEle, uint64(32)) + + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(GETTYSBURG_ADDRESS_BYTES))) + enc, _ := rs.NewEncoder(params, true) + require.NotNil(t, enc) + + dataFr, err := rs.ToFrArray(GETTYSBURG_ADDRESS_BYTES) + require.Nil(t, err) + require.NotNil(t, dataFr) + + assert.Equal(t, rs.ToByteArray(dataFr, uint64(len(GETTYSBURG_ADDRESS_BYTES))), GETTYSBURG_ADDRESS_BYTES) +} + +func TestRoundUpDivision(t *testing.T) { + a := rs.RoundUpDivision(1, 5) + b := rs.RoundUpDivision(5, 1) + + assert.Equal(t, a, uint64(1)) + assert.Equal(t, b, uint64(5)) +} diff --git a/op-service/eigenda/encoding/serialization.go b/op-service/eigenda/encoding/serialization.go new file mode 100644 index 000000000..830d29a77 --- /dev/null +++ b/op-service/eigenda/encoding/serialization.go @@ -0,0 +1,122 @@ +package encoding + +import ( + "bytes" + "encoding/gob" + "encoding/json" + "fmt" + + "github.com/consensys/gnark-crypto/ecc/bn254" +) + +func (c *Frame) Serialize() ([]byte, error) { + return encode(c) +} + +func (c *Frame) Deserialize(data []byte) (*Frame, error) { + err := decode(data, c) + if !c.Proof.IsInSubGroup() { + return nil, fmt.Errorf("proof is in not the subgroup") + } + + return c, err +} + +func (f *Frame) Encode() ([]byte, error) { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + err := enc.Encode(f) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func Decode(b []byte) (Frame, error) { + var f Frame + buf := bytes.NewBuffer(b) + dec := gob.NewDecoder(buf) + err := dec.Decode(&f) + if err != nil { + return Frame{}, err + } + return f, nil +} + +func (c *G1Commitment) Serialize() ([]byte, error) { + res := (*bn254.G1Affine)(c).Bytes() + return res[:], nil +} + +func (c *G1Commitment) Deserialize(data []byte) (*G1Commitment, error) { + _, err := (*bn254.G1Affine)(c).SetBytes(data) + if err != nil { + return nil, err + } + return c, err +} + +func (c *G1Commitment) UnmarshalJSON(data []byte) error { + var g1Point bn254.G1Affine + err := json.Unmarshal(data, &g1Point) + if err != nil { + return err + } + c.X = g1Point.X + c.Y = g1Point.Y + + if !(*bn254.G1Affine)(c).IsInSubGroup() { + return fmt.Errorf("G1Commitment not in the subgroup") + } + + return nil +} + +func (c *G2Commitment) Serialize() ([]byte, error) { + res := (*bn254.G2Affine)(c).Bytes() + return res[:], nil +} + +func (c *G2Commitment) Deserialize(data []byte) (*G2Commitment, error) { + _, err := (*bn254.G2Affine)(c).SetBytes(data) + if err != nil { + return nil, err + } + + return c, err +} + +func (c *G2Commitment) UnmarshalJSON(data []byte) error { + var g2Point bn254.G2Affine + err := json.Unmarshal(data, &g2Point) + if err != nil { + return err + } + c.X = g2Point.X + c.Y = g2Point.Y + + if !(*bn254.G2Affine)(c).IsInSubGroup() { + return fmt.Errorf("G2Commitment not in the subgroup") + } + return nil +} + +func encode(obj any) ([]byte, error) { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + err := enc.Encode(obj) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func decode(data []byte, obj any) error { + buf := bytes.NewBuffer(data) + dec := gob.NewDecoder(buf) + err := dec.Decode(obj) + if err != nil { + return err + } + return nil +} diff --git a/op-service/eigenda/encoding/test/README.md b/op-service/eigenda/encoding/test/README.md new file mode 100644 index 000000000..aef811389 --- /dev/null +++ b/op-service/eigenda/encoding/test/README.md @@ -0,0 +1,8 @@ +# encoding + + +- performs Reed Solomon Encoding using elliptic curve points. The library enables KZG multi-proof and reveal in O(n log n) time using FFT, based on FK20 algorithm. + +- is built upon crypto primitive from https://pkg.go.dev/github.com/protolambda/go-kzg + +- accepts arbitrary number of systematic nodes, parity nodes and data size, free of restriction on power of 2 diff --git a/op-service/eigenda/encoding/test/main.go b/op-service/eigenda/encoding/test/main.go new file mode 100644 index 000000000..7db109e9d --- /dev/null +++ b/op-service/eigenda/encoding/test/main.go @@ -0,0 +1,250 @@ +package main + +import ( + "fmt" + "log" + "math/rand" + + "runtime" + "time" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/prover" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/verifier" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +func main() { + // TestKzgRs() + //err := kzg.WriteGeneratorPoints(30000) + //if err != nil { + // log.Println("WriteGeneratorPoints failed:", err) + //} + readpoints() +} + +func readpoints() { + kzgConfig := &kzg.KzgConfig{ + G1Path: "../../inabox/resources/kzg/g1.point", + G2Path: "../../inabox/resources/kzg/g2.point", + CacheDir: "SRSTables", + SRSOrder: 3000, + SRSNumberToLoad: 3000, + NumWorker: uint64(runtime.GOMAXPROCS(0)), + } + + // create encoding object + kzgGroup, _ := prover.NewProver(kzgConfig, true) + fmt.Println("there are ", len(kzgGroup.Srs.G1), "points") + for i := 0; i < len(kzgGroup.Srs.G1); i++ { + + fmt.Printf("%v %v\n", i, string(kzgGroup.Srs.G1[i].String())) + } + if kzgGroup.Srs.G1[0].X == kzg.GenG1.X && kzgGroup.Srs.G1[0].Y == kzg.GenG1.Y { + fmt.Println("start with gen") + } +} + +func TestKzgRs() { + numSymbols := 3 + // encode parameters + numNode := uint64(4) // 200 + numSys := uint64(2) // 180 + numPar := numNode - numSys + // Prepare data + fmt.Printf("* Task Starts\n") + fmt.Printf(" Num Sys: %v\n", numSys) + fmt.Printf(" Num Par: %v\n", numPar) + //fmt.Printf(" Data size(byte): %v\n", len(inputBytes)) + + kzgConfig := &kzg.KzgConfig{ + G1Path: "g1.point", + G2Path: "g2.point", + CacheDir: "SRSTables", + SRSOrder: 3000, + SRSNumberToLoad: 3000, + NumWorker: uint64(runtime.GOMAXPROCS(0)), + } + + // create encoding object + p, _ := prover.NewProver(kzgConfig, true) + + params := encoding.EncodingParams{NumChunks: 200, ChunkLength: 180} + enc, _ := p.GetKzgEncoder(params) + + //inputFr := kzg.ToFrArray(inputBytes) + inputSize := uint64(numSymbols) + inputFr := make([]fr.Element, inputSize) + for i := uint64(0); i < inputSize; i++ { + inputFr[i].SetInt64(int64(i + 1)) + } + + fmt.Printf("Input \n") + printFr(inputFr) + + //inputSize := uint64(len(inputFr)) + commit, lengthCommit, lengthProof, frames, fIndices, err := enc.Encode(inputFr) + _ = lengthProof + _ = lengthCommit + if err != nil { + log.Fatal(err) + } + // Optionally verify + startVerify := time.Now() + + //os.Exit(0) + for i := 0; i < len(frames); i++ { + //for i, f := range frames { + f := frames[i] + j := fIndices[i] + q, err := rs.GetLeadingCosetIndex(uint64(i), numSys+numPar) + if err != nil { + log.Fatalf("%v", err) + } + + if j != q { + log.Fatal("leading coset inconsistency") + } + + fmt.Printf("frame %v leading coset %v\n", i, j) + lc := enc.Fs.ExpandedRootsOfUnity[uint64(j)] + + g2Atn, err := kzg.ReadG2Point(uint64(len(f.Coeffs)), kzgConfig) + if err != nil { + log.Fatalf("Load g2 %v failed\n", err) + } + err = verifier.VerifyFrame(&f, enc.Ks, commit, &lc, &g2Atn) + if err != nil { + log.Fatalf("Proof %v failed\n", i) + } + } + fmt.Printf("* Verify %v frames -> all correct. together using %v\n", + len(frames), time.Since(startVerify)) + // sample some frames + samples, indices := SampleFrames(frames, uint64(len(frames)-3)) + //samples, indices := SampleFrames(frames, numSys) + //fmt.Printf("* Sampled %v frames\n", numSys) + //// Decode data from samples + + dataFr, err := enc.Decode(samples, indices, inputSize) + if err != nil { + log.Fatal(err) + } + + //printFr(dataFr) + //dataFr, err := kzg.DecodeSys(samples, indices, inputSize) + //if err != nil { + //log.Fatalf("%v", err) + //} + + fmt.Println(dataFr) + // printFr(dataFr) + //deData := kzg.ToByteArray(dataFr, inputByteSize) + //fmt.Println("dataFr") + // printFr(dataFr) + //fmt.Println(deData) + // Verify data is original in Fr + //compareData(inputFr, dataFr) + // Verify data is original in Byte + //compareDataByte(deData, inputBytes) + //fmt.Printf("* Compared original %v bytes with reconstructed -> PASS\n", inputByteSize) + //_ = deData +} + +// func getData(inputSize uint64) []fr.Element { +// inputFr := make([]fr.Element, inputSize) +// for i := uint64(0); i < inputSize; i++ { +// bls.AsFr(&inputFr[i], i+1) +// } +// return inputFr +// } +// +// func compareData(inputFr, dataFr []fr.Element) { +// if len(inputFr) != len(dataFr) { +// log.Fatalf("Error. Diff length. input %v, data %v\n", len(inputFr), len(dataFr)) +// } +// +// for i := 0; i < len(inputFr); i++ { +// if !bls.EqualFr(&inputFr[i], &dataFr[i]) { +// log.Fatalf("Error. Diff value at %v. input %v, data %v\n", +// i, inputFr[i].String(), dataFr[i].String()) +// } +// } +// } +// +// func compareDataByte(inputFr, dataFr []byte) { +// if len(inputFr) != len(dataFr) { +// log.Fatalf("Error. Diff length. input %v, data %v\n", len(inputFr), len(dataFr)) +// } +// +// for i := 0; i < len(inputFr); i++ { +// if inputFr[i] != dataFr[i] { +// log.Fatalf("Error. Diff Data byte value at %v. input %v, data %v\n", +// i, inputFr[i:], dataFr[i:]) +// } +// } +// } +// +// func initPoly(size int) ([]fr.Element, []fr.Element) { +// v := make([]uint64, size) +// for i := 0; i < size; i++ { +// v[i] = uint64(i + 1) +// } +// polyFr := makeFr(v) +// fs := kzg.NewFFTSettings(3) +// dataFr, _ := fs.FFT(polyFr, false) +// return polyFr, dataFr +// } +// +// func initData(size uint64) ([]fr.Element, []fr.Element) { +// v := make([]uint64, size) +// for i := uint64(0); i < size; i++ { +// v[i] = uint64(i + 1) +// } +// dataFr := makeFr(v) +// order := kzg.CeilIntPowerOf2Num(size) +// fs := kzg.NewFFTSettings(uint8(order)) +// polyFr, err := fs.FFT(dataFr, true) +// if err != nil { +// log.Fatal(err) +// } +// return polyFr, dataFr +// } +// +// func makeFr(input []uint64) []fr.Element { +// inputFr := make([]fr.Element, len(input)) +// for i := 0; i < len(input); i++ { +// bls.AsFr(&inputFr[i], input[i]) +// } +// return inputFr +// } + +func printFr(d []fr.Element) { + for _, e := range d { + fmt.Printf("%v ", e.String()) + } + fmt.Printf("\n") +} + +// func printG1(d []bn254.G1Affine) { +// for i, e := range d { +// fmt.Printf("%v: %v \n", i, e.String()) +// } +// fmt.Printf("\n") +// } + +func SampleFrames(frames []encoding.Frame, num uint64) ([]encoding.Frame, []uint64) { + samples := make([]encoding.Frame, num) + indices := rand.Perm(len(frames)) + indices = indices[:num] + + frameIndices := make([]uint64, num) + for i, j := range indices { + samples[i] = frames[j] + frameIndices[i] = uint64(j) + } + return samples, frameIndices +} diff --git a/op-service/eigenda/encoding/test/testKzgBn254.go b/op-service/eigenda/encoding/test/testKzgBn254.go new file mode 100644 index 000000000..8cd715e95 --- /dev/null +++ b/op-service/eigenda/encoding/test/testKzgBn254.go @@ -0,0 +1,139 @@ +package main + +// "fmt" +// "log" +// "math" +// bn "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/bn254" +// bnKzg "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzgFFT/go-kzg-bn254" + +//func testLinComb() { +//polyFr := GenPoly(1,2,3,4,5) +////fmt.Println("polyFr") +////fmt.Println(polyFr) +//s1, _ := GenerateTestingSetup("1927409816240961209460912649125", 16+1) + +//var dr bn.G1Point +//for i := 0 ; i < 5 ; i++ { +//var product bn.G1Point +//var b big.Int +//polyFr[i].BigInt(b) +////product.ScalarMultiplication(&s1[i], &b) +//dr.AddG1() +//} + +//} + +//func SingleProof() { +//fftSetting := bnKzg.NewFFTSettings(4) +//s1, s2 := GenerateTestingSetup("1927409816240961209460912649125", 16+1) + +//fmt.Println("srs single proof") +////for i, v := range s1 { +////fmt.Printf("%v: %v \n", i, v.String()) +////} +////input := []uint64{1,2,3,4,5} +//polyFr := GenPoly(1,2,3,4,5) +////fmt.Println("polyFr") +////fmt.Println(polyFr) + +//kzgSetting := bnKzg.NewKZGSettings(fftSetting, s1, s2) +////commit := kzgSetting.CommitToPolyUnoptimized(polyFr) +//commit := kzgSetting.CommitToPoly(polyFr) + +//fmt.Println("bn254 commit") +//fmt.Printf("%v\n", commit) + +////text := []byte("hello hello ") +////polyFr := GenTextPoly(text) + +//proof := kzgSetting.ComputeProofSingle(polyFr, 3) + +//fmt.Printf("proof\n") +//fmt.Printf("%v\n", proof.String()) + +//var x bn.Fr +//bn.AsFr(&x, 3) +//var value bn.Fr +//bn.EvalPolyAt(&value, polyFr, &x) +//fmt.Println("value") +//fmt.Println(value.String()) + +//y := value + +//var xG2 bn.G2Point +//bn.MulG2(&xG2, &bn.GenG2, &x) +//var sMinuxX bn.G2Point +//bn.SubG2(&sMinuxX, &s2[1], &xG2) +//var yG1 bn.G1Point +//bn.MulG1(&yG1, &bn.GenG1, &y) +//var commitmentMinusY bn.G1Point +//bn.SubG1(&commitmentMinusY, commit, &yG1) + +//// at verifier, it does not need the orig polyFr +//payProof, _ := proof.MarshalText() +//payCommit, _ := commit.MarshalText() +//payValue := value.String() + +//payload := len(payProof) +len(payCommit) + len(payValue) +//if !kzgSetting.CheckProofSingle(commit, proof, &x, &value) { +//fmt.Println("not eval to result") +//} else { +//fmt.Println("eval to result", payload) +//fmt.Println("PayValye", payValue) +//} +//} + +//func testKzgBnFr() { +//a := [4]uint64{0, 1, 2, 3} +//aFr := makeBnFr(a[:]) +//fmt.Println("BnFr") +//printBnFr(aFr) + +//diff := make([]bn.Fr, 4) + +//for i := 0 ; i < 4 ; i++ { +//a := aFr[i] +//var b bn.Fr +//bn.CopyFr(&a, &b) + +//bn.SubModFr(&diff[i], &a, &b) +//} +//fmt.Println("diff") +//printBnFr(diff) +//} + +// func makeBn254Fr(input []uint64) []bn.Fr { +// inputFr := make([]bn.Fr, len(input)) +// for i := 0; i < len(input); i++ { +// bn.AsFr(&inputFr[i], input[i]) +// } +// return inputFr +// } + +//func compareDataBn254(inputFr, dataFr []bn.Fr) { +//if len(inputFr) != len(dataFr) { +//log.Fatalf("Error. Diff length. input %v, data %v\n", len(inputFr), len(dataFr)) +//} + +//for i := 0 ; i < len(inputFr) ; i++ { +//if !bn.EqualFr(&inputFr[i], &dataFr[i]) { +//log.Fatalf("Error. Diff value at %v. input %v, data %v\n", +//i, inputFr[i].String(), dataFr[i].String()) +//} +//} +//} + +////func initBnData(size int) ([]bn.Fr, []bn.Fr) { +////v := make([]uint64, size) +////for i := 0 ; i < size ; i++ { +////v[i] = uint64(i + 1) +////} +////dataFr := makeBnFr(v) +////order := multiprover.CeilIntPowerOf2Num(size) +////fs := bnKzg.NewFFTSettings(uint8(order)) +////polyFr, err := fs.FFT(dataFr, true) +////if err != nil { +////log.Fatal(err) +////} +////return polyFr, dataFr +////} diff --git a/op-service/eigenda/encoding/utils.go b/op-service/eigenda/encoding/utils.go new file mode 100644 index 000000000..8af967648 --- /dev/null +++ b/op-service/eigenda/encoding/utils.go @@ -0,0 +1,32 @@ +package encoding + +import ( + "golang.org/x/exp/constraints" + + "math" +) + +// GetBlobLength converts from blob size in bytes to blob size in symbols +func GetBlobLength(blobSize uint) uint { + symSize := uint(BYTES_PER_SYMBOL) + return (blobSize + symSize - 1) / symSize +} + +// GetBlobSize converts from blob length in symbols to blob size in bytes. This is not an exact conversion. +func GetBlobSize(blobLength uint) uint { + return blobLength * BYTES_PER_SYMBOL +} + +// GetBlobLength converts from blob size in bytes to blob size in symbols +func GetEncodedBlobLength(blobLength uint, quorumThreshold, advThreshold uint8) uint { + return roundUpDivide(blobLength*100, uint(quorumThreshold-advThreshold)) +} + +func NextPowerOf2(d uint64) uint64 { + nextPower := math.Ceil(math.Log2(float64(d))) + return uint64(math.Pow(2.0, nextPower)) +} + +func roundUpDivide[T constraints.Integer](a, b T) T { + return (a + b - 1) / b +} diff --git a/op-service/eigenda/encoding/utils/codec/codec.go b/op-service/eigenda/encoding/utils/codec/codec.go new file mode 100644 index 000000000..b6032b2d7 --- /dev/null +++ b/op-service/eigenda/encoding/utils/codec/codec.go @@ -0,0 +1,67 @@ +package codec + +import ( + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" +) + +// ConvertByPaddingEmptyByte takes bytes and insert an empty byte at the front of every 31 byte. +// The empty byte is padded at the low address, because we use big endian to interpret a fiedl element. +// This ensure every 32 bytes are within the valid range of a field element for bn254 curve. +// If the input data is not a multiple of 31, the reminder is added to the output by +// inserting a 0 and the reminder. The output does not necessarily be a multipler of 32 +func ConvertByPaddingEmptyByte(data []byte) []byte { + dataSize := len(data) + parseSize := encoding.BYTES_PER_SYMBOL - 1 + putSize := encoding.BYTES_PER_SYMBOL + + dataLen := (dataSize + parseSize - 1) / parseSize + + validData := make([]byte, dataLen*putSize) + validEnd := len(validData) + + for i := 0; i < dataLen; i++ { + start := i * parseSize + end := (i + 1) * parseSize + if end > len(data) { + end = len(data) + // 1 is the empty byte + validEnd = end - start + 1 + i*putSize + } + + // with big endian, set first byte is always 0 to ensure data is within valid range of + validData[i*encoding.BYTES_PER_SYMBOL] = 0x00 + copy(validData[i*encoding.BYTES_PER_SYMBOL+1:(i+1)*encoding.BYTES_PER_SYMBOL], data[start:end]) + + } + return validData[:validEnd] +} + +// RemoveEmptyByteFromPaddedBytes takes bytes and remove the first byte from every 32 bytes. +// This reverses the change made by the function ConvertByPaddingEmptyByte. +// The function does not assume the input is a multiple of BYTES_PER_SYMBOL(32 bytes). +// For the reminder of the input, the first byte is taken out, and the rest is appended to +// the output. +func RemoveEmptyByteFromPaddedBytes(data []byte) []byte { + dataSize := len(data) + parseSize := encoding.BYTES_PER_SYMBOL + dataLen := (dataSize + parseSize - 1) / parseSize + + putSize := encoding.BYTES_PER_SYMBOL - 1 + + validData := make([]byte, dataLen*putSize) + validLen := len(validData) + + for i := 0; i < dataLen; i++ { + // add 1 to leave the first empty byte untouched + start := i*parseSize + 1 + end := (i + 1) * parseSize + + if end > len(data) { + end = len(data) + validLen = end - start + i*putSize + } + + copy(validData[i*putSize:(i+1)*putSize], data[start:end]) + } + return validData[:validLen] +} diff --git a/op-service/eigenda/encoding/utils/codec/codec_test.go b/op-service/eigenda/encoding/utils/codec/codec_test.go new file mode 100644 index 000000000..5bfda0c89 --- /dev/null +++ b/op-service/eigenda/encoding/utils/codec/codec_test.go @@ -0,0 +1,51 @@ +package codec_test + +import ( + "crypto/rand" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/utils/codec" + "github.com/stretchr/testify/require" +) + +func TestSimplePaddingCodec(t *testing.T) { + gettysburgAddressBytes := []byte("Fourscore and seven years ago our fathers brought forth, on this continent, a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived, and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting-place for those who here gave their lives, that that nation might live. It is altogether fitting and proper that we should do this. But, in a larger sense, we cannot dedicate, we cannot consecrate—we cannot hallow—this ground. The brave men, living and dead, who struggled here, have consecrated it far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they here gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom, and that government of the people, by the people, for the people, shall not perish from the earth.") + + paddedData := codec.ConvertByPaddingEmptyByte(gettysburgAddressBytes) + + restored := codec.RemoveEmptyByteFromPaddedBytes(paddedData) + + require.Equal(t, gettysburgAddressBytes, restored[:len(gettysburgAddressBytes)]) +} + +func TestSimplePadding_IsValid(t *testing.T) { + gettysburgAddressBytes := []byte("Fourscore and seven years ago our fathers brought forth, on this continent, a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived, and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting-place for those who here gave their lives, that that nation might live. It is altogether fitting and proper that we should do this. But, in a larger sense, we cannot dedicate, we cannot consecrate—we cannot hallow—this ground. The brave men, living and dead, who struggled here, have consecrated it far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they here gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom, and that government of the people, by the people, for the people, shall not perish from the earth.") + + paddedData := codec.ConvertByPaddingEmptyByte(gettysburgAddressBytes) + + _, err := rs.ToFrArray(paddedData) + require.Nil(t, err) +} + +func TestSimplePaddingCodec_Fuzz(t *testing.T) { + numFuzz := 100 + + dataSizeList := make([]int, 0) + for i := 32; i < 3000; i = i + 10 { + dataSizeList = append(dataSizeList, i) + } + + for i := 0; i < numFuzz; i++ { + for j := 0; j < len(dataSizeList); j++ { + data := make([]byte, dataSizeList[j]) + _, err := rand.Read(data) + require.Nil(t, err) + paddedData := codec.ConvertByPaddingEmptyByte(data) + _, err = rs.ToFrArray(paddedData) + require.Nil(t, err) + restored := codec.RemoveEmptyByteFromPaddedBytes(paddedData) + require.Equal(t, data, restored) + } + } +} diff --git a/op-service/eigenda/encoding/utils/openCommitment/open_commitment.go b/op-service/eigenda/encoding/utils/openCommitment/open_commitment.go new file mode 100644 index 000000000..2b4564084 --- /dev/null +++ b/op-service/eigenda/encoding/utils/openCommitment/open_commitment.go @@ -0,0 +1,134 @@ +package openCommitment + +import ( + "errors" + "fmt" + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +// Implement https://github.com/ethereum/consensus-specs/blob/017a8495f7671f5fff2075a9bfc9238c1a0982f8/specs/deneb/polynomial-commitments.md#compute_kzg_proof_impl +func ComputeKzgProof( + evalFr []fr.Element, + index int, + G1srsLagrange []bn254.G1Affine, + rootOfUnities []fr.Element, +) (*bn254.G1Affine, *fr.Element, error) { + if len(evalFr) != len(rootOfUnities) { + return nil, nil, fmt.Errorf("inconsistent length between blob and root of unities") + } + if index < 0 || index >= len(evalFr) { + return nil, nil, fmt.Errorf("the function only opens points within a blob") + } + + polyShift := make([]fr.Element, len(evalFr)) + + valueFr := evalFr[index] + + zFr := rootOfUnities[index] + + for i := 0; i < len(polyShift); i++ { + polyShift[i].Sub(&evalFr[i], &valueFr) + } + + denomPoly := make([]fr.Element, len(rootOfUnities)) + + for i := 0; i < len(evalFr); i++ { + denomPoly[i].Sub(&rootOfUnities[i], &zFr) + } + + quotientPoly := make([]fr.Element, len(rootOfUnities)) + for i := 0; i < len(quotientPoly); i++ { + if denomPoly[i].IsZero() { + quotientPoly[i] = computeQuotientEvalOnDomain(zFr, evalFr, valueFr, rootOfUnities) + } else { + quotientPoly[i].Div(&polyShift[i], &denomPoly[i]) + } + } + + config := ecc.MultiExpConfig{} + + var proof bn254.G1Affine + _, err := proof.MultiExp(G1srsLagrange, quotientPoly, config) + if err != nil { + return nil, nil, err + } + + return &proof, &valueFr, nil +} + +func VerifyKzgProof(G1Gen, commitment, proof bn254.G1Affine, G2Gen, G2tau bn254.G2Affine, valueFr, zFr fr.Element) error { + + var valueG1 bn254.G1Affine + var valueBig big.Int + valueG1.ScalarMultiplication(&G1Gen, valueFr.BigInt(&valueBig)) + + var commitMinusValue bn254.G1Affine + commitMinusValue.Sub(&commitment, &valueG1) + + var zG2 bn254.G2Affine + zG2.ScalarMultiplication(&G2Gen, zFr.BigInt(&valueBig)) + + var xMinusZ bn254.G2Affine + xMinusZ.Sub(&G2tau, &zG2) + + return PairingsVerify(&commitMinusValue, &G2Gen, &proof, &xMinusZ) +} + +func PairingsVerify(a1 *bn254.G1Affine, a2 *bn254.G2Affine, b1 *bn254.G1Affine, b2 *bn254.G2Affine) error { + var negB1 bn254.G1Affine + negB1.Neg(b1) + + P := [2]bn254.G1Affine{*a1, negB1} + Q := [2]bn254.G2Affine{*a2, *b2} + + ok, err := bn254.PairingCheck(P[:], Q[:]) + if err != nil { + return err + } + if !ok { + return errors.New("pairingCheck pairing not ok") + } + + return nil +} + +func CommitInLagrange(evalFr []fr.Element, G1srsLagrange []bn254.G1Affine) (*bn254.G1Affine, error) { + config := ecc.MultiExpConfig{} + + var proof bn254.G1Affine + _, err := proof.MultiExp(G1srsLagrange, evalFr, config) + if err != nil { + return nil, err + } + return &proof, nil +} + +// Implement https://github.com/ethereum/consensus-specs/blob/017a8495f7671f5fff2075a9bfc9238c1a0982f8/specs/deneb/polynomial-commitments.md#compute_quotient_eval_within_domain +func computeQuotientEvalOnDomain(zFr fr.Element, evalFr []fr.Element, valueFr fr.Element, rootOfunities []fr.Element) fr.Element { + var quotient fr.Element + var f_i, numerator, denominator, temp fr.Element + + for i := 0; i < len(rootOfunities); i++ { + omega_i := rootOfunities[i] + if omega_i.Equal(&zFr) { + continue + } + + f_i.Sub(&evalFr[i], &valueFr) + numerator.Mul(&f_i, &omega_i) + + denominator.Sub(&zFr, &omega_i) + denominator.Mul(&denominator, &zFr) + + numerator.Mul(&f_i, &omega_i) + temp.Div(&numerator, &denominator) + + quotient.Add("ient, &temp) + + } + return quotient +} diff --git a/op-service/eigenda/encoding/utils/openCommitment/open_commitment_test.go b/op-service/eigenda/encoding/utils/openCommitment/open_commitment_test.go new file mode 100644 index 000000000..f538740a4 --- /dev/null +++ b/op-service/eigenda/encoding/utils/openCommitment/open_commitment_test.go @@ -0,0 +1,105 @@ +package openCommitment_test + +import ( + "crypto/rand" + "log" + "math/big" + "runtime" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + kzgProver "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/prover" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/utils/codec" + oc "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/utils/openCommitment" + + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/stretchr/testify/require" +) + +var ( + gettysburgAddressBytes = []byte("Fourscore and seven years ago our fathers brought forth, on this continent, a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived, and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting-place for those who here gave their lives, that that nation might live. It is altogether fitting and proper that we should do this. But, in a larger sense, we cannot dedicate, we cannot consecrate—we cannot hallow—this ground. The brave men, living and dead, who struggled here, have consecrated it far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they here gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom, and that government of the people, by the people, for the people, shall not perish from the earth.") + kzgConfig *kzg.KzgConfig + numNode uint64 + numSys uint64 + numPar uint64 +) + +func TestOpenCommitment(t *testing.T) { + log.Println("Setting up suite") + + kzgConfig = &kzg.KzgConfig{ + G1Path: "../../../inabox/resources/kzg/g1.point", + G2Path: "../../../inabox/resources/kzg/g2.point", + G2PowerOf2Path: "../../../inabox/resources/kzg/g2.point.powerOf2", + CacheDir: "../../../inabox/resources/kzg/SRSTables", + SRSOrder: 3000, + SRSNumberToLoad: 3000, + NumWorker: uint64(runtime.GOMAXPROCS(0)), + } + + // input evaluation + validInput := codec.ConvertByPaddingEmptyByte(gettysburgAddressBytes) + inputFr, err := rs.ToFrArray(validInput) + require.Nil(t, err) + + frLen := uint64(len(inputFr)) + paddedInputFr := make([]fr.Element, encoding.NextPowerOf2(frLen)) + // pad input Fr to power of 2 for computing FFT + for i := 0; i < len(paddedInputFr); i++ { + if i < len(inputFr) { + paddedInputFr[i].Set(&inputFr[i]) + } else { + paddedInputFr[i].SetZero() + } + } + + // we need prover only to access kzg SRS, and get kzg commitment of encoding + group, err := kzgProver.NewProver(kzgConfig, true) + require.Nil(t, err) + + // get root of unit for blob + numNode = 4 + numSys = 4 + numPar = 0 + numOpenChallenge := 10 + + params := encoding.ParamsFromSysPar(numSys, numPar, uint64(len(validInput))) + enc, err := group.GetKzgEncoder(params) + require.Nil(t, err) + rootOfUnities := enc.Fs.ExpandedRootsOfUnity[:len(enc.Fs.ExpandedRootsOfUnity)-1] + + // Lagrange basis SRS in normal order, not butterfly + lagrangeG1SRS, err := enc.Fs.FFTG1(group.Srs.G1[:len(paddedInputFr)], true) + require.Nil(t, err) + + // commit in lagrange form + commitLagrange, err := oc.CommitInLagrange(paddedInputFr, lagrangeG1SRS) + require.Nil(t, err) + + modulo := big.NewInt(int64(len(inputFr))) + // pick a random place in the blob to open + for k := 0; k < numOpenChallenge; k++ { + + indexBig, err := rand.Int(rand.Reader, modulo) + require.Nil(t, err) + + index := int(indexBig.Int64()) + + // open at index on the kzg + proof, valueFr, err := oc.ComputeKzgProof(paddedInputFr, index, lagrangeG1SRS, rootOfUnities) + require.Nil(t, err) + + _, _, g1Gen, g2Gen := bn254.Generators() + + err = oc.VerifyKzgProof(g1Gen, *commitLagrange, *proof, g2Gen, group.Srs.G2[1], *valueFr, rootOfUnities[index]) + require.Nil(t, err) + + require.Equal(t, *valueFr, inputFr[index]) + + //valueBytse := valueFr.Bytes() + //fmt.Println("value Byte", string(valueBytse[1:])) + } +} diff --git a/op-service/eigenda/encoding/utils/reverseBits/reverseBits.go b/op-service/eigenda/encoding/utils/reverseBits/reverseBits.go new file mode 100644 index 000000000..d08d1bf1f --- /dev/null +++ b/op-service/eigenda/encoding/utils/reverseBits/reverseBits.go @@ -0,0 +1,159 @@ +package reverseBits + +// Copy from github.com/protolambda/go-kzg. with some modification + +import ( + "errors" + + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +const ( + mask0 = ^uint32((1 << (1 << iota)) - 1) + mask1 + mask2 + mask3 + mask4 + //mask5 +) + +const ( + bit0 = uint8(1 << iota) + bit1 + bit2 + bit3 + bit4 + //bit5 +) + +var ErrRBOInvalidLength = errors.New("length must be power of 2 for RBO") +var ErrFrRBOListTooLarge = errors.New("Fr RBO list length too large") //lint:ignore ST1005 ignore noun +var ErrG1RBOListTooLarge = errors.New("G1 RBO list length too large") + +// bitmagic: binary search through a uint32 to find the index (least bit being 0) of the first set bit. +// Zero is a special case, it has a 0 bit index. +// Example: +// +// (in out): (0 0), (1 0), (2 1), (3 1), (4 2), (5 2), (6 2), (7 2), (8 3), (9 3) +func bitIndex(v uint32) (out uint8) { + if v == 0 { + return 0 + } + //if v&mask5 != 0 { + // v >>= bit5 + // out |= bit5 + //} + if v&mask4 != 0 { + v >>= bit4 + out |= bit4 + } + if v&mask3 != 0 { + v >>= bit3 + out |= bit3 + } + if v&mask2 != 0 { + v >>= bit2 + out |= bit2 + } + if v&mask1 != 0 { + v >>= bit1 + out |= bit1 + } + if v&mask0 != 0 { + out |= bit0 + } + return +} + +var revByte = [256]byte{ + 0b00000000, 0b10000000, 0b01000000, 0b11000000, 0b00100000, 0b10100000, 0b01100000, 0b11100000, 0b00010000, 0b10010000, 0b01010000, 0b11010000, 0b00110000, 0b10110000, 0b01110000, 0b11110000, + 0b00001000, 0b10001000, 0b01001000, 0b11001000, 0b00101000, 0b10101000, 0b01101000, 0b11101000, 0b00011000, 0b10011000, 0b01011000, 0b11011000, 0b00111000, 0b10111000, 0b01111000, 0b11111000, + 0b00000100, 0b10000100, 0b01000100, 0b11000100, 0b00100100, 0b10100100, 0b01100100, 0b11100100, 0b00010100, 0b10010100, 0b01010100, 0b11010100, 0b00110100, 0b10110100, 0b01110100, 0b11110100, + 0b00001100, 0b10001100, 0b01001100, 0b11001100, 0b00101100, 0b10101100, 0b01101100, 0b11101100, 0b00011100, 0b10011100, 0b01011100, 0b11011100, 0b00111100, 0b10111100, 0b01111100, 0b11111100, + 0b00000010, 0b10000010, 0b01000010, 0b11000010, 0b00100010, 0b10100010, 0b01100010, 0b11100010, 0b00010010, 0b10010010, 0b01010010, 0b11010010, 0b00110010, 0b10110010, 0b01110010, 0b11110010, + 0b00001010, 0b10001010, 0b01001010, 0b11001010, 0b00101010, 0b10101010, 0b01101010, 0b11101010, 0b00011010, 0b10011010, 0b01011010, 0b11011010, 0b00111010, 0b10111010, 0b01111010, 0b11111010, + 0b00000110, 0b10000110, 0b01000110, 0b11000110, 0b00100110, 0b10100110, 0b01100110, 0b11100110, 0b00010110, 0b10010110, 0b01010110, 0b11010110, 0b00110110, 0b10110110, 0b01110110, 0b11110110, + 0b00001110, 0b10001110, 0b01001110, 0b11001110, 0b00101110, 0b10101110, 0b01101110, 0b11101110, 0b00011110, 0b10011110, 0b01011110, 0b11011110, 0b00111110, 0b10111110, 0b01111110, 0b11111110, + 0b00000001, 0b10000001, 0b01000001, 0b11000001, 0b00100001, 0b10100001, 0b01100001, 0b11100001, 0b00010001, 0b10010001, 0b01010001, 0b11010001, 0b00110001, 0b10110001, 0b01110001, 0b11110001, + 0b00001001, 0b10001001, 0b01001001, 0b11001001, 0b00101001, 0b10101001, 0b01101001, 0b11101001, 0b00011001, 0b10011001, 0b01011001, 0b11011001, 0b00111001, 0b10111001, 0b01111001, 0b11111001, + 0b00000101, 0b10000101, 0b01000101, 0b11000101, 0b00100101, 0b10100101, 0b01100101, 0b11100101, 0b00010101, 0b10010101, 0b01010101, 0b11010101, 0b00110101, 0b10110101, 0b01110101, 0b11110101, + 0b00001101, 0b10001101, 0b01001101, 0b11001101, 0b00101101, 0b10101101, 0b01101101, 0b11101101, 0b00011101, 0b10011101, 0b01011101, 0b11011101, 0b00111101, 0b10111101, 0b01111101, 0b11111101, + 0b00000011, 0b10000011, 0b01000011, 0b11000011, 0b00100011, 0b10100011, 0b01100011, 0b11100011, 0b00010011, 0b10010011, 0b01010011, 0b11010011, 0b00110011, 0b10110011, 0b01110011, 0b11110011, + 0b00001011, 0b10001011, 0b01001011, 0b11001011, 0b00101011, 0b10101011, 0b01101011, 0b11101011, 0b00011011, 0b10011011, 0b01011011, 0b11011011, 0b00111011, 0b10111011, 0b01111011, 0b11111011, + 0b00000111, 0b10000111, 0b01000111, 0b11000111, 0b00100111, 0b10100111, 0b01100111, 0b11100111, 0b00010111, 0b10010111, 0b01010111, 0b11010111, 0b00110111, 0b10110111, 0b01110111, 0b11110111, + 0b00001111, 0b10001111, 0b01001111, 0b11001111, 0b00101111, 0b10101111, 0b01101111, 0b11101111, 0b00011111, 0b10011111, 0b01011111, 0b11011111, 0b00111111, 0b10111111, 0b01111111, 0b11111111, +} + +func reverseBits(b uint32) uint32 { + return (uint32(revByte[uint8(b)]) << 24) | + (uint32(revByte[uint8(b>>8)]) << 16) | + (uint32(revByte[uint8(b>>16)]) << 8) | + uint32(revByte[uint8(b>>24)]) +} + +func ReverseBitsLimited(length uint32, value uint32) uint32 { + unusedBitLen := 32 - bitIndex(length) + return reverseBits(value) >> unusedBitLen +} + +func ReverseBitOrder(length uint32, swap func(i, j uint32)) error { + if !(length > 0 || (length&(length-1) == 0)) { + // panic("length is not a power of 2") + return ErrRBOInvalidLength + } + // swap bits: + // 00000000000000000000000000000001 -> 10000000000000000000000000000000 + // then adjust, e.g. we may only want to swap the first 4 bits: + // 10000000000000000000000000000000 >> (32 - 4) = 1000 + unusedBitLen := 32 - bitIndex(length) + for i := uint32(0); i < length; i++ { + // only swap every pair once. If pair items are equal, nothing to do, skip work. + if r := reverseBits(i) >> unusedBitLen; r > i { + swap(r, i) + } + } + return nil +} + +// rearrange Fr elements in reverse bit order. Supports 2**31 max element count. +func ReverseBitOrderFr(values []fr.Element) error { + if len(values) > (1 << 31) { + return ErrFrRBOListTooLarge + } + var tmp fr.Element + err := ReverseBitOrder(uint32(len(values)), func(i, j uint32) { + tmp.Set(&values[i]) + + values[i].Set(&values[j]) + + values[j].Set(&tmp) + + }) + return err +} + +// rearrange Fr ptr elements in reverse bit order. Supports 2**31 max element count. +func ReverseBitOrderFrPtr(values []*fr.Element) error { + if len(values) > (1 << 31) { + return ErrFrRBOListTooLarge + } + err := ReverseBitOrder(uint32(len(values)), func(i, j uint32) { + values[i], values[j] = values[j], values[i] + }) + return err +} + +func ReverseBitOrderG1Point(values []bn254.G1Affine) error { + if len(values) > (1 << 31) { + return ErrG1RBOListTooLarge + } + var tmp bn254.G1Affine + err := ReverseBitOrder(uint32(len(values)), func(i, j uint32) { + tmp.Set(&values[i]) + values[i].Set(&values[j]) + values[j].Set(&tmp) + + }) + return err +} diff --git a/op-service/eigenda/encoding/utils/toeplitz/cir.go b/op-service/eigenda/encoding/utils/toeplitz/cir.go new file mode 100644 index 000000000..fe05ce3f1 --- /dev/null +++ b/op-service/eigenda/encoding/utils/toeplitz/cir.go @@ -0,0 +1,92 @@ +package toeplitz + +import ( + "errors" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/fft" +) + +type Circular struct { + V []fr.Element + Fs *fft.FFTSettings +} + +func NewCircular(v []fr.Element, fs *fft.FFTSettings) *Circular { + return &Circular{ + V: v, + Fs: fs, + } +} + +// Matrix multiplication between a circular matrix and a vector using FFT +func (c *Circular) Multiply(x []fr.Element) ([]fr.Element, error) { + if len(x) != len(c.V) { + return nil, errors.New("dimension inconsistent") + } + n := len(x) + + colV := make([]fr.Element, n) + for i := 0; i < n; i++ { + colV[i] = c.V[(n-i)%n] + } + + y, err := c.Fs.FFT(x, false) + if err != nil { + return nil, err + } + v, err := c.Fs.FFT(colV, false) + if err != nil { + return nil, err + } + u := make([]fr.Element, n) + err = Hadamard(y, v, u) + if err != nil { + return nil, err + } + + r, err := c.Fs.FFT(u, true) + if err != nil { + return nil, err + } + return r, nil +} + +// Taking FFT on the circular matrix vector +func (c *Circular) GetFFTCoeff() ([]fr.Element, error) { + n := len(c.V) + + colV := make([]fr.Element, n) + for i := 0; i < n; i++ { + colV[i] = c.V[(n-i)%n] + } + + out, err := c.Fs.FFT(colV, false) + if err != nil { + return nil, err + } + return out, nil +} + +// Taking FFT on the circular matrix vector +func (c *Circular) GetCoeff() ([]fr.Element, error) { + n := len(c.V) + + colV := make([]fr.Element, n) + for i := 0; i < n; i++ { + colV[i] = c.V[(n-i)%n] + } + return colV, nil +} + +// Hadamard product between 2 vectors containing Fr elements +func Hadamard(a, b, u []fr.Element) error { + if len(a) != len(b) { + return errors.New("dimension inconsistent. Cannot do Hadamard Product on Fr") + } + + for i := 0; i < len(a); i++ { + u[i].Mul(&a[i], &b[i]) + } + return nil +} diff --git a/op-service/eigenda/encoding/utils/toeplitz/cir_test.go b/op-service/eigenda/encoding/utils/toeplitz/cir_test.go new file mode 100644 index 000000000..1b879949b --- /dev/null +++ b/op-service/eigenda/encoding/utils/toeplitz/cir_test.go @@ -0,0 +1,67 @@ +package toeplitz_test + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/fft" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/utils/toeplitz" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/stretchr/testify/assert" +) + +func TestNewCircular(t *testing.T) { + v := make([]fr.Element, 4) + v[0].SetInt64(int64(7)) + v[1].SetInt64(int64(6)) + v[2].SetInt64(int64(5)) + v[3].SetInt64(int64(11)) + fs := fft.NewFFTSettings(4) + + c := toeplitz.NewCircular(v, fs) + + assert.Equal(t, v[0], c.V[0]) + assert.Equal(t, v[1], c.V[1]) + assert.Equal(t, v[2], c.V[2]) + assert.Equal(t, v[3], c.V[3]) +} + +func TestMultiplyCircular_InvalidDimensions(t *testing.T) { + v := make([]fr.Element, 2) + v[0].SetInt64(int64(7)) + v[1].SetInt64(int64(11)) + fs := fft.NewFFTSettings(2) + + c := toeplitz.NewCircular(v, fs) + + x := make([]fr.Element, 4) + x[0].SetInt64(int64(1)) + x[1].SetInt64(int64(2)) + x[2].SetInt64(int64(3)) + x[3].SetInt64(int64(4)) + _, err := c.Multiply(x) + assert.EqualError(t, err, "dimension inconsistent") +} + +func TestHadamard_InvalidDimension(t *testing.T) { + a := make([]fr.Element, 2) + a[0].SetInt64(int64(1)) + a[1].SetInt64(int64(2)) + + b := make([]fr.Element, 1) + b[0].SetInt64(int64(3)) + + c := make([]fr.Element, 3) + err := toeplitz.Hadamard(a, b, c) + assert.EqualError(t, err, "dimension inconsistent. Cannot do Hadamard Product on Fr") + + // TODO: This causes a panic because there are no checks on the size of c + // b = make([]fr.Element, 2) + // b[0] = bls.ToFr("3") + // b[1] = bls.ToFr("4") + + // c = make([]fr.Element, 1) + // fmt.Println(len(a), len(b), len(c)) + // err = kzg.Hadamard(a, b, c) + // require.Nil(t, err) +} diff --git a/op-service/eigenda/encoding/utils/toeplitz/toeplitz.go b/op-service/eigenda/encoding/utils/toeplitz/toeplitz.go new file mode 100644 index 000000000..b58f3d404 --- /dev/null +++ b/op-service/eigenda/encoding/utils/toeplitz/toeplitz.go @@ -0,0 +1,161 @@ +package toeplitz + +import ( + "errors" + "log" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/fft" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +// V is ordered as (v_0, .., v_6), so it creates a +// matrix below. Slice must be odd +// v_0 v_1 v_2 v_3 +// v_6 v_0 v_1 v_2 +// v_5 v_6 v_0 v_1 +// v_4 v_5 v_6 v_0 +type Toeplitz struct { + V []fr.Element + Fs *fft.FFTSettings +} + +func NewToeplitz(v []fr.Element, fs *fft.FFTSettings) (*Toeplitz, error) { + if len(v)%2 != 1 { + log.Println("num diagonal vector must be odd") + return nil, errors.New("num diagonal vector must be odd") + } + + return &Toeplitz{ + V: v, + Fs: fs, + }, nil +} + +// Mutliplication of a matrix and a vector, both elements are Fr Element +// good info about FFT and Toeplitz, +// https://alinush.github.io/2020/03/19/multiplying-a-vector-by-a-toeplitz-matrix.html +func (t *Toeplitz) Multiply(x []fr.Element) ([]fr.Element, error) { + cv := t.ExtendCircularVec() + + rv := t.FromColVToRowV(cv) + cir := NewCircular(rv, t.Fs) + + xE := make([]fr.Element, len(cv)) + for i := 0; i < len(x); i++ { + xE[i].Set(&x[i]) + } + for i := len(x); i < len(cv); i++ { + xE[i].SetZero() + } + + product, err := cir.Multiply(xE) + if err != nil { + return nil, err + } + + return product[:len(x)], nil +} + +// Take FFT on Toeplitz vector, coefficient is used for computing hadamard product +// but carried with multi scalar multiplication +func (t *Toeplitz) GetFFTCoeff() ([]fr.Element, error) { + cv := t.ExtendCircularVec() + + rv := t.FromColVToRowV(cv) + cir := NewCircular(rv, t.Fs) + + return cir.GetFFTCoeff() +} + +func (t *Toeplitz) GetCoeff() ([]fr.Element, error) { + cv := t.ExtendCircularVec() + + rv := t.FromColVToRowV(cv) + cir := NewCircular(rv, t.Fs) + + return cir.GetCoeff() +} + +// Expand toeplitz matrix into circular matrix +// the outcome is a also concise representation +// if V is (v_0, v_1, v_2, v_3, v_4, v_5, v_6) +// then E is (v_0, v_6, v_5, v_4, 0, v_3, v_2, v_1) +// representing +// [v_0, v_6, v_5, v_4, 0 , v_3, v_2, v_1 ] +// [v_1, v_0, v_6, v_5, v_4, 0 , v_3, v_2 ] +// [v_2, v_1, v_0, v_6, v_5, v_4, 0 , v_3 ] +// [v_3, v_2, v_1, v_0, v_6, v_5, v_4, 0 ] +// [0 , v_3, v_2, v_1, v_0, v_6, v_5, v_4 ] +// [v_4, 0 , v_3, v_2, v_1, v_0, v_6, v_5 ] +// [v_5, v_4, 0 , v_3, v_2, v_1, v_0, v_6 ] +// [v_6, v_5, v_4, 0 , v_3, v_2, v_1, v_0 ] + +func (t *Toeplitz) ExtendCircularVec() []fr.Element { + E := make([]fr.Element, len(t.V)+1) // extra 1 from extended, equal to 2*dimE + numRow := t.GetMatDim() + E[0].Set(&t.V[0]) + + for i := 1; i < numRow; i++ { + + E[i].Set(&t.V[len(t.V)-i]) + } + + // assign some value to the extra dimension + E[numRow].SetZero() + + // numRow == numCol + for i := 1; i < numRow; i++ { + E[numRow+i].Set(&t.V[numRow-i]) + } + + return E +} + +// if col Vector is [v_0, v_1, v_2, v_3, 0, v_4, v_5, v_6] +// then row Vector is [v_0, v_6, v_5, v_4, 0, v_3, v_2, v_1] +// this operation is involutory. i.e. f(f(v)) = v + +func (t *Toeplitz) FromColVToRowV(cv []fr.Element) []fr.Element { + n := len(cv) + rv := make([]fr.Element, n) + + rv[0].Set(&cv[0]) + + for i := 1; i < n; i++ { + + rv[i].Set(&cv[n-i]) + } + + return rv +} + +func (t *Toeplitz) GetMatDim() int { + return (len(t.V) + 1) / 2 +} + +// naive implementation for multiplication. Used for testing +func (t *Toeplitz) DirectMultiply(x []fr.Element) []fr.Element { + numCol := t.GetMatDim() + + n := len(t.V) + + out := make([]fr.Element, numCol) + for i := 0; i < numCol; i++ { + var sum fr.Element + sum.SetZero() + + for j := 0; j < numCol; j++ { + idx := (j - i + n) % n + var product fr.Element + product.Mul(&t.V[idx], &x[j]) + + sum.Add(&product, &sum) + + } + + out[i].Set(&sum) + } + + return out +} diff --git a/op-service/eigenda/encoding/utils/toeplitz/toeplitz_test.go b/op-service/eigenda/encoding/utils/toeplitz/toeplitz_test.go new file mode 100644 index 000000000..0b78e994c --- /dev/null +++ b/op-service/eigenda/encoding/utils/toeplitz/toeplitz_test.go @@ -0,0 +1,161 @@ +package toeplitz_test + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/fft" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/utils/toeplitz" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// V is ordered as (v_0, .., v_6), so it creates a +// matrix below. Slice must be odd +// v_0 v_1 v_2 v_3 +// v_6 v_0 v_1 v_2 +// v_5 v_6 v_0 v_1 +// v_4 v_5 v_6 v_0 +func TestNewToeplitz(t *testing.T) { + v := make([]fr.Element, 7) + v[0].SetInt64(int64(7)) + v[1].SetInt64(int64(11)) + v[2].SetInt64(int64(5)) + v[3].SetInt64(int64(6)) + v[4].SetInt64(int64(3)) + v[5].SetInt64(int64(8)) + v[6].SetInt64(int64(1)) + fs := fft.NewFFTSettings(4) + + toe, err := toeplitz.NewToeplitz(v, fs) + require.Nil(t, err) + + assert.Equal(t, v[0], toe.V[0]) + assert.Equal(t, v[1], toe.V[1]) + assert.Equal(t, v[2], toe.V[2]) + assert.Equal(t, v[3], toe.V[3]) + assert.Equal(t, v[4], toe.V[4]) + assert.Equal(t, v[5], toe.V[5]) +} + +func TestNewToeplitz_InvalidSize(t *testing.T) { + v := make([]fr.Element, 2) + v[0].SetInt64(int64(4)) + v[1].SetInt64(int64(2)) + fs := fft.NewFFTSettings(4) + + _, err := toeplitz.NewToeplitz(v, fs) + assert.EqualError(t, err, "num diagonal vector must be odd") +} + +// Expand toeplitz matrix into circular matrix +// the outcome is a also concise representation +// if V is (v_0, v_1, v_2, v_3, v_4, v_5, v_6) +// then E is (v_0, v_6, v_5, v_4, 0, v_3, v_2, v_1) +func TestExtendCircularVec(t *testing.T) { + v := make([]fr.Element, 7) + v[0].SetInt64(int64(7)) + v[1].SetInt64(int64(11)) + v[2].SetInt64(int64(5)) + v[3].SetInt64(int64(6)) + v[4].SetInt64(int64(3)) + v[5].SetInt64(int64(8)) + v[6].SetInt64(int64(1)) + + fs := fft.NewFFTSettings(4) + c, err := toeplitz.NewToeplitz(v, fs) + require.Nil(t, err) + + cVec := c.ExtendCircularVec() + assert.Equal(t, cVec[0], v[0]) + assert.Equal(t, cVec[1], v[6]) + assert.Equal(t, cVec[2], v[5]) + assert.Equal(t, cVec[3], v[4]) + assert.Equal(t, cVec[4], encoding.ZERO) + assert.Equal(t, cVec[5], v[3]) + assert.Equal(t, cVec[6], v[2]) + assert.Equal(t, cVec[7], v[1]) +} + +// if col Vector is [v_0, v_1, v_2, v_3, 0, v_4, v_5, v_6] +// then row Vector is [v_0, v_6, v_5, v_4, 0, v_3, v_2, v_1] +// this operation is involutory. i.e. f(f(v)) = v +func TestFromColVToRowV(t *testing.T) { + v := make([]fr.Element, 7) + v[0].SetInt64(int64(7)) + v[1].SetInt64(int64(11)) + v[2].SetInt64(int64(5)) + v[3].SetInt64(int64(6)) + v[4].SetInt64(int64(3)) + v[5].SetInt64(int64(8)) + v[6].SetInt64(int64(1)) + + fs := fft.NewFFTSettings(4) + c, err := toeplitz.NewToeplitz(v, fs) + require.Nil(t, err) + + cVec := c.ExtendCircularVec() + rVec := c.FromColVToRowV(cVec) + + assert.Equal(t, rVec[0], v[0]) + assert.Equal(t, rVec[1], v[1]) + assert.Equal(t, rVec[2], v[2]) + assert.Equal(t, rVec[3], v[3]) + assert.Equal(t, rVec[4], encoding.ZERO) + assert.Equal(t, rVec[5], v[4]) + assert.Equal(t, rVec[6], v[5]) + assert.Equal(t, rVec[7], v[6]) + + // involutory + cVec = c.FromColVToRowV(rVec) + assert.Equal(t, cVec[0], v[0]) + assert.Equal(t, cVec[1], v[6]) + assert.Equal(t, cVec[2], v[5]) + assert.Equal(t, cVec[3], v[4]) + assert.Equal(t, cVec[4], encoding.ZERO) + assert.Equal(t, cVec[5], v[3]) + assert.Equal(t, cVec[6], v[2]) + assert.Equal(t, cVec[7], v[1]) +} + +func TestMultiplyToeplitz(t *testing.T) { + v := make([]fr.Element, 7) + v[0].SetInt64(int64(7)) + v[1].SetInt64(int64(11)) + v[2].SetInt64(int64(5)) + v[3].SetInt64(int64(6)) + v[4].SetInt64(int64(3)) + v[5].SetInt64(int64(8)) + v[6].SetInt64(int64(1)) + + fs := fft.NewFFTSettings(4) + toe, err := toeplitz.NewToeplitz(v, fs) + + require.Nil(t, err) + + x := make([]fr.Element, 4) + x[0].SetInt64(int64(1)) + x[1].SetInt64(int64(2)) + x[2].SetInt64(int64(3)) + x[3].SetInt64(int64(4)) + + b, err := toe.Multiply(x) + require.Nil(t, err) + + p := make([]fr.Element, 4) + p[0].SetInt64(int64(68)) + p[1].SetInt64(int64(68)) + p[2].SetInt64(int64(75)) + p[3].SetInt64(int64(50)) + + assert.Equal(t, b[0], p[0]) + assert.Equal(t, b[1], p[1]) + assert.Equal(t, b[2], p[2]) + assert.Equal(t, b[3], p[3]) + + // Assert with direct multiplication + b2 := toe.DirectMultiply(x) + assert.Equal(t, b, b2) +} diff --git a/op-service/eigenda/metrics.go b/op-service/eigenda/metrics.go new file mode 100644 index 000000000..0f0a1c4ad --- /dev/null +++ b/op-service/eigenda/metrics.go @@ -0,0 +1,5 @@ +package eigenda + +type Metrics interface { + RecordInterval(method string) func(error) +} diff --git a/op-service/eigenda/signer.go b/op-service/eigenda/signer.go new file mode 100644 index 000000000..fd9ee1ab0 --- /dev/null +++ b/op-service/eigenda/signer.go @@ -0,0 +1,125 @@ +package eigenda + +import ( + "context" + "crypto/ecdsa" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "fmt" + "log" + "time" + + kms "cloud.google.com/go/kms/apiv1" + bsscore "github.com/ethereum-optimism/optimism/bss-core" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "google.golang.org/api/option" +) + +type BlobRequestSigner interface { + SignBlobRequest(nonce uint32) ([]byte, error) + GetAccountID() string +} + +type localBlobSigner struct { + PrivateKey *ecdsa.PrivateKey +} + +func NewLocalBlobSigner(privateKeyHex string) (*localBlobSigner, error) { + + privateKeyBytes := common.FromHex(privateKeyHex) + privateKey, err := crypto.ToECDSA(privateKeyBytes) + if err != nil { + log.Fatalf("Failed to parse private key: %v", err) + } + + return &localBlobSigner{ + PrivateKey: privateKey, + }, nil +} + +func (s *localBlobSigner) SignBlobRequest(nonce uint32) ([]byte, error) { + + // Message you want to sign + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, nonce) + hash := crypto.Keccak256(buf) + + // Sign the hash using the private key + sig, err := crypto.Sign(hash, s.PrivateKey) + if err != nil { + return nil, fmt.Errorf("failed to sign hash: %v", err) + } + + return sig, nil +} + +func (s *localBlobSigner) GetAccountID() string { + + publicKeyBytes := crypto.FromECDSAPub(&s.PrivateKey.PublicKey) + return hexutil.Encode(publicKeyBytes) + +} + +type hsmBlobSigner struct { + mk *bsscore.ManagedKey + pubkey *ecdsa.PublicKey +} + +func NewHsmBlobSigner(hsmCreden, hsmAPIName, hsmPubkey string) (*hsmBlobSigner, error) { + + proBytes, err := hex.DecodeString(hsmCreden) + if err != nil { + return nil, err + } + apikey := option.WithCredentialsJSON(proBytes) + ctx, cancle := context.WithTimeout(context.Background(), time.Minute) + defer cancle() + client, err := kms.NewKeyManagementClient(ctx, apikey) + if err != nil { + return nil, err + } + + rawData, err := base64.StdEncoding.DecodeString(hsmPubkey) + if err != nil { + return nil, err + } + + //rawData[23:] skip the header + pubkey, err := crypto.UnmarshalPubkey(rawData[23:]) + if err != nil { + return nil, err + } + + mk := &bsscore.ManagedKey{ + KeyName: hsmAPIName, + EthereumAddr: crypto.PubkeyToAddress(*pubkey), + Gclient: client, + } + return &hsmBlobSigner{mk: mk, pubkey: pubkey}, nil +} + +func (s *hsmBlobSigner) SignBlobRequest(nonce uint32) ([]byte, error) { + + //Message you want to sign + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, nonce) + hash := crypto.Keccak256(buf) + + // hash the transaction (with Keccak-256 probably) + ctx, cancle := context.WithTimeout(context.Background(), time.Second*10) + defer cancle() + sig, err := s.mk.SignHash(ctx, common.BytesToHash(hash)) + if err != nil { + return nil, err + } + + return sig, nil +} + +func (s *hsmBlobSigner) GetAccountID() string { + publicKeyBytes := crypto.FromECDSAPub(s.pubkey) + return hexutil.Encode(publicKeyBytes) +} diff --git a/op-service/eigenda/verify/cert.go b/op-service/eigenda/verify/cert.go new file mode 100644 index 000000000..9cc87f3a3 --- /dev/null +++ b/op-service/eigenda/verify/cert.go @@ -0,0 +1,130 @@ +package verify + +import ( + "bytes" + "context" + "errors" + "fmt" + "math/big" + + binding "github.com/ethereum-optimism/optimism/op-service/eigenda/bindings/EigenDAServiceManager" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + + "golang.org/x/exp/slices" +) + +var ErrBatchMetadataHashNotFound = errors.New("BatchMetadataHash not found for BatchId") + +// CertVerifier verifies the DA certificate against on-chain EigenDA contracts +// to ensure disperser returned fields haven't been tampered with +type CertVerifier struct { + ethConfirmationDepth uint64 + manager *binding.ContractEigenDAServiceManagerCaller + finalizedBlockClient *FinalizedBlockClient + ethClient *ethclient.Client +} + +func NewCertVerifier(cfg *Config, l log.Logger) (*CertVerifier, error) { + c, err := rpc.DialContext(context.Background(), cfg.RPCURL) + if err != nil { + return nil, fmt.Errorf("failed to dial ETH RPC node: %s", err.Error()) + } + client := ethclient.NewClient(c) + + // construct binding + m, err := binding.NewContractEigenDAServiceManagerCaller(common.HexToAddress(cfg.SvcManagerAddr), client) + if err != nil { + return nil, err + } + + return &CertVerifier{ + manager: m, + finalizedBlockClient: NewFinalizedBlockClient(c), + ethConfirmationDepth: cfg.EthConfirmationDepth, + ethClient: client, + }, nil +} + +func (cv *CertVerifier) VerifyBatch(header *binding.IEigenDAServiceManagerBatchHeader, + id uint32, recordHash [32]byte, blockNum uint32) error { + // 0 - Determine block context number + blockNumber, err := cv.getContextBlock() + if err != nil { + return err + } + + // 1 - Verify batch hash + + // 1.a - ensure that a batch hash can be looked up for a batch ID + expectedHash, err := cv.manager.BatchIdToBatchMetadataHash(&bind.CallOpts{BlockNumber: blockNumber}, id) + if err != nil { + return err + } + if bytes.Equal(expectedHash[:], make([]byte, 32)) { + return ErrBatchMetadataHashNotFound + } + + // 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 := slices.Equal(expectedHash[:], actualHash[:]) + if !equal { + return fmt.Errorf("batch hash mismatch, expected: %x, got: %x", expectedHash, actualHash) + } + + return nil +} + +// VerifyMerkleProof +func (cv *CertVerifier) VerifyMerkleProof(inclusionProof []byte, root []byte, blobIndex uint32, blobHeader BlobHeader) error { + leafHash, err := HashEncodeBlobHeader(blobHeader) + if err != nil { + return err + } + + generatedRoot, err := ProcessInclusionProof(inclusionProof, leafHash, uint64(blobIndex)) + if err != nil { + return err + } + + equal := slices.Equal(root, generatedRoot.Bytes()) + if !equal { + return fmt.Errorf("root hash mismatch, expected: %x, got: %x", root, generatedRoot) + } + + return nil +} + +// 3 - (TODO) verify blob security params +func (cv *CertVerifier) VerifyBlobParams(inclusionProof []byte, rootHash []byte, leafHash []byte, index uint64) error { + return nil +} + +func (cv *CertVerifier) getContextBlock() (*big.Int, error) { + var blockNumber *big.Int + if cv.ethConfirmationDepth == 0 { + // Get the latest finalized block + blockHeader, err := cv.finalizedBlockClient.GetBlock(context.Background(), "finalized", false) + if err != nil { + return nil, err + } + blockNumber = blockHeader.Number() + } else { + blockHeader, err := cv.ethClient.BlockByNumber(context.Background(), nil) + if err != nil { + return nil, err + } + blockNumber = new(big.Int) + blockNumber.Sub(blockHeader.Number(), big.NewInt(int64(cv.ethConfirmationDepth-1))) + } + return blockNumber, nil +} diff --git a/op-service/eigenda/verify/certificate.go b/op-service/eigenda/verify/certificate.go new file mode 100644 index 000000000..54441a0a9 --- /dev/null +++ b/op-service/eigenda/verify/certificate.go @@ -0,0 +1,70 @@ +package verify + +import ( + "fmt" + "math/big" + + "github.com/Layr-Labs/eigenda/api/grpc/disperser" +) + +var ( + ErrInvalidDomainType = fmt.Errorf("invalid domain type") +) + +// 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 +} diff --git a/op-service/eigenda/verify/finalized_block_number_client.go b/op-service/eigenda/verify/finalized_block_number_client.go new file mode 100644 index 000000000..79760e1f0 --- /dev/null +++ b/op-service/eigenda/verify/finalized_block_number_client.go @@ -0,0 +1,63 @@ +package verify + +import ( + "context" + "encoding/json" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" +) + +type FinalizedBlockClient struct { + c *rpc.Client +} + +// Dial connects a client to the given URL. +func Dial(rawurl string) (*FinalizedBlockClient, error) { + return DialContext(context.Background(), rawurl) +} + +// DialContext connects a client to the given URL with context. +func DialContext(ctx context.Context, rawurl string) (*FinalizedBlockClient, error) { + c, err := rpc.DialContext(ctx, rawurl) + if err != nil { + return nil, err + } + return NewFinalizedBlockClient(c), nil +} + +// NewFinalizedBlockClient creates a client that uses the given RPC client. +func NewFinalizedBlockClient(c *rpc.Client) *FinalizedBlockClient { + return &FinalizedBlockClient{c} +} + +// Close closes the underlying RPC connection. +func (ec *FinalizedBlockClient) Close() { + ec.c.Close() +} + +// Client gets the underlying RPC client. +func (ec *FinalizedBlockClient) Client() *rpc.Client { + return ec.c +} + +func (c *FinalizedBlockClient) GetBlock(ctx context.Context, method string, args ...interface{}) (*types.Block, error) { + var raw json.RawMessage + err := c.c.CallContext(ctx, &raw, method, args...) + if err != nil { + return nil, err + } + + // Decode header and transactions. + var head *types.Header + if err := json.Unmarshal(raw, &head); err != nil { + return nil, err + } + // When the block is not found, the API returns JSON null. + if head == nil { + return nil, ethereum.NotFound + } + + return types.NewBlockWithHeader(head), nil +} diff --git a/op-service/eigenda/verify/hasher.go b/op-service/eigenda/verify/hasher.go new file mode 100644 index 000000000..715a096d1 --- /dev/null +++ b/op-service/eigenda/verify/hasher.go @@ -0,0 +1,144 @@ +package verify + +import ( + "encoding/binary" + + binding "github.com/ethereum-optimism/optimism/op-service/eigenda/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 +} + +// HashBlobHeader function to hash BlobHeader +func HashBlobHeader(blobHeader 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 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/op-service/eigenda/verify/hasher_test.go b/op-service/eigenda/verify/hasher_test.go new file mode 100644 index 000000000..c2be9d666 --- /dev/null +++ b/op-service/eigenda/verify/hasher_test.go @@ -0,0 +1,134 @@ +package verify + +import ( + "math/big" + "testing" + + eigenda_common "github.com/Layr-Labs/eigenda/api/grpc/common" + "github.com/Layr-Labs/eigenda/api/grpc/disperser" + binding "github.com/ethereum-optimism/optimism/op-service/eigenda/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) + +} + +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 := &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 := &Certificate{ + BlobHeader: header, + } + + actual, err := HashEncodeBlobHeader(cert.ReadBlobHeader()) + + require.NoError(t, err) + require.Equal(t, expected, actual.String()) +} diff --git a/op-service/eigenda/verify/merkle.go b/op-service/eigenda/verify/merkle.go new file mode 100644 index 000000000..3d8bc9b71 --- /dev/null +++ b/op-service/eigenda/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/op-service/eigenda/verify/merkle_test.go b/op-service/eigenda/verify/merkle_test.go new file mode 100644 index 000000000..2ac5e0027 --- /dev/null +++ b/op-service/eigenda/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/op-service/eigenda/verify/verifier.go b/op-service/eigenda/verify/verifier.go new file mode 100644 index 000000000..564f82584 --- /dev/null +++ b/op-service/eigenda/verify/verifier.go @@ -0,0 +1,207 @@ +package verify + +import ( + "fmt" + + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fp" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding" + "github.com/ethereum/go-ethereum/log" + + binding "github.com/ethereum-optimism/optimism/op-service/eigenda/bindings/EigenDAServiceManager" + + "github.com/Layr-Labs/eigenda/api/grpc/common" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg/prover" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/rs" +) + +type Config struct { + Verify bool + RPCURL string + SvcManagerAddr string + KzgConfig *kzg.KzgConfig + EthConfirmationDepth uint64 +} + +type Verifier struct { + verifyCert bool + prover *prover.Prover + cv *CertVerifier +} + +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{ + verifyCert: cfg.Verify, + prover: prover, + cv: cv, + }, nil +} + +func (v *Verifier) VerifyCert(cert *Certificate) error { + if !v.verifyCert { + return nil + } + + // 1 - verify batch + blobHeadersRoot := [32]byte{} + signatoryRecordHash := [32]byte{} + root := cert.Proof().GetBatchMetadata().GetBatchHeader().GetBatchRoot() + recordHash := cert.Proof().BatchMetadata.GetSignatoryRecordHash() + if len(root) > 32 { + root = root[:32] + } + if len(recordHash) > 32 { + recordHash = recordHash[:32] + } + copy(blobHeadersRoot[:], root) + copy(signatoryRecordHash[:], recordHash) + header := binding.IEigenDAServiceManagerBatchHeader{ + BlobHeadersRoot: blobHeadersRoot, + 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(), signatoryRecordHash, cert.Proof().BatchMetadata.GetConfirmationBlockNumber()) + if err != nil { + return err + } + + // 2 - verify merkle inclusion proof + err = v.cv.VerifyMerkleProof(cert.Proof().GetInclusionProof(), cert.BatchHeaderRoot(), cert.Proof().GetBlobIndex(), cert.ReadBlobHeader()) + if err != nil { + return err + } + + // 3 - verify security parameters + err = v.VerifySecurityParams(cert.ReadBlobHeader(), header) + if err != nil { + return err + } + + 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 + encoder, err := v.prover.GetKzgEncoder( + encoding.ParamsFromSysPar(420, 69, uint64(len(blob))), + ) + if err != nil { + return nil, err + } + + inputFr, err := rs.ToFrArray(blob) + if err != nil { + return nil, fmt.Errorf("cannot convert bytes to field elements, %w", err) + } + + commit, err := encoder.Commit(inputFr) + if err != nil { + return nil, err + } + + return &commit, nil +} + +// 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) VerifyCommitment(expectedCommit *common.G1Commitment, blob []byte) error { + actualCommit, err := v.Commit(blob) + if err != nil { + return err + } + + expectedX := &fp.Element{} + expectedX.SetBytes(expectedCommit.X) + expectedY := &fp.Element{} + expectedY.SetBytes(expectedCommit.Y) + + errMsg := "" + if !actualCommit.X.Equal(expectedX) || !actualCommit.Y.Equal(expectedY) { + errMsg += fmt.Sprintf("field elements do not match, x actual commit: %x, x expected commit: %x, ", actualCommit.X.Marshal(), (*expectedX).Marshal()) + errMsg += fmt.Sprintf("y actual commit: %x, y expected commit: %x", actualCommit.Y.Marshal(), (*expectedY).Marshal()) + return fmt.Errorf(errMsg) + } + + return nil +} + +// VerifySecurityParams ensures that returned security parameters are valid +func (v *Verifier) VerifySecurityParams(blobHeader 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 +} + +// 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 { + return 0, err + } + + if len(percentages) > int(quorumNum) { + return percentages[quorumNum], nil + } + + return 0, nil +} diff --git a/op-service/eigenda/verify/verify_test.go b/op-service/eigenda/verify/verify_test.go new file mode 100644 index 000000000..716a1988e --- /dev/null +++ b/op-service/eigenda/verify/verify_test.go @@ -0,0 +1,59 @@ +package verify + +import ( + "encoding/hex" + "runtime" + "testing" + + "github.com/Layr-Labs/eigenda/api/grpc/common" + "github.com/ethereum-optimism/optimism/op-service/eigenda/codecs" + "github.com/ethereum-optimism/optimism/op-service/eigenda/encoding/kzg" + "github.com/stretchr/testify/assert" +) + +func TestCommitmentVerification(t *testing.T) { + t.Parallel() + + var data = []byte("inter-subjective and not objective!") + + x, err := hex.DecodeString("1021d699eac68ce312196d480266e8b82fd5fe5c4311e53313837b64db6df178") + assert.NoError(t, err) + + y, err := hex.DecodeString("02efa5a7813233ae13f32bae9b8f48252fa45c1b06a5d70bed471a9bea8d98ae") + assert.NoError(t, err) + + c := &common.G1Commitment{ + X: x, + Y: y, + } + + kzgConfig := &kzg.KzgConfig{ + G1Path: "../resources/g1.point", + G2PowerOf2Path: "../resources/g2.point.powerOf2", + CacheDir: "../resources/SRSTables", + SRSOrder: 3000, + SRSNumberToLoad: 3000, + NumWorker: uint64(runtime.GOMAXPROCS(0)), + } + + 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.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.VerifyCommitment(c, fakeData) + assert.Error(t, err) +} diff --git a/op-service/src/op_service/v1/calldata.proto b/op-service/src/op_service/v1/calldata.proto deleted file mode 100644 index ad78de4be..000000000 --- a/op-service/src/op_service/v1/calldata.proto +++ /dev/null @@ -1,23 +0,0 @@ -syntax = "proto3"; - -option go_package = "github.com/ethereum-optimism/optimism/op_service"; - -package op_service.v1; - -// CalldataFrame wraps the frame data or the eigenda blob reference to the frame data -message CalldataFrame { - oneof value { - bytes frame = 1; - FrameRef frame_ref = 2; - } -} - -// This is a copy of BlobRequest here: https://github.com/Layr-Labs/eigenda/blob/main/api/proto/retriever/retriever.proto#L10 -message FrameRef { - bytes batch_header_hash = 1; - uint32 blob_index = 2; - uint32 reference_block_number = 3; - repeated uint32 quorum_ids = 4; - uint32 blob_length = 5; - bytes request_id = 6; -} diff --git a/op-service/txmgr/txmgr.go b/op-service/txmgr/txmgr.go index fb14bfd51..e586d404e 100644 --- a/op-service/txmgr/txmgr.go +++ b/op-service/txmgr/txmgr.go @@ -506,7 +506,7 @@ func (m *SimpleTxManager) increaseGasPrice(ctx context.Context, tx *types.Transa m.l.Warn("failed to get suggested gas tip and basefee", "err", err) return tx } - gasTipCap, gasFeeCap := updateFees(tx.GasTipCap(), tx.GasFeeCap(), tip, basefee, m.l) + gasTipCap, gasFeeCap := updateFees(tx.GasTipCap(), tx.GasFeeCap(), tip, basefee, tx.Type() == types.BlobTxType, m.l) if tx.GasTipCapIntCmp(gasTipCap) == 0 && tx.GasFeeCapIntCmp(gasFeeCap) == 0 { return tx @@ -625,7 +625,7 @@ func calcThresholdValue(x *big.Int, isBlobTx bool) *big.Int { // updateFees takes the old tip/basefee & the new tip/basefee and then suggests // a gasTipCap and gasFeeCap that satisfies geth's required fee bumps // Geth: FC and Tip must be bumped if any increase -func updateFees(oldTip, oldFeeCap, newTip, newBaseFee *big.Int, lgr log.Logger) (*big.Int, *big.Int) { +func updateFees(oldTip, oldFeeCap, newTip, newBaseFee *big.Int, isBlobTx bool, lgr log.Logger) (*big.Int, *big.Int) { newFeeCap := calcGasFeeCap(newBaseFee, newTip) lgr = lgr.New("old_tip", oldTip, "old_feecap", oldFeeCap, "new_tip", newTip, "new_feecap", newFeeCap) // If the new prices are less than the old price, reuse the old prices @@ -634,8 +634,8 @@ func updateFees(oldTip, oldFeeCap, newTip, newBaseFee *big.Int, lgr log.Logger) return oldTip, oldFeeCap } // Determine if we need to increase the suggested values - thresholdTip := calcThresholdValue(oldTip, false) - thresholdFeeCap := calcThresholdValue(oldFeeCap, false) + thresholdTip := calcThresholdValue(oldTip, isBlobTx) + thresholdFeeCap := calcThresholdValue(oldFeeCap, isBlobTx) if newTip.Cmp(thresholdTip) >= 0 && newFeeCap.Cmp(thresholdFeeCap) >= 0 { lgr.Debug("Using new tip and feecap") return newTip, newFeeCap