diff --git a/bindings/encoding/input.go b/bindings/encoding/input.go index 24c29f137..c7f1d5f2f 100644 --- a/bindings/encoding/input.go +++ b/bindings/encoding/input.go @@ -210,8 +210,6 @@ func EncodeProposeBlockInput(metadataInput *TaikoL1BlockMetadataInput) ([]byte, // EncodeProveBlockInput encodes the input params for TaikoL1.proveBlock. func EncodeProveBlockInput( evidence *TaikoL1Evidence, - anchorTx *types.Transaction, - anchorReceipt *types.Receipt, ) ([]byte, error) { evidenceBytes, err := EncodeEvidence(evidence) if err != nil { diff --git a/bindings/encoding/input_test.go b/bindings/encoding/input_test.go index 61e2ad150..140ba32aa 100644 --- a/bindings/encoding/input_test.go +++ b/bindings/encoding/input_test.go @@ -56,15 +56,6 @@ func TestEncodeProveBlockInput(t *testing.T) { VerifierId: 1024, Proof: randomHash().Big().Bytes(), }, - types.NewTransaction( - 0, - common.BytesToAddress(randomHash().Bytes()), - common.Big0, - 0, - common.Big0, - randomHash().Bytes(), - ), - types.NewReceipt(randomHash().Bytes(), false, 1024), ) require.Nil(t, err) diff --git a/cmd/flags/prover.go b/cmd/flags/prover.go index 634e73780..b96ca328d 100644 --- a/cmd/flags/prover.go +++ b/cmd/flags/prover.go @@ -53,6 +53,12 @@ var ( "`lowerBound-upperBound` (e.g. `30m-1h`), testing purposes only", Category: proverCategory, } + OracleProver = &cli.BoolFlag{ + Name: "oracleProver", + Usage: "Set whether prover should use oracle prover or not", + Category: proverCategory, + Value: false, + } ) // All prover flags. @@ -67,4 +73,5 @@ var ProverFlags = MergeFlags(CommonFlags, []cli.Flag{ MaxConcurrentProvingJobs, Dummy, RandomDummyProofDelay, + OracleProver, }) diff --git a/driver/anchor_tx_constructor/anchor_tx_constructor_test.go b/driver/anchor_tx_constructor/anchor_tx_constructor_test.go index 927d704d4..b808d6ea5 100644 --- a/driver/anchor_tx_constructor/anchor_tx_constructor_test.go +++ b/driver/anchor_tx_constructor/anchor_tx_constructor_test.go @@ -45,7 +45,7 @@ func (s *AnchorTxConstructorTestSuite) TestAssembleAnchorTx() { } func (s *AnchorTxConstructorTestSuite) TestNewAnchorTransactor() { - godlenTouchAddress, err := s.RpcClient.TaikoL2.GOLDENTOUCHADDRESS(nil) + goldenTouchAddress, err := s.RpcClient.TaikoL2.GOLDENTOUCHADDRESS(nil) s.Nil(err) c, err := New( @@ -58,7 +58,7 @@ func (s *AnchorTxConstructorTestSuite) TestNewAnchorTransactor() { s.Nil(err) s.Equal(true, opts.NoSend) s.Equal(common.Big0, opts.Nonce) - s.Equal(godlenTouchAddress, opts.From) + s.Equal(goldenTouchAddress, opts.From) s.Equal(common.Big256, opts.GasFeeCap) s.Equal(common.Big0, opts.GasTipCap) } diff --git a/prover/anchor_tx_validator/anchor_tx_validator_test.go b/prover/anchor_tx_validator/anchor_tx_validator_test.go index 69fb36a5c..0414986d2 100644 --- a/prover/anchor_tx_validator/anchor_tx_validator_test.go +++ b/prover/anchor_tx_validator/anchor_tx_validator_test.go @@ -29,11 +29,11 @@ func (s *AnchorTxValidatorTestSuite) TestValidateAnchorTx() { wrongPrivKey, err := crypto.HexToECDSA("2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200") s.Nil(err) - godlenTouchPrivKey, err := s.RpcClient.TaikoL2.GOLDENTOUCHPRIVATEKEY(nil) + goldenTouchPrivKey, err := s.RpcClient.TaikoL2.GOLDENTOUCHPRIVATEKEY(nil) s.Nil(err) // 0x92954368afd3caa1f3ce3ead0069c1af414054aefe1ef9aeacc1bf426222ce38 - goldenTouchPriKey, err := crypto.HexToECDSA(common.Bytes2Hex(godlenTouchPrivKey.Bytes())) + goldenTouchPriKey, err := crypto.HexToECDSA(common.Bytes2Hex(goldenTouchPrivKey.Bytes())) s.Nil(err) // invalid To diff --git a/prover/config.go b/prover/config.go index 6d013b518..b4238de3a 100644 --- a/prover/config.go +++ b/prover/config.go @@ -27,6 +27,7 @@ type Config struct { StartingBlockID *big.Int MaxConcurrentProvingJobs uint Dummy bool + OracleProver bool RandomDummyProofDelayLowerBound *time.Duration RandomDummyProofDelayUpperBound *time.Duration } @@ -87,6 +88,7 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { StartingBlockID: startingBlockID, MaxConcurrentProvingJobs: c.Uint(flags.MaxConcurrentProvingJobs.Name), Dummy: c.Bool(flags.Dummy.Name), + OracleProver: c.Bool(flags.OracleProver.Name), RandomDummyProofDelayLowerBound: randomDummyProofDelayLowerBound, RandomDummyProofDelayUpperBound: randomDummyProofDelayUpperBound, }, nil diff --git a/prover/config_test.go b/prover/config_test.go index adf21187c..c30017ab8 100644 --- a/prover/config_test.go +++ b/prover/config_test.go @@ -29,6 +29,7 @@ func (s *ProverTestSuite) TestNewConfigFromCliContext() { &cli.StringFlag{Name: flags.L1ProverPrivKey.Name}, &cli.BoolFlag{Name: flags.Dummy.Name}, &cli.StringFlag{Name: flags.RandomDummyProofDelay.Name}, + &cli.BoolFlag{Name: flags.OracleProver.Name}, } app.Action = func(ctx *cli.Context) error { c, err := NewConfigFromCliContext(ctx) @@ -46,6 +47,7 @@ func (s *ProverTestSuite) TestNewConfigFromCliContext() { s.Equal(30*time.Minute, *c.RandomDummyProofDelayLowerBound) s.Equal(time.Hour, *c.RandomDummyProofDelayUpperBound) s.True(c.Dummy) + s.True(c.OracleProver) s.Nil(new(Prover).InitFromCli(context.Background(), ctx)) return err @@ -62,5 +64,6 @@ func (s *ProverTestSuite) TestNewConfigFromCliContext() { "-" + flags.L1ProverPrivKey.Name, os.Getenv("L1_PROVER_PRIVATE_KEY"), "-" + flags.Dummy.Name, "-" + flags.RandomDummyProofDelay.Name, "30m-1h", + "-" + flags.OracleProver.Name, })) } diff --git a/prover/proof_producer/oracle_proof_producer.go b/prover/proof_producer/oracle_proof_producer.go new file mode 100644 index 000000000..db9abd60c --- /dev/null +++ b/prover/proof_producer/oracle_proof_producer.go @@ -0,0 +1,135 @@ +package producer + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/taikoxyz/taiko-client/bindings" + "github.com/taikoxyz/taiko-client/bindings/encoding" + "github.com/taikoxyz/taiko-client/pkg/rpc" + anchorTxValidator "github.com/taikoxyz/taiko-client/prover/anchor_tx_validator" +) + +// OracleProducer is responsible for generating a fake "zkproof" consisting +// of a signature of the evidence. +type OracleProducer struct { + rpc *rpc.Client + proverPrivKey *ecdsa.PrivateKey + anchorTxValidator *anchorTxValidator.AnchorTxValidator +} + +// NewOracleProducer creates a new NewOracleProducer instance. +func NewOracleProducer( + rpc *rpc.Client, + proverPrivKey *ecdsa.PrivateKey, + taikoL2Address common.Address, +) (*OracleProducer, error) { + anchorValidator, err := anchorTxValidator.New(taikoL2Address, rpc.L2ChainID, rpc) + if err != nil { + return nil, err + } + + return &OracleProducer{rpc, proverPrivKey, anchorValidator}, nil +} + +// RequestProof implements the ProofProducer interface. +func (d *OracleProducer) RequestProof( + ctx context.Context, + opts *ProofRequestOptions, + blockID *big.Int, + meta *bindings.TaikoDataBlockMetadata, + header *types.Header, + resultCh chan *ProofWithHeader, +) error { + log.Info( + "Request oracle proof", + "blockID", blockID, + "beneficiary", meta.Beneficiary, + "height", header.Number, + "hash", header.Hash(), + ) + + block, err := d.rpc.L2.BlockByHash(ctx, header.Hash()) + if err != nil { + return fmt.Errorf("failed to get L2 block with given hash %s: %w", header.Hash(), err) + } + + anchorTx := block.Transactions()[0] + if err := d.anchorTxValidator.ValidateAnchorTx(ctx, anchorTx); err != nil { + return fmt.Errorf("invalid anchor transaction: %w", err) + } + + signalRoot, err := d.anchorTxValidator.GetAnchoredSignalRoot(ctx, anchorTx) + if err != nil { + return err + } + + parent, err := d.rpc.L2.BlockByHash(ctx, block.ParentHash()) + if err != nil { + return err + } + + blockInfo, err := d.rpc.TaikoL1.GetBlock(nil, blockID) + if err != nil { + return err + } + + // signature should be done with proof set to nil, verifierID set to 0, + // and prover set to 0 address. + evidence := &encoding.TaikoL1Evidence{ + MetaHash: blockInfo.MetaHash, + ParentHash: block.ParentHash(), + BlockHash: block.Hash(), + SignalRoot: signalRoot, + Graffiti: [32]byte{}, + Prover: common.HexToAddress("0x0000000000000000000000000000000000000000"), + ParentGasUsed: uint32(parent.GasUsed()), + GasUsed: uint32(block.GasUsed()), + VerifierId: 0, + Proof: nil, + } + + proof, _, err := hashAndSignForOracleProof(evidence, d.proverPrivKey) + if err != nil { + return fmt.Errorf("failed to sign evidence: %w", err) + } + + resultCh <- &ProofWithHeader{ + BlockID: blockID, + Header: header, + Meta: meta, + ZkProof: proof, + } + + return nil +} + +// HashSignAndSetEvidenceForOracleProof hashes and signs the TaikoL1Evidence according to the +// protocol spec to generate an "oracle proof" via the signature and v value. +func hashAndSignForOracleProof( + evidence *encoding.TaikoL1Evidence, + privateKey *ecdsa.PrivateKey, +) ([]byte, uint8, error) { + inputToSign, err := encoding.EncodeProveBlockInput(evidence) + if err != nil { + return nil, 0, fmt.Errorf("failed to encode TaikoL1.proveBlock inputs: %w", err) + } + + hashed := crypto.Keccak256Hash(inputToSign) + + sig, err := crypto.Sign(hashed.Bytes(), privateKey) + if err != nil { + return nil, 0, fmt.Errorf("failed to sign TaikoL1Evidence: %w", err) + } + + // add 27 to be able to be ecrecover in solidity + v := uint8(int(sig[64])) + 27 + + return sig, v, nil +} diff --git a/prover/proof_producer/oracle_proof_producer_test.go b/prover/proof_producer/oracle_proof_producer_test.go new file mode 100644 index 000000000..c946fefa9 --- /dev/null +++ b/prover/proof_producer/oracle_proof_producer_test.go @@ -0,0 +1,62 @@ +package producer + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "math/big" + "math/rand" + "os" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + "github.com/taikoxyz/taiko-client/bindings/encoding" +) + +// randomHash generates a random blob of data and returns it as a hash. +func randomHash() common.Hash { + var hash common.Hash + if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil { + panic(err) + } + return hash +} + +func TestHashAndSignOracleProof(t *testing.T) { + evidence := &encoding.TaikoL1Evidence{ + MetaHash: randomHash(), + BlockHash: randomHash(), + ParentHash: randomHash(), + SignalRoot: randomHash(), + Graffiti: randomHash(), + Prover: common.BigToAddress(new(big.Int).SetUint64(rand.Uint64())), + ParentGasUsed: 1024, + GasUsed: 1024, + VerifierId: 0, + Proof: nil, + } + + privateKey, err := crypto.HexToECDSA(os.Getenv("L1_PROVER_PRIVATE_KEY")) + require.Nil(t, err) + + publicKey := privateKey.Public() + + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + require.True(t, ok) + + input, err := encoding.EncodeProveBlockInput(evidence) + require.Nil(t, err) + + hash := crypto.Keccak256Hash(input) + + sig, _, err := hashAndSignForOracleProof(evidence, privateKey) + require.Nil(t, err) + + pubKey, err := crypto.Ecrecover(hash.Bytes(), sig) + require.Nil(t, err) + isValid := crypto.VerifySignature(pubKey, hash.Bytes(), sig[:64]) + require.True(t, isValid) + + require.Equal(t, elliptic.Marshal(publicKeyECDSA, publicKeyECDSA.X, publicKeyECDSA.Y), pubKey) +} diff --git a/prover/proof_submitter/util.go b/prover/proof_submitter/util.go index 1e2963684..231c686f3 100644 --- a/prover/proof_submitter/util.go +++ b/prover/proof_submitter/util.go @@ -24,7 +24,7 @@ var ( // isSubmitProofTxErrorRetryable checks whether the error returned by a proof submission transaction // is retryable. func isSubmitProofTxErrorRetryable(err error, blockID *big.Int) bool { - if strings.HasPrefix(err.Error(), "L1_NOT_ORACLE_PROVEN") || !strings.HasPrefix(err.Error(), "L1_") { + if strings.HasPrefix(err.Error(), "L1_NOT_ORACLE_PROVER") || !strings.HasPrefix(err.Error(), "L1_") { return true } diff --git a/prover/proof_submitter/util_test.go b/prover/proof_submitter/util_test.go index cf39db471..602af89c3 100644 --- a/prover/proof_submitter/util_test.go +++ b/prover/proof_submitter/util_test.go @@ -11,7 +11,7 @@ import ( func (s *ProofSubmitterTestSuite) TestIsSubmitProofTxErrorRetryable() { s.True(isSubmitProofTxErrorRetryable(errors.New(testAddr.String()), common.Big0)) - s.True(isSubmitProofTxErrorRetryable(errors.New("L1_NOT_ORACLE_PROVEN"), common.Big0)) + s.True(isSubmitProofTxErrorRetryable(errors.New("L1_NOT_ORACLE_PROVER"), common.Big0)) s.False(isSubmitProofTxErrorRetryable(errors.New("L1_DUP_PROVERS"), common.Big0)) s.False(isSubmitProofTxErrorRetryable(errors.New("L1_"+testAddr.String()), common.Big0)) } diff --git a/prover/proof_submitter/valid_proof_submitter.go b/prover/proof_submitter/valid_proof_submitter.go index e1f7cb22c..c111e61a8 100644 --- a/prover/proof_submitter/valid_proof_submitter.go +++ b/prover/proof_submitter/valid_proof_submitter.go @@ -26,21 +26,23 @@ var _ ProofSubmitter = (*ValidProofSubmitter)(nil) type ValidProofSubmitter struct { rpc *rpc.Client proofProducer proofProducer.ProofProducer - reusltCh chan *proofProducer.ProofWithHeader + resultCh chan *proofProducer.ProofWithHeader anchorTxValidator *anchorTxValidator.AnchorTxValidator proverPrivKey *ecdsa.PrivateKey proverAddress common.Address mutex *sync.Mutex + isOracle bool } // NewValidProofSubmitter creates a new ValidProofSubmitter instance. func NewValidProofSubmitter( rpc *rpc.Client, proofProducer proofProducer.ProofProducer, - reusltCh chan *proofProducer.ProofWithHeader, + resultCh chan *proofProducer.ProofWithHeader, taikoL2Address common.Address, proverPrivKey *ecdsa.PrivateKey, mutex *sync.Mutex, + isOracle bool, ) (*ValidProofSubmitter, error) { anchorValidator, err := anchorTxValidator.New(taikoL2Address, rpc.L2ChainID, rpc) if err != nil { @@ -50,11 +52,12 @@ func NewValidProofSubmitter( return &ValidProofSubmitter{ rpc: rpc, proofProducer: proofProducer, - reusltCh: reusltCh, + resultCh: resultCh, anchorTxValidator: anchorValidator, proverPrivKey: proverPrivKey, proverAddress: crypto.PubkeyToAddress(proverPrivKey.PublicKey), mutex: mutex, + isOracle: isOracle, }, nil } @@ -78,7 +81,7 @@ func (s *ValidProofSubmitter) RequestProof(ctx context.Context, event *bindings. ProposeBlockTxHash: event.Raw.TxHash, } - if err := s.proofProducer.RequestProof(ctx, opts, event.Id, &event.Meta, header, s.reusltCh); err != nil { + if err := s.proofProducer.RequestProof(ctx, opts, event.Id, &event.Meta, header, s.resultCh); err != nil { return err } @@ -134,16 +137,11 @@ func (s *ValidProofSubmitter) SubmitProof( } // Get and validate this anchor transaction's receipt. - anchorTxReceipt, err := s.anchorTxValidator.GetAndValidateAnchorTxReceipt(ctx, anchorTx) + _, err = s.anchorTxValidator.GetAndValidateAnchorTxReceipt(ctx, anchorTx) if err != nil { return fmt.Errorf("failed to fetch anchor transaction receipt: %w", err) } - circuitsIdx, err := proofProducer.DegreeToCircuitsIdx(proofWithHeader.Degree) - if err != nil { - return err - } - signalRoot, err := s.anchorTxValidator.GetAnchoredSignalRoot(ctx, anchorTx) if err != nil { return err @@ -165,14 +163,29 @@ func (s *ValidProofSubmitter) SubmitProof( BlockHash: block.Hash(), SignalRoot: signalRoot, Graffiti: [32]byte{}, - Prover: s.proverAddress, ParentGasUsed: uint32(parent.GasUsed()), GasUsed: uint32(block.GasUsed()), - VerifierId: circuitsIdx, Proof: zkProof, } - input, err := encoding.EncodeProveBlockInput(evidence, anchorTx, anchorTxReceipt) + var circuitsIdx uint16 + var prover common.Address + + if s.isOracle { + prover = common.HexToAddress("0x0000000000000000000000000000000000000000") + circuitsIdx = uint16(int(zkProof[64])) + 27 + } else { + prover = s.proverAddress + + circuitsIdx, err = proofProducer.DegreeToCircuitsIdx(proofWithHeader.Degree) + if err != nil { + return err + } + } + evidence.Prover = prover + evidence.VerifierId = circuitsIdx + + input, err := encoding.EncodeProveBlockInput(evidence) if err != nil { return fmt.Errorf("failed to encode TaikoL1.proveBlock inputs: %w", err) } diff --git a/prover/proof_submitter/valid_proof_submitter_test.go b/prover/proof_submitter/valid_proof_submitter_test.go index 975ecbc9d..f750a2af5 100644 --- a/prover/proof_submitter/valid_proof_submitter_test.go +++ b/prover/proof_submitter/valid_proof_submitter_test.go @@ -45,6 +45,7 @@ func (s *ProofSubmitterTestSuite) SetupTest() { common.HexToAddress(os.Getenv("TAIKO_L2_ADDRESS")), l1ProverPrivKey, &sync.Mutex{}, + false, ) s.Nil(err) diff --git a/prover/prover.go b/prover/prover.go index 53d2e81f7..243ba0919 100644 --- a/prover/prover.go +++ b/prover/prover.go @@ -124,7 +124,16 @@ func InitFromConfig(ctx context.Context, p *Prover, cfg *Config) (err error) { p.submitProofConcurrencyGuard = make(chan struct{}, cfg.MaxConcurrentProvingJobs) var producer proofProducer.ProofProducer - if cfg.Dummy { + + if cfg.OracleProver { + if producer, err = proofProducer.NewOracleProducer( + p.rpc, + p.cfg.L1ProverPrivKey, + p.cfg.TaikoL2Address, + ); err != nil { + return err + } + } else if cfg.Dummy { producer = &proofProducer.DummyProofProducer{ RandomDummyProofDelayLowerBound: p.cfg.RandomDummyProofDelayLowerBound, RandomDummyProofDelayUpperBound: p.cfg.RandomDummyProofDelayUpperBound, @@ -149,6 +158,7 @@ func InitFromConfig(ctx context.Context, p *Prover, cfg *Config) (err error) { p.cfg.TaikoL2Address, p.cfg.L1ProverPrivKey, p.submitProofTxMutex, + p.cfg.OracleProver, ); err != nil { return err }