From a8d91b14e274162333ffca62589345ea1ef1ab5c Mon Sep 17 00:00:00 2001 From: lightclient Date: Mon, 27 Nov 2023 08:16:02 -0700 Subject: [PATCH 01/17] core: implement auth and authcall --- core/blockchain_test.go | 128 ++++++++++++++++++++++++++++++++++++++ core/vm/eips.go | 102 ++++++++++++++++++++++++++++++ core/vm/errors.go | 1 + core/vm/evm.go | 82 ++++++++++++++++++++++++ core/vm/interpreter.go | 9 ++- core/vm/jump_table.go | 7 +++ core/vm/memory_table.go | 4 ++ core/vm/opcodes.go | 7 +++ core/vm/operations_acl.go | 4 +- params/protocol_params.go | 2 + 10 files changed, 342 insertions(+), 4 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 6600bbfa2a..9cb9813fb2 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -17,6 +17,7 @@ package core import ( + "bytes" "errors" "fmt" "math/big" @@ -5100,3 +5101,130 @@ func TestEIP3651(t *testing.T) { t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) } } + +func TestEIP3074(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + engine = beacon.NewFaker() + + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr = crypto.PubkeyToAddress(key.PublicKey) + funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + config = *params.AllEthashProtocolChanges + gspec = &Genesis{ + Config: &config, + Alloc: GenesisAlloc{ + addr: {Balance: funds}, + // The address 0xAAAA sloads 0x00 and 0x01 + aa: { + Code: nil, // added below + Nonce: 0, + Balance: big.NewInt(0), + }, + // The address 0xBBBB calls 0xAAAA + bb: { + Code: []byte{ + byte(vm.CALLER), + byte(vm.PUSH0), + byte(vm.SSTORE), + byte(vm.STOP), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + ) + + invoker := []byte{ + // copy sig to memory + byte(vm.CALLDATASIZE), + byte(vm.PUSH0), + byte(vm.PUSH0), + byte(vm.CALLDATACOPY), + + // set up auth + byte(vm.CALLDATASIZE), + byte(vm.PUSH0), + } + // push authority to stack + invoker = append(invoker, append([]byte{byte(vm.PUSH20)}, addr.Bytes()...)...) + invoker = append(invoker, []byte{ + + byte(vm.AUTH), + byte(vm.POP), + + // execute authcall + byte(vm.PUSH0), // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.DUP1), // valueExt + byte(vm.DUP1), // value + byte(vm.PUSH2), // address + byte(0xbb), + byte(0xbb), + byte(vm.GAS), // gas + byte(vm.AUTHCALL), + byte(vm.STOP), + }..., + ) + + // Set the invoker's code. + if entry, _ := gspec.Alloc[aa]; true { + entry.Code = invoker + gspec.Alloc[aa] = entry + } + + gspec.Config.BerlinBlock = common.Big0 + gspec.Config.LondonBlock = common.Big0 + gspec.Config.TerminalTotalDifficulty = common.Big0 + gspec.Config.TerminalTotalDifficultyPassed = true + gspec.Config.ShanghaiTime = u64(0) + gspec.Config.CancunTime = u64(0) + gspec.Config.PragueTime = u64(0) + signer := types.LatestSigner(gspec.Config) + + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + commit := common.Hash{0x42} + msg := []byte{params.AuthMagic} + msg = append(msg, common.LeftPadBytes(gspec.Config.ChainID.Bytes(), 32)...) + msg = append(msg, common.LeftPadBytes(aa.Bytes(), 32)...) + msg = append(msg, commit.Bytes()...) + msg = crypto.Keccak256(msg) + + sig, _ := crypto.Sign(msg, key) + sig = append([]byte{sig[len(sig)-1]}, sig[0:len(sig)-1]...) + txdata := &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: 0, + To: &aa, + Gas: 500000, + GasFeeCap: newGwei(5), + GasTipCap: big.NewInt(2), + AccessList: nil, + Data: append(sig, commit.Bytes()...), + } + tx := types.NewTx(txdata) + tx, _ = types.SignTx(tx, signer, key) + + b.AddTx(tx) + }) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr)}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + // Verify authcall worked correctly. + state, _ := chain.State() + got := state.GetState(bb, common.Hash{}) + if want := common.LeftPadBytes(addr.Bytes(), 32); !bytes.Equal(got.Bytes(), want) { + t.Fatalf("incorrect sender in authcall: got %s, want %s", got.Hex(), common.Bytes2Hex(want)) + } +} diff --git a/core/vm/eips.go b/core/vm/eips.go index ca46f192d7..196fab1881 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -21,6 +21,7 @@ import ( "sort" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -333,3 +334,104 @@ func enable6780(jt *JumpTable) { maxStack: maxStack(1, 0), } } + +func enable3074(jt *JumpTable) { + jt[AUTH] = &operation{ + execute: opAuth, + constantGas: 3100, + dynamicGas: gasReturn, + minStack: minStack(3, 1), + maxStack: maxStack(3, 1), + memorySize: memoryAuth, + } + jt[AUTHCALL] = &operation{ + execute: opAuthCall, + constantGas: params.CallGasEIP150, + dynamicGas: gasCallEIP2929, + minStack: minStack(8, 1), + maxStack: maxStack(8, 1), + memorySize: memoryCall, + } +} + +func opAuth(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + tmp = scope.Stack.pop() + authority = common.Address(tmp.Bytes20()) + offset = scope.Stack.pop() + length = scope.Stack.pop() + data = scope.Memory.GetPtr(int64(offset.Uint64()), int64(length.Uint64())) + sig = make([]byte, 65) + commit common.Hash + ) + copy(sig, data) + if len(data) > 65 { + copy(commit[:], data[65:]) + } + + // Build original auth message. + msg := []byte{params.AuthMagic} + msg = append(msg, common.LeftPadBytes(interpreter.evm.chainConfig.ChainID.Bytes(), 32)...) + msg = append(msg, common.LeftPadBytes(scope.Contract.Address().Bytes(), 32)...) + msg = append(msg, commit.Bytes()...) + msg = crypto.Keccak256(msg) + + // Verify signature against provided address. + sig = append(sig[1:], sig[0]) + pub, err := crypto.Ecrecover(msg, sig) + var recovered common.Address + copy(recovered[:], crypto.Keccak256(pub[1:])[12:]) + + fmt.Println(recovered) + fmt.Println(authority) + + if err != nil || recovered != authority { + scope.Stack.push(uint256.NewInt(0)) + return nil, err + } + + scope.Stack.push(uint256.NewInt(1)) + scope.Authorized = &authority + return nil, nil +} + +func opAuthCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if scope.Authorized == nil { + return nil, ErrAuthorizedNotSet + } + var ( + stack = scope.Stack + temp = stack.pop() + gas = interpreter.evm.callGasTemp + addr, value, _, inOffset, inSize, retOffset, retSize = stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr = common.Address(addr.Bytes20()) + args = scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + ) + + if interpreter.readOnly && !value.IsZero() { + return nil, ErrWriteProtection + } + + var bigVal = big0 + if !value.IsZero() { + gas += params.CallStipend + bigVal = value.ToBig() + } + + ret, returnGas, err := interpreter.evm.AuthCall(scope.Contract, *scope.Authorized, toAddr, args, gas, bigVal) + + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + scope.Contract.Gas += returnGas + + interpreter.returnData = ret + return ret, nil + +} diff --git a/core/vm/errors.go b/core/vm/errors.go index fbbf19e178..c1e6174b74 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -37,6 +37,7 @@ var ( ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + ErrAuthorizedNotSet = errors.New("authcall without setting authorized") // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. diff --git a/core/vm/evm.go b/core/vm/evm.go index e6d1648901..a53ebf7408 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -428,6 +428,88 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte return ret, gas, err } +// AuthCall mimic Call except it sets the caller to the Authorized addres$ in Scope. +func (evm *EVM) AuthCall(invoker ContractRef, caller, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { + // Fail if we're trying to execute above the call depth limit + if evm.depth > int(params.CallCreateDepth) { + return nil, gas, ErrDepth + } + // Fail if we're trying to transfer more than the available balance + if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, invoker.Address(), value) { + return nil, gas, ErrInsufficientBalance + } + snapshot := evm.StateDB.Snapshot() + p, isPrecompile := evm.precompile(addr) + debug := evm.Config.Tracer != nil + + if !evm.StateDB.Exist(addr) { + if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { + // Calling a non existing account, don't do anything, but ping the tracer + if debug { + if evm.depth == 0 { + evm.Config.Tracer.CaptureStart(evm, invoker.Address(), addr, false, input, gas, value) + evm.Config.Tracer.CaptureEnd(ret, 0, nil) + } else { + evm.Config.Tracer.CaptureEnter(AUTHCALL, invoker.Address(), addr, input, gas, value) + evm.Config.Tracer.CaptureExit(ret, 0, nil) + } + } + return nil, gas, nil + } + evm.StateDB.CreateAccount(addr) + } + evm.Context.Transfer(evm.StateDB, invoker.Address(), addr, value) + + // Capture the tracer start/end events in debug mode + if debug { + if evm.depth == 0 { + evm.Config.Tracer.CaptureStart(evm, invoker.Address(), addr, false, input, gas, value) + defer func(startGas uint64) { // Lazy evaluation of the parameters + evm.Config.Tracer.CaptureEnd(ret, startGas-gas, err) + }(gas) + } else { + // Handle tracer events for entering and exiting a call frame + evm.Config.Tracer.CaptureEnter(AUTHCALL, invoker.Address(), addr, input, gas, value) + defer func(startGas uint64) { + evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) + }(gas) + } + } + + if isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + code := evm.StateDB.GetCode(addr) + if len(code) == 0 { + ret, err = nil, nil // gas is unchanged + } else { + addrCopy := addr + callerCopy := caller + // If the account has no code, we can abort here + // The depth-check is already done, and precompiles handled above + contract := NewContract(AccountRef(callerCopy), AccountRef(addrCopy), value, gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + ret, err = evm.interpreter.Run(contract, input, false) + gas = contract.Gas + } + } + // When an error was returned by the EVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in homestead this also counts for code storage gas errors. + if err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { + gas = 0 + } + // TODO: consider clearing up unused snapshots: + //} else { + // evm.StateDB.DiscardSnapshot(snapshot) + } + return ret, gas, err +} + type codeAndHash struct { code []byte hash common.Hash diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 5c04ecac59..51d6a20026 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -57,9 +57,10 @@ type Config struct { // ScopeContext contains the things that are per-call, such as stack and memory, // but not transients like pc and gas type ScopeContext struct { - Memory *Memory - Stack *Stack - Contract *Contract + Memory *Memory + Stack *Stack + Contract *Contract + Authorized *common.Address } // EVMInterpreter represents an EVM interpreter @@ -128,6 +129,8 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { var table *JumpTable switch { + case evm.chainRules.IsPrague: + table = &pragueInstructionSet case evm.chainRules.IsCancun: table = &cancunInstructionSet case evm.chainRules.IsShanghai: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index a65ba0a3d5..e62e3bb212 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -57,6 +57,7 @@ var ( mergeInstructionSet = newMergeInstructionSet() shanghaiInstructionSet = newShanghaiInstructionSet() cancunInstructionSet = newCancunInstructionSet() + pragueInstructionSet = newPraugeInstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. @@ -81,6 +82,12 @@ func validate(jt JumpTable) JumpTable { return jt } +func newPraugeInstructionSet() JumpTable { + instructionSet := newCancunInstructionSet() + enable3074(&instructionSet) // EIP-3074 AUTH & AUTHCALL + return validate(instructionSet) +} + func newCancunInstructionSet() JumpTable { instructionSet := newShanghaiInstructionSet() // Disabled in bor diff --git a/core/vm/memory_table.go b/core/vm/memory_table.go index 2c412c6f1c..b4d8ebca98 100644 --- a/core/vm/memory_table.go +++ b/core/vm/memory_table.go @@ -56,6 +56,10 @@ func memoryMcopy(stack *Stack) (uint64, bool) { return calcMemSize64(mStart, stack.Back(2)) // stack[2]: length } +func memoryAuth(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(1), stack.Back(2)) +} + func memoryCreate(stack *Stack) (uint64, bool) { return calcMemSize64(stack.Back(1), stack.Back(2)) } diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index c7a3a163be..670123a5d0 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -218,6 +218,9 @@ const ( DELEGATECALL OpCode = 0xf4 CREATE2 OpCode = 0xf5 + AUTH OpCode = 0xf6 + AUTHCALL OpCode = 0xf7 + STATICCALL OpCode = 0xfa REVERT OpCode = 0xfd INVALID OpCode = 0xfe @@ -391,6 +394,8 @@ var opCodeToString = [256]string{ CALLCODE: "CALLCODE", DELEGATECALL: "DELEGATECALL", CREATE2: "CREATE2", + AUTH: "AUTH", + AUTHCALL: "AUTHCALL", STATICCALL: "STATICCALL", REVERT: "REVERT", INVALID: "INVALID", @@ -548,6 +553,8 @@ var stringToOp = map[string]OpCode{ "LOG4": LOG4, "CREATE": CREATE, "CREATE2": CREATE2, + "AUTH": AUTH, + "AUTHCALL": AUTHCALL, "CALL": CALL, "RETURN": RETURN, "CALLCODE": CALLCODE, diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 3050af22fb..4d1772d42c 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -18,6 +18,7 @@ package vm import ( "errors" + "fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -47,7 +48,8 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { // Once we're done with YOLOv2 and schedule this for mainnet, might // be good to remove this panic here, which is just really a // canary to have during testing - panic("impossible case: address was not present in access list during sstore op") + + panic(fmt.Sprintf("impossible case: address was not present in access list during sstore op %s", contract.Address())) } } diff --git a/params/protocol_params.go b/params/protocol_params.go index a1f2a17845..83fa796fbf 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -196,6 +196,8 @@ var ( BeaconRootsStorageAddress = common.HexToAddress("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") // SystemAddress is where the system-transaction is sent from as per EIP-4788 SystemAddress common.Address = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") + + AuthMagic = byte(0x03) ) func BaseFeeChangeDenominator(borConfig *BorConfig, number *big.Int) uint64 { From 7842448e14b26a97f108d47f582a57e3f2fe75da Mon Sep 17 00:00:00 2001 From: lightclient Date: Mon, 27 Nov 2023 08:23:10 -0700 Subject: [PATCH 02/17] core/vm: remove gas stipend for authcall --- core/vm/eips.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 196fab1881..aad5b3504e 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -414,7 +414,6 @@ func opAuthCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ var bigVal = big0 if !value.IsZero() { - gas += params.CallStipend bigVal = value.ToBig() } From 4f812ffcd45409d7442b28537defedafa191a71e Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Mon, 22 Apr 2024 19:52:10 +0530 Subject: [PATCH 03/17] core: fix tests, handle interrupt ctx --- core/blockchain_test.go | 9 +++++---- core/vm/eips.go | 2 +- core/vm/evm.go | 4 ++-- core/vm/instructions_test.go | 24 ++++++++++++------------ core/vm/interpreter.go | 2 +- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 9cb9813fb2..42df79429b 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -5182,9 +5182,10 @@ func TestEIP3074(t *testing.T) { gspec.Config.LondonBlock = common.Big0 gspec.Config.TerminalTotalDifficulty = common.Big0 gspec.Config.TerminalTotalDifficultyPassed = true - gspec.Config.ShanghaiTime = u64(0) - gspec.Config.CancunTime = u64(0) - gspec.Config.PragueTime = u64(0) + gspec.Config.ShanghaiBlock = common.Big0 + gspec.Config.CancunBlock = common.Big0 + // TODO(manav2401): Add HF name whenever decided + // gspec.Config.PragueTime = u64(0) signer := types.LatestSigner(gspec.Config) _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { @@ -5212,7 +5213,7 @@ func TestEIP3074(t *testing.T) { b.AddTx(tx) }) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr)}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr)}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } diff --git a/core/vm/eips.go b/core/vm/eips.go index aad5b3504e..59d000a149 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -417,7 +417,7 @@ func opAuthCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ bigVal = value.ToBig() } - ret, returnGas, err := interpreter.evm.AuthCall(scope.Contract, *scope.Authorized, toAddr, args, gas, bigVal) + ret, returnGas, err := interpreter.evm.AuthCall(scope.Contract, *scope.Authorized, toAddr, args, gas, bigVal, nil) if err != nil { temp.Clear() diff --git a/core/vm/evm.go b/core/vm/evm.go index a53ebf7408..7de1ac1137 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -429,7 +429,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte } // AuthCall mimic Call except it sets the caller to the Authorized addres$ in Scope. -func (evm *EVM) AuthCall(invoker ContractRef, caller, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { +func (evm *EVM) AuthCall(invoker ContractRef, caller, addr common.Address, input []byte, gas uint64, value *big.Int, interruptCtx context.Context) (ret []byte, leftOverGas uint64, err error) { // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth @@ -491,7 +491,7 @@ func (evm *EVM) AuthCall(invoker ContractRef, caller, addr common.Address, input // The depth-check is already done, and precompiles handled above contract := NewContract(AccountRef(callerCopy), AccountRef(addrCopy), value, gas) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) - ret, err = evm.interpreter.Run(contract, input, false) + ret, err = evm.interpreter.PreRun(contract, input, false, interruptCtx) gas = contract.Gas } } diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index bdd10e7f91..17170bfebb 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -120,7 +120,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu stack.push(x) stack.push(y) - opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) @@ -239,7 +239,7 @@ func TestAddMod(t *testing.T) { stack.push(z) stack.push(y) stack.push(x) - opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, nil}) actual := stack.pop() if actual.Cmp(expected) != 0 { @@ -271,7 +271,7 @@ func TestWriteExpectedValues(t *testing.T) { stack.push(x) stack.push(y) - _, _ = opFn(&pc, interpreter, &ScopeContext{nil, stack, nil}) + _, _ = opFn(&pc, interpreter, &ScopeContext{nil, stack, nil, nil}) actual := stack.pop() result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} } @@ -312,7 +312,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() - scope = &ScopeContext{nil, stack, nil} + scope = &ScopeContext{nil, stack, nil, nil} evmInterpreter = NewEVMInterpreter(env) ) @@ -570,7 +570,7 @@ func TestOpMstore(t *testing.T) { v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v))) stack.push(new(uint256.Int)) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, nil}) if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) @@ -578,7 +578,7 @@ func TestOpMstore(t *testing.T) { stack.push(new(uint256.Int).SetUint64(0x1)) stack.push(new(uint256.Int)) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, nil}) if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") @@ -606,7 +606,7 @@ func BenchmarkOpMstore(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(value) stack.push(memStart) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, nil}) } } @@ -623,7 +623,7 @@ func TestOpTstore(t *testing.T) { to = common.Address{1} contractRef = contractRef{caller} contract = NewContract(contractRef, AccountRef(to), new(big.Int), 0) - scopeContext = ScopeContext{mem, stack, contract} + scopeContext = ScopeContext{mem, stack, contract, nil} value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700") ) @@ -679,7 +679,7 @@ func BenchmarkOpKeccak256(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(uint256.NewInt(32)) stack.push(start) - opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, nil}) } } @@ -775,7 +775,7 @@ func TestRandom(t *testing.T) { evmInterpreter = env.interpreter ) - opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) @@ -821,7 +821,7 @@ func TestBlobHash(t *testing.T) { evmInterpreter = env.interpreter ) stack.push(uint256.NewInt(tt.idx)) - opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -962,7 +962,7 @@ func TestOpMCopy(t *testing.T) { mem.Resize(memorySize) } // Do the copy - opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, nil}) want := common.FromHex(strings.ReplaceAll(tc.want, " ", "")) if have := mem.store; !bytes.Equal(want, have) { t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 51d6a20026..d7c9e24471 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -178,7 +178,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { return &EVMInterpreter{evm: evm, table: table} } -// PreRun is a wrapper around Run that allows for a delay to be injected before each opcode when induced by tests else it calls the lagace Run() method +// PreRun is a wrapper around Run that allows for a delay to be injected before each opcode when induced by tests else it calls the legacy Run() method func (in *EVMInterpreter) PreRun(contract *Contract, input []byte, readOnly bool, interruptCtx context.Context) (ret []byte, err error) { var opcodeDelay interface{} From 7077d0c6578b2692c40056722f4a1dae72c0f18e Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Mon, 22 Apr 2024 20:09:43 +0530 Subject: [PATCH 04/17] core/vm: handle nonce in AUTH message --- core/vm/eips.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 59d000a149..6f96e86da5 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -18,6 +18,7 @@ package vm import ( "fmt" + "math/big" "sort" "github.com/ethereum/go-ethereum/common" @@ -356,13 +357,14 @@ func enable3074(jt *JumpTable) { func opAuth(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - tmp = scope.Stack.pop() - authority = common.Address(tmp.Bytes20()) - offset = scope.Stack.pop() - length = scope.Stack.pop() - data = scope.Memory.GetPtr(int64(offset.Uint64()), int64(length.Uint64())) - sig = make([]byte, 65) - commit common.Hash + tmp = scope.Stack.pop() + authority = common.Address(tmp.Bytes20()) + offset = scope.Stack.pop() + length = scope.Stack.pop() + data = scope.Memory.GetPtr(int64(offset.Uint64()), int64(length.Uint64())) + authorityNonce = big.NewInt(int64(interpreter.evm.StateDB.GetNonce(authority))) + sig = make([]byte, 65) + commit common.Hash ) copy(sig, data) if len(data) > 65 { @@ -372,6 +374,7 @@ func opAuth(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt // Build original auth message. msg := []byte{params.AuthMagic} msg = append(msg, common.LeftPadBytes(interpreter.evm.chainConfig.ChainID.Bytes(), 32)...) + msg = append(msg, common.LeftPadBytes(authorityNonce.Bytes(), 32)...) msg = append(msg, common.LeftPadBytes(scope.Contract.Address().Bytes(), 32)...) msg = append(msg, commit.Bytes()...) msg = crypto.Keccak256(msg) From 0c16e6d219c27302226eb23346755a7615e5e9a2 Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Mon, 22 Apr 2024 20:26:52 +0530 Subject: [PATCH 05/17] core/vm: handle error if authorized is a contract address --- core/vm/eips.go | 6 ++++++ core/vm/errors.go | 1 + 2 files changed, 7 insertions(+) diff --git a/core/vm/eips.go b/core/vm/eips.go index 6f96e86da5..b544075bde 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -371,6 +371,12 @@ func opAuth(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt copy(commit[:], data[65:]) } + // Verify if the provided authority address isn't a contract + if code := interpreter.evm.StateDB.GetCode(authority); len(code) != 0 { + scope.Stack.push(uint256.NewInt(0)) + return nil, ErrAuthorizedIsContract + } + // Build original auth message. msg := []byte{params.AuthMagic} msg = append(msg, common.LeftPadBytes(interpreter.evm.chainConfig.ChainID.Bytes(), 32)...) diff --git a/core/vm/errors.go b/core/vm/errors.go index c1e6174b74..5ed286d5c3 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -37,6 +37,7 @@ var ( ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + ErrAuthorizedIsContract = errors.New("authcall with authorized as a contract address") ErrAuthorizedNotSet = errors.New("authcall without setting authorized") // errStopToken is an internal token indicating interpreter loop termination, From 1764839d6aac10b81dd63e8a91a12e91a1c191db Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Mon, 22 Apr 2024 20:35:29 +0530 Subject: [PATCH 06/17] params: update auth magic to 0x04 --- params/protocol_params.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/params/protocol_params.go b/params/protocol_params.go index 83fa796fbf..4d0c468389 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -197,7 +197,8 @@ var ( // SystemAddress is where the system-transaction is sent from as per EIP-4788 SystemAddress common.Address = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") - AuthMagic = byte(0x03) + // AuthMagic is used for signing EIP-3074 signatures to prevent signature collisions with other signing formats. + AuthMagic = byte(0x04) ) func BaseFeeChangeDenominator(borConfig *BorConfig, number *big.Int) uint64 { From bd7f9cf9e3252cb8f43fa3d174bfb893538c8b6b Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Tue, 23 Apr 2024 10:28:08 +0530 Subject: [PATCH 07/17] core: add prague block in 3074 tests --- core/blockchain_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 42df79429b..855647257f 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -5184,8 +5184,7 @@ func TestEIP3074(t *testing.T) { gspec.Config.TerminalTotalDifficultyPassed = true gspec.Config.ShanghaiBlock = common.Big0 gspec.Config.CancunBlock = common.Big0 - // TODO(manav2401): Add HF name whenever decided - // gspec.Config.PragueTime = u64(0) + gspec.Config.PragueBlock = common.Big0 signer := types.LatestSigner(gspec.Config) _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { From e62b94a0e3c95bb3d75a46a75a85e08a148bfcb0 Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Wed, 24 Apr 2024 15:15:02 +0530 Subject: [PATCH 08/17] core: fix 3074 tests --- core/blockchain_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 855647257f..bd46e2f061 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -5173,7 +5173,7 @@ func TestEIP3074(t *testing.T) { ) // Set the invoker's code. - if entry, _ := gspec.Alloc[aa]; true { + if entry := gspec.Alloc[aa]; true { entry.Code = invoker gspec.Alloc[aa] = entry } @@ -5191,6 +5191,7 @@ func TestEIP3074(t *testing.T) { commit := common.Hash{0x42} msg := []byte{params.AuthMagic} msg = append(msg, common.LeftPadBytes(gspec.Config.ChainID.Bytes(), 32)...) + msg = append(msg, common.LeftPadBytes(big.NewInt(1).Bytes(), 32)...) // nonce: 1 msg = append(msg, common.LeftPadBytes(aa.Bytes(), 32)...) msg = append(msg, commit.Bytes()...) msg = crypto.Keccak256(msg) From 99fbb2344d515f01550300f634a9803aad1cdbfe Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Thu, 25 Apr 2024 18:51:33 +0530 Subject: [PATCH 09/17] core/vm: set authorized to nil when called with contract --- core/vm/eips.go | 1 + core/vm/evm.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index b544075bde..92cc851aba 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -373,6 +373,7 @@ func opAuth(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt // Verify if the provided authority address isn't a contract if code := interpreter.evm.StateDB.GetCode(authority); len(code) != 0 { + scope.Authorized = nil scope.Stack.push(uint256.NewInt(0)) return nil, ErrAuthorizedIsContract } diff --git a/core/vm/evm.go b/core/vm/evm.go index 7de1ac1137..f596dcc85c 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -428,7 +428,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte return ret, gas, err } -// AuthCall mimic Call except it sets the caller to the Authorized addres$ in Scope. +// AuthCall mimic Call except it sets the caller to the Authorized address in Scope. func (evm *EVM) AuthCall(invoker ContractRef, caller, addr common.Address, input []byte, gas uint64, value *big.Int, interruptCtx context.Context) (ret []byte, leftOverGas uint64, err error) { // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { From 5db1359c384c973ca230fb6f50fb255f02a2aea1 Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Fri, 26 Apr 2024 18:15:03 +0530 Subject: [PATCH 10/17] core/vm: add test for AUTH instruction --- core/vm/instructions_test.go | 99 ++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 17170bfebb..616a79304a 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -18,6 +18,7 @@ package vm import ( "bytes" + "crypto/ecdsa" "encoding/json" "fmt" "math/big" @@ -33,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" + "github.com/stretchr/testify/require" ) type TwoOperandTestcase struct { @@ -973,3 +975,100 @@ func TestOpMCopy(t *testing.T) { } } } + +func TestOpAuth(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") // invoker + contract = NewContract(AccountRef(aa), AccountRef(aa), big.NewInt(0), 0) + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr = crypto.PubkeyToAddress(key.PublicKey) + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + env = NewEVM(BlockContext{}, TxContext{}, statedb, params.AllDevChainProtocolChanges, Config{}) + stack = newstack() + pc = uint64(0) + evmInterpreter = env.interpreter + commit = make([]byte, 32) + ) + + // Setup stack (with length = 97, offset = 0, and invoker address) + setupAuthStack(stack, 97, 0, addr) + + // Generate message as per EIP-3074 and sign it + msg := composeAuthMessage(params.AllDevChainProtocolChanges.ChainID, big.NewInt(0), aa, commit) + sig, err := signMessage(msg, key) + require.NoError(t, err, "failed to sign message") + require.Equal(t, 65, len(sig.sig), "invalid signature length") + + // Setup memory and scope for the AUTH call + memory := setupAuthMemory(sig, commit) + scope := &ScopeContext{memory, stack, contract, nil} + + // Call AUTH + res, err := opAuth(&pc, evmInterpreter, scope) + require.NoError(t, err, "failed to execute AUTH") + require.Equal(t, 0, len(res), "unexpected return value") + + // Check the stack for response and scope for authorized + actual := stack.pop() + require.Equal(t, uint64(1), actual.Uint64(), "unexpected value in stack") + require.Equal(t, addr, *scope.Authorized, "unexpected authorized address in scope") +} + +type Signature struct { + sig []byte + r *big.Int + s *big.Int + v *big.Int +} + +// signMessage signs the givem message with ecdsa private key. It returns the +// v, r, and s values of the signature. +func signMessage(msg []byte, key *ecdsa.PrivateKey) (*Signature, error) { + sig, err := crypto.Sign(msg[:], key) + if err != nil { + return nil, err + } + if len(sig) != crypto.SignatureLength { + return nil, fmt.Errorf("invalid signature length") + } + fmt.Println("sig", len(sig)) + r := new(big.Int).SetBytes(sig[:32]) + s := new(big.Int).SetBytes(sig[32:64]) + v := big.NewInt(int64(sig[64])) + fmt.Println(v, r, s) + return &Signature{sig, r, s, v}, nil +} + +// setupAuthStack pushes values expected by the AUTH instruction to the stack +func setupAuthStack(stack *Stack, length, offset uint64, addr common.Address) { + stack.push(uint256.NewInt(length)) + stack.push(uint256.NewInt(offset)) + stack.push(uint256.NewInt(0).SetBytes(addr.Bytes())) +} + +// setupAuthMemory sets the values expected by the AUTH instruction in memory +func setupAuthMemory(sig *Signature, commit []byte) *Memory { + memory := NewMemory() + memory.Resize(100) + + // Set signature (yParity, r, s) + memory.Set(0, 1, big.NewInt(int64(sig.v.Sign())).Bytes()) // [0] + memory.Set(1, 32, sig.r.Bytes()) // [1:33] + memory.Set(33, 32, sig.s.Bytes()) // [33:65] + + // Set `commit` for remaining bytes + memory.Set(65, 32, commit) // [65:97] + return memory +} + +// composeAuthMessage composes the message expected by the AUTH instruction in this format +// `keccak256(MAGIC || chainId || nonce || invokerAddress || commit)` +func composeAuthMessage(chainId, nonce *big.Int, invokerAddress common.Address, commit []byte) []byte { + msg := []byte{params.AuthMagic} + msg = append(msg, common.LeftPadBytes(chainId.Bytes(), 32)...) + msg = append(msg, common.LeftPadBytes(nonce.Bytes(), 32)...) + msg = append(msg, common.LeftPadBytes(invokerAddress.Bytes(), 32)...) + msg = append(msg, commit...) + msg = crypto.Keccak256(msg) + return msg +} From bafd541d65d51d5fd487a4efeb37641c0c033195 Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Mon, 29 Apr 2024 18:35:19 +0530 Subject: [PATCH 11/17] core/vm: add more AUTH tests --- core/vm/eips.go | 11 ++++- core/vm/errors.go | 1 + core/vm/instructions_test.go | 86 ++++++++++++++++++++++++++++++++++-- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 92cc851aba..d711d8ece2 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -389,15 +389,22 @@ func opAuth(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt // Verify signature against provided address. sig = append(sig[1:], sig[0]) pub, err := crypto.Ecrecover(msg, sig) + if err != nil { + scope.Authorized = nil + scope.Stack.push(uint256.NewInt(0)) + return nil, err + } + var recovered common.Address copy(recovered[:], crypto.Keccak256(pub[1:])[12:]) fmt.Println(recovered) fmt.Println(authority) - if err != nil || recovered != authority { + if recovered != authority { + scope.Authorized = nil scope.Stack.push(uint256.NewInt(0)) - return nil, err + return nil, ErrInvalidAuthSignature } scope.Stack.push(uint256.NewInt(1)) diff --git a/core/vm/errors.go b/core/vm/errors.go index 5ed286d5c3..97b6b94014 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -37,6 +37,7 @@ var ( ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + ErrInvalidAuthSignature = errors.New("invalid auth signature") ErrAuthorizedIsContract = errors.New("authcall with authorized as a contract address") ErrAuthorizedNotSet = errors.New("authcall without setting authorized") diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 616a79304a..0fd5d76cec 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -976,11 +976,11 @@ func TestOpMCopy(t *testing.T) { } } -func TestOpAuth(t *testing.T) { +func TestOpAuthHappyCase(t *testing.T) { var ( aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") // invoker contract = NewContract(AccountRef(aa), AccountRef(aa), big.NewInt(0), 0) - key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key, _ = crypto.GenerateKey() addr = crypto.PubkeyToAddress(key.PublicKey) statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) env = NewEVM(BlockContext{}, TxContext{}, statedb, params.AllDevChainProtocolChanges, Config{}) @@ -1014,6 +1014,86 @@ func TestOpAuth(t *testing.T) { require.Equal(t, addr, *scope.Authorized, "unexpected authorized address in scope") } +func TestOpAuthInvalidSignature(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") // invoker + contract = NewContract(AccountRef(aa), AccountRef(aa), big.NewInt(0), 0) + key, _ = crypto.GenerateKey() + addr = crypto.PubkeyToAddress(key.PublicKey) + key2, _ = crypto.GenerateKey() + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + env = NewEVM(BlockContext{}, TxContext{}, statedb, params.AllDevChainProtocolChanges, Config{}) + stack = newstack() + pc = uint64(0) + evmInterpreter = env.interpreter + commit = make([]byte, 32) + ) + + // Setup stack (with length = 97, offset = 0, and invoker address) + setupAuthStack(stack, 97, 0, addr) + + // Generate message as per EIP-3074 and sign it + msg := composeAuthMessage(params.AllDevChainProtocolChanges.ChainID, big.NewInt(0), aa, commit) + sig, err := signMessage(msg, key2) + require.NoError(t, err, "failed to sign message") + require.Equal(t, 65, len(sig.sig), "invalid signature length") + + // Setup memory and scope for the AUTH call + memory := setupAuthMemory(sig, commit) + scope := &ScopeContext{memory, stack, contract, nil} + + // Call AUTH + res, err := opAuth(&pc, evmInterpreter, scope) + require.ErrorIs(t, err, ErrInvalidAuthSignature, "unexpected error executing AUTH") + require.Equal(t, 0, len(res), "unexpected return value") + + // Check the stack for response and scope for authorized + actual := stack.pop() + require.Equal(t, uint64(0), actual.Uint64(), "unexpected value in stack") + require.Empty(t, scope.Authorized, "unexpected authorized address in scope") +} + +func TestOpAuthInvalidAuthority(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") // invoker + contract = NewContract(AccountRef(aa), AccountRef(aa), big.NewInt(0), 0) + key, _ = crypto.GenerateKey() + addr = crypto.PubkeyToAddress(key.PublicKey) + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + env = NewEVM(BlockContext{}, TxContext{}, statedb, params.AllDevChainProtocolChanges, Config{}) + stack = newstack() + pc = uint64(0) + evmInterpreter = env.interpreter + commit = make([]byte, 32) + ) + + // Setup stack (with length = 97, offset = 0, and invoker address) + setupAuthStack(stack, 97, 0, addr) + + // Assign a code to the authority address + statedb.SetCode(addr, []byte{0x01}) + + // Generate message as per EIP-3074 and sign it + msg := composeAuthMessage(params.AllDevChainProtocolChanges.ChainID, big.NewInt(0), aa, commit) + sig, err := signMessage(msg, key) + require.NoError(t, err, "failed to sign message") + require.Equal(t, 65, len(sig.sig), "invalid signature length") + + // Setup memory and scope for the AUTH call + memory := setupAuthMemory(sig, commit) + scope := &ScopeContext{memory, stack, contract, nil} + + // Call AUTH + res, err := opAuth(&pc, evmInterpreter, scope) + require.ErrorIs(t, err, ErrAuthorizedIsContract, "unexpected error executing AUTH") + require.Equal(t, 0, len(res), "unexpected return value") + + // Check the stack for response and scope for authorized + actual := stack.pop() + require.Equal(t, uint64(0), actual.Uint64(), "unexpected value in stack") + require.Empty(t, scope.Authorized, "unexpected authorized address in scope") +} + type Signature struct { sig []byte r *big.Int @@ -1031,11 +1111,9 @@ func signMessage(msg []byte, key *ecdsa.PrivateKey) (*Signature, error) { if len(sig) != crypto.SignatureLength { return nil, fmt.Errorf("invalid signature length") } - fmt.Println("sig", len(sig)) r := new(big.Int).SetBytes(sig[:32]) s := new(big.Int).SetBytes(sig[32:64]) v := big.NewInt(int64(sig[64])) - fmt.Println(v, r, s) return &Signature{sig, r, s, v}, nil } From 31570f8be11978ad765ea2cc5e5836f9e2db3b20 Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Mon, 29 Apr 2024 19:02:42 +0530 Subject: [PATCH 12/17] core/vm: refactor and simplify AUTH tests --- core/vm/instructions_test.go | 157 ++++++++++++----------------------- 1 file changed, 54 insertions(+), 103 deletions(-) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 0fd5d76cec..7a080d9dd8 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -976,25 +976,20 @@ func TestOpMCopy(t *testing.T) { } } -func TestOpAuthHappyCase(t *testing.T) { +func setupAuthTest(t *testing.T, authority, invoker common.Address, key *ecdsa.PrivateKey) (*EVM, *ScopeContext, *Stack) { var ( - aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") // invoker - contract = NewContract(AccountRef(aa), AccountRef(aa), big.NewInt(0), 0) - key, _ = crypto.GenerateKey() - addr = crypto.PubkeyToAddress(key.PublicKey) - statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - env = NewEVM(BlockContext{}, TxContext{}, statedb, params.AllDevChainProtocolChanges, Config{}) - stack = newstack() - pc = uint64(0) - evmInterpreter = env.interpreter - commit = make([]byte, 32) + commit = make([]byte, 32) + contract = NewContract(AccountRef(invoker), AccountRef(invoker), big.NewInt(0), 0) + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + env = NewEVM(BlockContext{}, TxContext{}, statedb, params.AllDevChainProtocolChanges, Config{}) + stack = newstack() ) - // Setup stack (with length = 97, offset = 0, and invoker address) - setupAuthStack(stack, 97, 0, addr) + // Setup stack (with length = 97, offset = 0, and authority address) + setupAuthStack(stack, 97, 0, authority) // Generate message as per EIP-3074 and sign it - msg := composeAuthMessage(params.AllDevChainProtocolChanges.ChainID, big.NewInt(0), aa, commit) + msg := composeAuthMessage(params.AllDevChainProtocolChanges.ChainID, big.NewInt(0), invoker, commit) sig, err := signMessage(msg, key) require.NoError(t, err, "failed to sign message") require.Equal(t, 65, len(sig.sig), "invalid signature length") @@ -1003,95 +998,7 @@ func TestOpAuthHappyCase(t *testing.T) { memory := setupAuthMemory(sig, commit) scope := &ScopeContext{memory, stack, contract, nil} - // Call AUTH - res, err := opAuth(&pc, evmInterpreter, scope) - require.NoError(t, err, "failed to execute AUTH") - require.Equal(t, 0, len(res), "unexpected return value") - - // Check the stack for response and scope for authorized - actual := stack.pop() - require.Equal(t, uint64(1), actual.Uint64(), "unexpected value in stack") - require.Equal(t, addr, *scope.Authorized, "unexpected authorized address in scope") -} - -func TestOpAuthInvalidSignature(t *testing.T) { - var ( - aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") // invoker - contract = NewContract(AccountRef(aa), AccountRef(aa), big.NewInt(0), 0) - key, _ = crypto.GenerateKey() - addr = crypto.PubkeyToAddress(key.PublicKey) - key2, _ = crypto.GenerateKey() - statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - env = NewEVM(BlockContext{}, TxContext{}, statedb, params.AllDevChainProtocolChanges, Config{}) - stack = newstack() - pc = uint64(0) - evmInterpreter = env.interpreter - commit = make([]byte, 32) - ) - - // Setup stack (with length = 97, offset = 0, and invoker address) - setupAuthStack(stack, 97, 0, addr) - - // Generate message as per EIP-3074 and sign it - msg := composeAuthMessage(params.AllDevChainProtocolChanges.ChainID, big.NewInt(0), aa, commit) - sig, err := signMessage(msg, key2) - require.NoError(t, err, "failed to sign message") - require.Equal(t, 65, len(sig.sig), "invalid signature length") - - // Setup memory and scope for the AUTH call - memory := setupAuthMemory(sig, commit) - scope := &ScopeContext{memory, stack, contract, nil} - - // Call AUTH - res, err := opAuth(&pc, evmInterpreter, scope) - require.ErrorIs(t, err, ErrInvalidAuthSignature, "unexpected error executing AUTH") - require.Equal(t, 0, len(res), "unexpected return value") - - // Check the stack for response and scope for authorized - actual := stack.pop() - require.Equal(t, uint64(0), actual.Uint64(), "unexpected value in stack") - require.Empty(t, scope.Authorized, "unexpected authorized address in scope") -} - -func TestOpAuthInvalidAuthority(t *testing.T) { - var ( - aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") // invoker - contract = NewContract(AccountRef(aa), AccountRef(aa), big.NewInt(0), 0) - key, _ = crypto.GenerateKey() - addr = crypto.PubkeyToAddress(key.PublicKey) - statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - env = NewEVM(BlockContext{}, TxContext{}, statedb, params.AllDevChainProtocolChanges, Config{}) - stack = newstack() - pc = uint64(0) - evmInterpreter = env.interpreter - commit = make([]byte, 32) - ) - - // Setup stack (with length = 97, offset = 0, and invoker address) - setupAuthStack(stack, 97, 0, addr) - - // Assign a code to the authority address - statedb.SetCode(addr, []byte{0x01}) - - // Generate message as per EIP-3074 and sign it - msg := composeAuthMessage(params.AllDevChainProtocolChanges.ChainID, big.NewInt(0), aa, commit) - sig, err := signMessage(msg, key) - require.NoError(t, err, "failed to sign message") - require.Equal(t, 65, len(sig.sig), "invalid signature length") - - // Setup memory and scope for the AUTH call - memory := setupAuthMemory(sig, commit) - scope := &ScopeContext{memory, stack, contract, nil} - - // Call AUTH - res, err := opAuth(&pc, evmInterpreter, scope) - require.ErrorIs(t, err, ErrAuthorizedIsContract, "unexpected error executing AUTH") - require.Equal(t, 0, len(res), "unexpected return value") - - // Check the stack for response and scope for authorized - actual := stack.pop() - require.Equal(t, uint64(0), actual.Uint64(), "unexpected value in stack") - require.Empty(t, scope.Authorized, "unexpected authorized address in scope") + return env, scope, stack } type Signature struct { @@ -1150,3 +1057,47 @@ func composeAuthMessage(chainId, nonce *big.Int, invokerAddress common.Address, msg = crypto.Keccak256(msg) return msg } + +func TestOpAuth(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") // invoker + key, _ = crypto.GenerateKey() + key2, _ = crypto.GenerateKey() + addr = crypto.PubkeyToAddress(key.PublicKey) + ) + + type testcase struct { + name string + key *ecdsa.PrivateKey + addcode bool + expectedResult int + expectedError error + expectedStack uint64 + expectedAuthorized *common.Address + } + for _, tt := range []testcase{ + {name: "happy case", key: key, addcode: false, expectedResult: 0, expectedError: nil, expectedStack: 1, expectedAuthorized: &addr}, + {name: "invalid signature", key: key2, addcode: false, expectedResult: 0, expectedError: ErrInvalidAuthSignature, expectedStack: 0, expectedAuthorized: nil}, + {name: "invalid authority", key: key, addcode: true, expectedResult: 0, expectedError: ErrAuthorizedIsContract, expectedStack: 0, expectedAuthorized: nil}, + } { + pc := uint64(0) + + // Setup for AUTH test + evm, scope, stack := setupAuthTest(t, addr, aa, tt.key) + + if tt.addcode { + // Set code in authority + evm.StateDB.SetCode(addr, []byte{0x01}) + } + + // Call AUTH + res, err := opAuth(&pc, evm.interpreter, scope) + require.Equal(t, tt.expectedError, err, "unexpected error executing AUTH") + require.Equal(t, tt.expectedResult, len(res), "unexpected return value") + + // Check the stack for response and scope for authorized + actual := stack.pop() + require.Equal(t, tt.expectedStack, actual.Uint64(), "unexpected value in stack") + require.Equal(t, tt.expectedAuthorized, scope.Authorized, "unexpected authorized address in scope") + } +} From 473569ecadbf88557a6e547aaada798f69bf835a Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Mon, 29 Apr 2024 19:11:50 +0530 Subject: [PATCH 13/17] core/vm: add invalid signature tests --- core/vm/instructions_test.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 7a080d9dd8..e42d404485 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -20,6 +20,7 @@ import ( "bytes" "crypto/ecdsa" "encoding/json" + "errors" "fmt" "math/big" "os" @@ -1070,21 +1071,29 @@ func TestOpAuth(t *testing.T) { name string key *ecdsa.PrivateKey addcode bool + changeSignature bool expectedResult int expectedError error expectedStack uint64 expectedAuthorized *common.Address } for _, tt := range []testcase{ - {name: "happy case", key: key, addcode: false, expectedResult: 0, expectedError: nil, expectedStack: 1, expectedAuthorized: &addr}, - {name: "invalid signature", key: key2, addcode: false, expectedResult: 0, expectedError: ErrInvalidAuthSignature, expectedStack: 0, expectedAuthorized: nil}, - {name: "invalid authority", key: key, addcode: true, expectedResult: 0, expectedError: ErrAuthorizedIsContract, expectedStack: 0, expectedAuthorized: nil}, + {name: "happy case", key: key, addcode: false, changeSignature: false, expectedResult: 0, expectedError: nil, expectedStack: 1, expectedAuthorized: &addr}, + {name: "invalid key", key: key2, addcode: false, changeSignature: false, expectedResult: 0, expectedError: ErrInvalidAuthSignature, expectedStack: 0, expectedAuthorized: nil}, + {name: "invalid signature", key: key, addcode: false, changeSignature: true, expectedResult: 0, expectedError: errors.New("recovery failed"), expectedStack: 0, expectedAuthorized: nil}, + {name: "invalid authority", key: key, addcode: true, changeSignature: false, expectedResult: 0, expectedError: ErrAuthorizedIsContract, expectedStack: 0, expectedAuthorized: nil}, } { pc := uint64(0) // Setup for AUTH test evm, scope, stack := setupAuthTest(t, addr, aa, tt.key) + if tt.changeSignature { + // Change the `s` portion of signature to make it invalid + temp := make([]byte, 32) + scope.Memory.Set(33, 32, temp) // [33:65] + } + if tt.addcode { // Set code in authority evm.StateDB.SetCode(addr, []byte{0x01}) From bd8fe626046e09a43fa9ce29d01dff749329e0ea Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Tue, 30 Apr 2024 15:55:12 +0530 Subject: [PATCH 14/17] core/vm: add explanations in test, fix authcall --- core/blockchain_test.go | 57 ++++++++++++++++++------------------ core/vm/eips.go | 16 +++++----- core/vm/evm.go | 4 ++- core/vm/instructions_test.go | 8 ++--- 4 files changed, 44 insertions(+), 41 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index bd46e2f061..a4e023936a 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -5104,7 +5104,9 @@ func TestEIP3651(t *testing.T) { func TestEIP3074(t *testing.T) { var ( - aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + // Invoker contract (which uses `auth` and `authcall` and calls destination contract) + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + // Destination contract which will be ultimately called bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") engine = beacon.NewFaker() @@ -5117,18 +5119,16 @@ func TestEIP3074(t *testing.T) { Config: &config, Alloc: GenesisAlloc{ addr: {Balance: funds}, - // The address 0xAAAA sloads 0x00 and 0x01 aa: { Code: nil, // added below Nonce: 0, Balance: big.NewInt(0), }, - // The address 0xBBBB calls 0xAAAA bb: { Code: []byte{ - byte(vm.CALLER), - byte(vm.PUSH0), - byte(vm.SSTORE), + byte(vm.CALLER), // pushes the caller to stack [caller] + byte(vm.PUSH0), // pushes 0 to stack [caller, 0] + byte(vm.SSTORE), // stores caller in slot 0 (to verify caller later) byte(vm.STOP), }, Nonce: 0, @@ -5139,35 +5139,34 @@ func TestEIP3074(t *testing.T) { ) invoker := []byte{ - // copy sig to memory - byte(vm.CALLDATASIZE), - byte(vm.PUSH0), - byte(vm.PUSH0), - byte(vm.CALLDATACOPY), + // for `auth`, signature needs to be in memory (which will be passed via calldata) + // copy the signature from calldata to memory + byte(vm.CALLDATASIZE), // pushes calldata size to stack [len(calldata)] (size) + byte(vm.PUSH0), // pushes 0 to stack [len(calldata), 0] (offset) + byte(vm.PUSH0), // pushes 0 to stack [len(calldata), 0, 0] (destOffset) + byte(vm.CALLDATACOPY), // copy calldata to memory (based on destOffset, offset, size above) // set up auth - byte(vm.CALLDATASIZE), - byte(vm.PUSH0), + byte(vm.CALLDATASIZE), // pushes calldata size to stack [len(calldata)] (length) + byte(vm.PUSH0), // pushes 0 to stack [len(calldata), 0] (offset) } - // push authority to stack + // push authority to stack [len(calldata), 0, authority] (authority) invoker = append(invoker, append([]byte{byte(vm.PUSH20)}, addr.Bytes()...)...) invoker = append(invoker, []byte{ - - byte(vm.AUTH), - byte(vm.POP), + byte(vm.AUTH), // call auth (based on authority, offset, length above) + byte(vm.POP), // pop result of auth // execute authcall - byte(vm.PUSH0), // out size - byte(vm.DUP1), // out offset - byte(vm.DUP1), // out insize - byte(vm.DUP1), // in offset - byte(vm.DUP1), // valueExt - byte(vm.DUP1), // value - byte(vm.PUSH2), // address - byte(0xbb), - byte(0xbb), - byte(vm.GAS), // gas - byte(vm.AUTHCALL), + byte(vm.PUSH0), // [0] (retLength) + byte(vm.DUP1), // [0, 0] (retOffset) + byte(vm.DUP1), // [0, 0, 0] (argsLength) + byte(vm.DUP1), // [0, 0, 0, 0] (argsOffset) + byte(vm.DUP1), // [0, 0, 0, 0, 0] (value) + byte(vm.PUSH2), // push the destination contract address + byte(0xbb), // [0, 0, 0, 0, 0, 0xbb] + byte(0xbb), // [0, 0, 0, 0, 0, 0xbbbb] (addr) + byte(vm.GAS), // [0, 0, 0, 0, 0, 0xbbbb, gas] (gas) + byte(vm.AUTHCALL), // call authcall (based on 7 values above) byte(vm.STOP), }..., ) @@ -5198,6 +5197,8 @@ func TestEIP3074(t *testing.T) { sig, _ := crypto.Sign(msg, key) sig = append([]byte{sig[len(sig)-1]}, sig[0:len(sig)-1]...) + + // Send a tx to the invoker contract (which will perform auth and authcall) txdata := &types.DynamicFeeTx{ ChainID: gspec.Config.ChainID, Nonce: 0, diff --git a/core/vm/eips.go b/core/vm/eips.go index d711d8ece2..3b2c3dd4e6 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -349,8 +349,8 @@ func enable3074(jt *JumpTable) { execute: opAuthCall, constantGas: params.CallGasEIP150, dynamicGas: gasCallEIP2929, - minStack: minStack(8, 1), - maxStack: maxStack(8, 1), + minStack: minStack(7, 1), + maxStack: maxStack(7, 1), memorySize: memoryCall, } } @@ -417,12 +417,12 @@ func opAuthCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ return nil, ErrAuthorizedNotSet } var ( - stack = scope.Stack - temp = stack.pop() - gas = interpreter.evm.callGasTemp - addr, value, _, inOffset, inSize, retOffset, retSize = stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() - toAddr = common.Address(addr.Bytes20()) - args = scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + stack = scope.Stack + temp = stack.pop() + gas = interpreter.evm.callGasTemp + addr, value, inOffset, inSize, retOffset, retSize = stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr = common.Address(addr.Bytes20()) + args = scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) ) if interpreter.readOnly && !value.IsZero() { diff --git a/core/vm/evm.go b/core/vm/evm.go index f596dcc85c..7042f707cd 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -428,7 +428,9 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte return ret, gas, err } -// AuthCall mimic Call except it sets the caller to the Authorized address in Scope. +// AuthCall mimic Call except it sets the caller to the Authorized address in Scope +// and invoker and addr represents the invoker contract and destination contract +// being called respectively. func (evm *EVM) AuthCall(invoker ContractRef, caller, addr common.Address, input []byte, gas uint64, value *big.Int, interruptCtx context.Context) (ret []byte, leftOverGas uint64, err error) { // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index e42d404485..d90766bc4b 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -1101,12 +1101,12 @@ func TestOpAuth(t *testing.T) { // Call AUTH res, err := opAuth(&pc, evm.interpreter, scope) - require.Equal(t, tt.expectedError, err, "unexpected error executing AUTH") - require.Equal(t, tt.expectedResult, len(res), "unexpected return value") + require.Equal(t, tt.expectedError, err, tt.name, "unexpected error executing AUTH") + require.Equal(t, tt.expectedResult, len(res), tt.name, "unexpected return value") // Check the stack for response and scope for authorized actual := stack.pop() - require.Equal(t, tt.expectedStack, actual.Uint64(), "unexpected value in stack") - require.Equal(t, tt.expectedAuthorized, scope.Authorized, "unexpected authorized address in scope") + require.Equal(t, tt.expectedStack, actual.Uint64(), tt.name, "unexpected value in stack") + require.Equal(t, tt.expectedAuthorized, scope.Authorized, tt.name, "unexpected authorized address in scope") } } From a69ed66b1730ba5107cac8d5dcf6fa3bf40efa16 Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Wed, 1 May 2024 19:25:44 +0530 Subject: [PATCH 15/17] core/vm: deduct value from authorized --- core/vm/evm.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 7042f707cd..8893cc4d27 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -437,7 +437,7 @@ func (evm *EVM) AuthCall(invoker ContractRef, caller, addr common.Address, input return nil, gas, ErrDepth } // Fail if we're trying to transfer more than the available balance - if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, invoker.Address(), value) { + if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller, value) { return nil, gas, ErrInsufficientBalance } snapshot := evm.StateDB.Snapshot() @@ -460,7 +460,7 @@ func (evm *EVM) AuthCall(invoker ContractRef, caller, addr common.Address, input } evm.StateDB.CreateAccount(addr) } - evm.Context.Transfer(evm.StateDB, invoker.Address(), addr, value) + evm.Context.Transfer(evm.StateDB, caller, addr, value) // Capture the tracer start/end events in debug mode if debug { From b9119b76567062c09aff52b924caab4c1837aebe Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Wed, 1 May 2024 19:25:54 +0530 Subject: [PATCH 16/17] core/vm: add authcall tests --- core/vm/instructions_test.go | 117 +++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index d90766bc4b..a7c61df681 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -1110,3 +1110,120 @@ func TestOpAuth(t *testing.T) { require.Equal(t, tt.expectedAuthorized, scope.Authorized, tt.name, "unexpected authorized address in scope") } } + +// CanTransferTest mimics CanTransfer from core/evm.go +func CanTransferTest(db StateDB, addr common.Address, amount *big.Int) bool { + return db.GetBalance(addr).Cmp(amount) >= 0 +} + +// TransferTest mimics Transfer from core/evm.go +func TransferTest(db StateDB, sender, recipient common.Address, amount *big.Int) { + db.SubBalance(sender, amount) + db.AddBalance(recipient, amount) +} + +func setupAuthCallTest(authority *common.Address, dest common.Address, value uint64) (*EVM, *ScopeContext, *Stack) { + var ( + // Invoker contract + cc = common.HexToAddress("0x000000000000000000000000000000000000cccc") + invoker = NewContract(AccountRef(cc), AccountRef(cc), big.NewInt(0), 0) + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockContext = BlockContext{ + CanTransfer: CanTransferTest, + Transfer: TransferTest, + BlockNumber: big.NewInt(0), + } + env = NewEVM(blockContext, TxContext{}, statedb, params.AllDevChainProtocolChanges, Config{}) + stack = newstack() + ) + + // Create destination contract and assign the code to address `bb` + destCode := []byte{ + byte(CALLER), // pushes the caller to stack [caller] + byte(PUSH0), // pushes 0 to stack [caller, 0] + byte(SSTORE), // stores caller in slot 0 (to verify caller later) + byte(STOP), + } + statedb.CreateAccount(dest) + statedb.SetCode(dest, destCode) + + // Prepare stack + stack.push(uint256.NewInt(0)) // retLength + stack.push(uint256.NewInt(0)) // retOffset + stack.push(uint256.NewInt(0)) // argsLength + stack.push(uint256.NewInt(0)) // argsOffset + stack.push(uint256.NewInt(value)) // value + stack.push(uint256.NewInt(0).SetBytes(dest.Bytes())) // addr + stack.push(uint256.NewInt(0)) // gas + + memory := NewMemory() + memory.Resize(0) + + // Used by SSTORE + statedb.AddAddressToAccessList(dest) + + scope := &ScopeContext{memory, stack, invoker, authority} + env.interpreter.evm.callGasTemp = 10000000 + + return env, scope, stack +} + +func TestOpAuthCall(t *testing.T) { + var ( + authority = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + dest = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + ) + + type testcase struct { + name string + setAuthority bool + value uint64 + expectedResultLength int + expectedError error + expectedStack uint64 + expectedStackLength int + expectedAuthority []byte + } + for _, tt := range []testcase{ + {name: "happy case", setAuthority: true, value: 0, expectedResultLength: 0, expectedError: nil, expectedStack: 1, expectedStackLength: 1, expectedAuthority: common.LeftPadBytes(authority.Bytes(), 32)}, + {name: "authorized not set", setAuthority: false, value: 0, expectedResultLength: 0, expectedError: ErrAuthorizedNotSet, expectedStack: 0, expectedStackLength: 1, expectedAuthority: common.LeftPadBytes(common.Hash{}.Bytes(), 32)}, + {name: "consume value", setAuthority: true, value: 100, expectedResultLength: 0, expectedError: nil, expectedStack: 1, expectedStackLength: 1, expectedAuthority: common.LeftPadBytes(authority.Bytes(), 32)}, + } { + var ( + env *EVM + scope *ScopeContext + stack *Stack + ) + if tt.setAuthority { + env, scope, stack = setupAuthCallTest(&authority, dest, tt.value) + } else { + env, scope, stack = setupAuthCallTest(nil, dest, tt.value) + } + + // Add some funds to authority if required + env.StateDB.AddBalance(authority, big.NewInt(0).SetUint64(tt.value)) + + // Execute AUTHCALL + pc := uint64(0) + res, err := opAuthCall(&pc, env.interpreter, scope) + require.Equal(t, tt.expectedResultLength, len(res), tt.name, "unexpected return value executing AUTHCALL") + require.Equal(t, tt.expectedError, err, tt.name, "unexpected error executing AUTHCALL") + + if err != ErrAuthorizedNotSet { + // Check stack + require.Equal(t, tt.expectedStackLength, stack.len(), tt.name, "unexpected stack length after AUTHCALL") + actual := stack.pop() + require.Equal(t, tt.expectedStack, actual.Uint64(), tt.name, "unexpected stack length after AUTHCALL") + + // Check if balance transfer has happened or not + authorityBalance := env.StateDB.GetBalance(authority) + destBalance := env.StateDB.GetBalance(dest) + require.Equal(t, uint64(0), authorityBalance.Uint64(), tt.name, "unexpected balance in authority after AUTHCALL") + require.Equal(t, tt.value, destBalance.Uint64(), tt.name, "unexpected balance in desination contract after AUTHCALL") + } + + // Check if authority has been set in dest contract + got := env.StateDB.GetState(dest, common.Hash{}) + require.Equal(t, tt.expectedAuthority, got.Bytes(), tt.name, "incorrect sender in AUTHCALL") + } +} From 919387dfee5aae726279bf8824494dac2601f619 Mon Sep 17 00:00:00 2001 From: Manav Darji Date: Mon, 6 May 2024 18:28:03 +0530 Subject: [PATCH 17/17] core/vm: align with latest upstream, fix tests --- core/vm/eips.go | 47 ++++++++++++++++-------------------- core/vm/errors.go | 2 -- core/vm/instructions_test.go | 4 +-- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 3b2c3dd4e6..b048e3eaa0 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -18,7 +18,6 @@ package vm import ( "fmt" - "math/big" "sort" "github.com/ethereum/go-ethereum/common" @@ -355,56 +354,52 @@ func enable3074(jt *JumpTable) { } } +// opAuth implements the EIP-3074 AUTH instruction. func opAuth(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - tmp = scope.Stack.pop() - authority = common.Address(tmp.Bytes20()) - offset = scope.Stack.pop() - length = scope.Stack.pop() - data = scope.Memory.GetPtr(int64(offset.Uint64()), int64(length.Uint64())) - authorityNonce = big.NewInt(int64(interpreter.evm.StateDB.GetNonce(authority))) - sig = make([]byte, 65) - commit common.Hash + tmp = scope.Stack.pop() + authority = common.Address(tmp.Bytes20()) + offset = scope.Stack.pop() + length = scope.Stack.pop() + data = scope.Memory.GetPtr(int64(offset.Uint64()), int64(length.Uint64())) + sig = make([]byte, 65) + commit common.Hash ) copy(sig, data) if len(data) > 65 { copy(commit[:], data[65:]) } - // Verify if the provided authority address isn't a contract - if code := interpreter.evm.StateDB.GetCode(authority); len(code) != 0 { + // If the desired authority has code, the operation must be considered + // unsuccessful. + statedb := interpreter.evm.StateDB + if statedb.GetCodeSize(authority) != 0 { scope.Authorized = nil scope.Stack.push(uint256.NewInt(0)) - return nil, ErrAuthorizedIsContract + return nil, nil } // Build original auth message. msg := []byte{params.AuthMagic} msg = append(msg, common.LeftPadBytes(interpreter.evm.chainConfig.ChainID.Bytes(), 32)...) - msg = append(msg, common.LeftPadBytes(authorityNonce.Bytes(), 32)...) + msg = append(msg, common.LeftPadBytes(uint256.NewInt(statedb.GetNonce(authority)).Bytes(), 32)...) msg = append(msg, common.LeftPadBytes(scope.Contract.Address().Bytes(), 32)...) msg = append(msg, commit.Bytes()...) msg = crypto.Keccak256(msg) // Verify signature against provided address. - sig = append(sig[1:], sig[0]) + sig = append(sig[1:], sig[0]) // send y parity to back pub, err := crypto.Ecrecover(msg, sig) - if err != nil { - scope.Authorized = nil - scope.Stack.push(uint256.NewInt(0)) - return nil, err - } var recovered common.Address - copy(recovered[:], crypto.Keccak256(pub[1:])[12:]) - - fmt.Println(recovered) - fmt.Println(authority) + if err == nil { + copy(recovered[:], crypto.Keccak256(pub[1:])[12:]) + } - if recovered != authority { + if err != nil || recovered != authority { scope.Authorized = nil scope.Stack.push(uint256.NewInt(0)) - return nil, ErrInvalidAuthSignature + return nil, err } scope.Stack.push(uint256.NewInt(1)) @@ -412,6 +407,7 @@ func opAuth(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt return nil, nil } +// opAuthCall implements the EIP-3074 AUTHCALL instruction. func opAuthCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { if scope.Authorized == nil { return nil, ErrAuthorizedNotSet @@ -449,5 +445,4 @@ func opAuthCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ interpreter.returnData = ret return ret, nil - } diff --git a/core/vm/errors.go b/core/vm/errors.go index 97b6b94014..c1e6174b74 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -37,8 +37,6 @@ var ( ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") - ErrInvalidAuthSignature = errors.New("invalid auth signature") - ErrAuthorizedIsContract = errors.New("authcall with authorized as a contract address") ErrAuthorizedNotSet = errors.New("authcall without setting authorized") // errStopToken is an internal token indicating interpreter loop termination, diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index a7c61df681..922f3b1b61 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -1079,9 +1079,9 @@ func TestOpAuth(t *testing.T) { } for _, tt := range []testcase{ {name: "happy case", key: key, addcode: false, changeSignature: false, expectedResult: 0, expectedError: nil, expectedStack: 1, expectedAuthorized: &addr}, - {name: "invalid key", key: key2, addcode: false, changeSignature: false, expectedResult: 0, expectedError: ErrInvalidAuthSignature, expectedStack: 0, expectedAuthorized: nil}, + {name: "invalid key", key: key2, addcode: false, changeSignature: false, expectedResult: 0, expectedError: nil, expectedStack: 0, expectedAuthorized: nil}, {name: "invalid signature", key: key, addcode: false, changeSignature: true, expectedResult: 0, expectedError: errors.New("recovery failed"), expectedStack: 0, expectedAuthorized: nil}, - {name: "invalid authority", key: key, addcode: true, changeSignature: false, expectedResult: 0, expectedError: ErrAuthorizedIsContract, expectedStack: 0, expectedAuthorized: nil}, + {name: "invalid authority", key: key, addcode: true, changeSignature: false, expectedResult: 0, expectedError: nil, expectedStack: 0, expectedAuthorized: nil}, } { pc := uint64(0)