From bd977979c1bbcfb2c6c37d3892d6698365e1dae3 Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Wed, 10 Mar 2021 22:57:40 +0100 Subject: [PATCH 1/4] EIP-3074: split into AUTHORIZE and AUTHORIZEDCALL --- core/vm/eips.go | 108 ++++++++++++++++++++++------------------- core/vm/errors.go | 1 + core/vm/interpreter.go | 1 + core/vm/opcodes.go | 33 +++++++------ 4 files changed, 78 insertions(+), 65 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 84e1ddf16d26..6c5c68afbea5 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -149,34 +149,34 @@ func enable2929(jt *JumpTable) { } func enable3074(jt *JumpTable) { - jt[CALLFROM] = &operation{ - execute: opCallFrom, - constantGas: WarmStorageReadCostEIP2929 + params.EcrecoverGas, - dynamicGas: gasCallEIP2929, - minStack: minStack(12, 2), - maxStack: maxStack(12, 2), - memorySize: memoryCall, + jt[AUTHORIZE] = &operation{ + execute: opAuthorize, + constantGas: params.EcrecoverGas, + minStack: minStack(4, 1), + maxStack: minStack(4, 1), + } + + jt[AUTHORIZEDCALL] = &operation{ + execute: opAuthorizedCall, + constantGas: jt[CALL].constantGas, + dynamicGas: jt[CALL].dynamicGas, + minStack: minStack(7, 1), + maxStack: minStack(7, 1), + memorySize: jt[CALL].memorySize, returns: true, } } -func opCallFrom(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opAuthorize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { stack := callContext.stack - // Pop gas. The actual gas in interpreter.evm.callGasTemp. - // We can use this as a temporary value - temp := stack.pop() - gas := interpreter.evm.callGasTemp - // Pop other call parameters. - addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() - commit, sponsee, sigV, sigR, sigS := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() - - valid, success := false, false - var ret []byte = nil + commit, sigV, sigR, sigS := stack.pop(), stack.pop(), stack.pop(), stack.pop() r := sigR.ToBig() s := sigS.ToBig() v := uint8(sigV[0]) - 27 + callContext.sponsee = nil + // tighter sig s values input homestead only apply to tx sigs if val, of := sigV.Uint64WithOverflow(); !of && val <= 0xff && crypto.ValidateSignatureValues(v, r, s, false) { input := make([]byte, 65) @@ -195,47 +195,55 @@ func opCallFrom(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ( // the first byte of pubkey is bitcoin heritage if err == nil { from := common.BytesToAddress(crypto.Keccak256(pubKey[1:])[12:]) - if sponsee.IsZero() || sponsee.Bytes20() == from { - valid = true - - toAddr := common.Address(addr.Bytes20()) - // Get the arguments from the memory. - args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - - var bigVal = big0 - //TODO: use uint256.Int instead of converting with toBig() - // By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls), - // but it would make more sense to extend the usage of uint256.Int - if !value.IsZero() { - gas += params.CallStipend - bigVal = value.ToBig() - } - - ret, returnGas, err := interpreter.evm.Call(callContext.contract, from, toAddr, args, gas, bigVal) - - success = err == nil - if success || err == ErrExecutionReverted { - callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) - } - callContext.contract.Gas += returnGas - } + callContext.sponsee = &from } } - if valid { - addr.SetOne() + if callContext.sponsee != nil { + commit.SetBytes20(callContext.sponsee.Bytes()) } else { - addr.Clear() - callContext.contract.Gas += gas + commit.Clear() } - stack.push(&addr) + stack.push(&commit) + return nil, nil +} - if success { - temp.SetOne() - } else { +func opAuthorizedCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + stack := callContext.stack + // Pop gas. The actual gas in interpreter.evm.callGasTemp. + // We can use this as a temporary value + temp := stack.pop() + gas := interpreter.evm.callGasTemp + // Pop other call parameters. + addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) + // Get the arguments from the memory. + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + + var bigVal = big0 + //TODO: use uint256.Int instead of converting with toBig() + // By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls), + // but it would make more sense to extend the usage of uint256.Int + if !value.IsZero() { + gas += params.CallStipend + bigVal = value.ToBig() + } + + ret, returnGas, err := []byte(nil), gas, ErrNoAuthorizedSponsee + if callContext.sponsee != nil { + ret, returnGas, err = interpreter.evm.Call(callContext.contract, *callContext.sponsee, toAddr, args, gas, bigVal) + } + + if err != nil { temp.Clear() + } else { + temp.SetOne() } stack.push(&temp) + if err == nil || err == ErrExecutionReverted { + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + callContext.contract.Gas += returnGas return ret, nil } diff --git a/core/vm/errors.go b/core/vm/errors.go index f6b156a02e38..406a2e7ad35d 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -39,6 +39,7 @@ var ( ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidRetsub = errors.New("invalid retsub") ErrReturnStackExceeded = errors.New("return stack limit reached") + ErrNoAuthorizedSponsee = errors.New("authorized sponsee not set") ) // ErrStackUnderflow wraps an evm error when the items on the stack less diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 55cb1f2dfe4d..c83e2695aea9 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -68,6 +68,7 @@ type callCtx struct { memory *Memory stack *Stack contract *Contract + sponsee *common.Address } // keccakState wraps sha3.state. In addition to the usual hash methods, it also supports diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 9bb480234ece..eedb475a5ef7 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -213,10 +213,11 @@ const ( RETURN DELEGATECALL CREATE2 - CALLFROM OpCode = 0xf9 - STATICCALL OpCode = 0xfa - REVERT OpCode = 0xfd - SELFDESTRUCT OpCode = 0xff + AUTHORIZE OpCode = 0xf8 + AUTHORIZEDCALL OpCode = 0xf9 + STATICCALL OpCode = 0xfa + REVERT OpCode = 0xfd + SELFDESTRUCT OpCode = 0xff ) // Since the opcodes aren't all in order we can't use a regular slice. @@ -372,16 +373,17 @@ var opCodeToString = map[OpCode]string{ LOG4: "LOG4", // 0xf0 range. - CREATE: "CREATE", - CALL: "CALL", - CALLFROM: "CALLFROM", - RETURN: "RETURN", - CALLCODE: "CALLCODE", - DELEGATECALL: "DELEGATECALL", - CREATE2: "CREATE2", - STATICCALL: "STATICCALL", - REVERT: "REVERT", - SELFDESTRUCT: "SELFDESTRUCT", + CREATE: "CREATE", + CALL: "CALL", + AUTHORIZE: "AUTHORIZE", + AUTHORIZEDCALL: "AUTHORIZEDCALL", + RETURN: "RETURN", + CALLCODE: "CALLCODE", + DELEGATECALL: "DELEGATECALL", + CREATE2: "CREATE2", + STATICCALL: "STATICCALL", + REVERT: "REVERT", + SELFDESTRUCT: "SELFDESTRUCT", PUSH: "PUSH", DUP: "DUP", @@ -535,7 +537,8 @@ var stringToOp = map[string]OpCode{ "CREATE": CREATE, "CREATE2": CREATE2, "CALL": CALL, - "CALLFROM": CALLFROM, + "AUTHORIZE": AUTHORIZE, + "AUTHORIZEDCALL": AUTHORIZEDCALL, "RETURN": RETURN, "CALLCODE": CALLCODE, "REVERT": REVERT, From 7ce82f7d38fd186c881b32b98507319e79237884 Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Thu, 11 Mar 2021 11:37:10 +0100 Subject: [PATCH 2/4] EIP-3074: ban self-sponsoring --- core/vm/eips.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 6c5c68afbea5..e01aad837d49 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -195,7 +195,9 @@ func opAuthorize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) // the first byte of pubkey is bitcoin heritage if err == nil { from := common.BytesToAddress(crypto.Keccak256(pubKey[1:])[12:]) - callContext.sponsee = &from + if from != interpreter.evm.Origin { + callContext.sponsee = &from + } } } From 64ce68da0d23ea0a43643c0a93bf9aaef6b5478d Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Thu, 11 Mar 2021 15:07:23 +0100 Subject: [PATCH 3/4] EIP-3074: bugfix --- core/vm/eips.go | 4 ++-- params/config.go | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index e01aad837d49..13ec5431f2d5 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -153,7 +153,7 @@ func enable3074(jt *JumpTable) { execute: opAuthorize, constantGas: params.EcrecoverGas, minStack: minStack(4, 1), - maxStack: minStack(4, 1), + maxStack: maxStack(4, 1), } jt[AUTHORIZEDCALL] = &operation{ @@ -161,7 +161,7 @@ func enable3074(jt *JumpTable) { constantGas: jt[CALL].constantGas, dynamicGas: jt[CALL].dynamicGas, minStack: minStack(7, 1), - maxStack: minStack(7, 1), + maxStack: maxStack(7, 1), memorySize: jt[CALL].memorySize, returns: true, } diff --git a/params/config.go b/params/config.go index 6da3e55d4362..7a330b741f86 100644 --- a/params/config.go +++ b/params/config.go @@ -365,7 +365,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, YOLO v3: %v, Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, YOLO v3: %v, Quilt: %v, Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -380,6 +380,7 @@ func (c *ChainConfig) String() string { c.MuirGlacierBlock, c.BerlinBlock, c.YoloV3Block, + c.QuiltBlock, engine, ) } From 5144a99912c1b2d15c505cc857de756672ee85a0 Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Sat, 13 Mar 2021 01:29:25 +0100 Subject: [PATCH 4/4] EIP-3074: update to current spec --- core/vm/eips.go | 38 +++++++++++++++++++------------------- core/vm/errors.go | 2 +- core/vm/interpreter.go | 8 ++++---- core/vm/opcodes.go | 36 ++++++++++++++++++------------------ 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 13ec5431f2d5..8b14b50fe3ec 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -149,15 +149,15 @@ func enable2929(jt *JumpTable) { } func enable3074(jt *JumpTable) { - jt[AUTHORIZE] = &operation{ - execute: opAuthorize, + jt[AUTH] = &operation{ + execute: opAuth, constantGas: params.EcrecoverGas, minStack: minStack(4, 1), maxStack: maxStack(4, 1), } - jt[AUTHORIZEDCALL] = &operation{ - execute: opAuthorizedCall, + jt[AUTHCALL] = &operation{ + execute: opAuthCall, constantGas: jt[CALL].constantGas, dynamicGas: jt[CALL].dynamicGas, minStack: minStack(7, 1), @@ -167,18 +167,17 @@ func enable3074(jt *JumpTable) { } } -func opAuthorize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opAuth(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { stack := callContext.stack - commit, sigV, sigR, sigS := stack.pop(), stack.pop(), stack.pop(), stack.pop() + commit, sigYParity, sigR, sigS := stack.pop(), stack.pop(), stack.pop(), stack.pop() r := sigR.ToBig() s := sigS.ToBig() - v := uint8(sigV[0]) - 27 + yParity := uint8(sigYParity[0]) - callContext.sponsee = nil + callContext.authorizedAccount = nil - // tighter sig s values input homestead only apply to tx sigs - if val, of := sigV.Uint64WithOverflow(); !of && val <= 0xff && crypto.ValidateSignatureValues(v, r, s, false) { + if val, of := sigYParity.Uint64WithOverflow(); !of && val <= 0xff && crypto.ValidateSignatureValues(yParity, r, s, true) { input := make([]byte, 65) input[0] = 0x03 copy(input[13:33], callContext.contract.Address().Bytes()) @@ -188,7 +187,7 @@ func opAuthorize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) sig := make([]byte, 65) sigR.WriteToSlice(sig[0:32]) sigS.WriteToSlice(sig[32:64]) - sig[64] = v + sig[64] = yParity // v needs to be at the end for libsecp256k1 pubKey, err := crypto.Ecrecover(hash[:], sig) // make sure the public key is a valid one @@ -196,13 +195,13 @@ func opAuthorize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) if err == nil { from := common.BytesToAddress(crypto.Keccak256(pubKey[1:])[12:]) if from != interpreter.evm.Origin { - callContext.sponsee = &from + callContext.authorizedAccount = &from } } } - if callContext.sponsee != nil { - commit.SetBytes20(callContext.sponsee.Bytes()) + if callContext.authorizedAccount != nil { + commit.SetBytes20(callContext.authorizedAccount.Bytes()) } else { commit.Clear() } @@ -210,7 +209,11 @@ func opAuthorize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) return nil, nil } -func opAuthorizedCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opAuthCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + if callContext.authorizedAccount == nil { + return nil, ErrNoAuthorizedAccount + } + stack := callContext.stack // Pop gas. The actual gas in interpreter.evm.callGasTemp. // We can use this as a temporary value @@ -231,10 +234,7 @@ func opAuthorizedCall(pc *uint64, interpreter *EVMInterpreter, callContext *call bigVal = value.ToBig() } - ret, returnGas, err := []byte(nil), gas, ErrNoAuthorizedSponsee - if callContext.sponsee != nil { - ret, returnGas, err = interpreter.evm.Call(callContext.contract, *callContext.sponsee, toAddr, args, gas, bigVal) - } + ret, returnGas, err := interpreter.evm.Call(callContext.contract, *callContext.authorizedAccount, toAddr, args, gas, bigVal) if err != nil { temp.Clear() diff --git a/core/vm/errors.go b/core/vm/errors.go index 406a2e7ad35d..7c3321518028 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -39,7 +39,7 @@ var ( ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidRetsub = errors.New("invalid retsub") ErrReturnStackExceeded = errors.New("return stack limit reached") - ErrNoAuthorizedSponsee = errors.New("authorized sponsee not set") + ErrNoAuthorizedAccount = errors.New("authorized account not set") ) // ErrStackUnderflow wraps an evm error when the items on the stack less diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index c83e2695aea9..e5a0b3a608c7 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -65,10 +65,10 @@ type Interpreter interface { // callCtx contains the things that are per-call, such as stack and memory, // but not transients like pc and gas type callCtx struct { - memory *Memory - stack *Stack - contract *Contract - sponsee *common.Address + memory *Memory + stack *Stack + contract *Contract + authorizedAccount *common.Address } // keccakState wraps sha3.state. In addition to the usual hash methods, it also supports diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index eedb475a5ef7..c9417d3a1ad7 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -213,11 +213,11 @@ const ( RETURN DELEGATECALL CREATE2 - AUTHORIZE OpCode = 0xf8 - AUTHORIZEDCALL OpCode = 0xf9 - STATICCALL OpCode = 0xfa - REVERT OpCode = 0xfd - SELFDESTRUCT OpCode = 0xff + AUTH + AUTHCALL + STATICCALL OpCode = 0xfa + REVERT OpCode = 0xfd + SELFDESTRUCT OpCode = 0xff ) // Since the opcodes aren't all in order we can't use a regular slice. @@ -373,17 +373,17 @@ var opCodeToString = map[OpCode]string{ LOG4: "LOG4", // 0xf0 range. - CREATE: "CREATE", - CALL: "CALL", - AUTHORIZE: "AUTHORIZE", - AUTHORIZEDCALL: "AUTHORIZEDCALL", - RETURN: "RETURN", - CALLCODE: "CALLCODE", - DELEGATECALL: "DELEGATECALL", - CREATE2: "CREATE2", - STATICCALL: "STATICCALL", - REVERT: "REVERT", - SELFDESTRUCT: "SELFDESTRUCT", + CREATE: "CREATE", + CALL: "CALL", + AUTH: "AUTH", + AUTHCALL: "AUTHCALL", + RETURN: "RETURN", + CALLCODE: "CALLCODE", + DELEGATECALL: "DELEGATECALL", + CREATE2: "CREATE2", + STATICCALL: "STATICCALL", + REVERT: "REVERT", + SELFDESTRUCT: "SELFDESTRUCT", PUSH: "PUSH", DUP: "DUP", @@ -537,8 +537,8 @@ var stringToOp = map[string]OpCode{ "CREATE": CREATE, "CREATE2": CREATE2, "CALL": CALL, - "AUTHORIZE": AUTHORIZE, - "AUTHORIZEDCALL": AUTHORIZEDCALL, + "AUTH": AUTH, + "AUTHCALL": AUTHCALL, "RETURN": RETURN, "CALLCODE": CALLCODE, "REVERT": REVERT,