Merge pull request #187 from status-im/modexp-fixes

Modexp fixes
This commit is contained in:
coffeepots 2018-12-06 10:10:59 +00:00 committed by GitHub
commit 86853ea97c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 33 deletions

View File

@ -6,9 +6,9 @@ PrecompileTests
+ bn256mul.json OK + bn256mul.json OK
+ ecrecover.json OK + ecrecover.json OK
+ identity.json OK + identity.json OK
- modexp.json Fail + modexp.json OK
+ pairing.json OK + pairing.json OK
+ ripemd160.json OK + ripemd160.json OK
+ sha256.json OK + sha256.json OK
``` ```
OK: 7/8 Fail: 1/8 Skip: 0/8 OK: 8/8 Fail: 0/8 Skip: 0/8

View File

@ -12,9 +12,8 @@ import
# some methods based on py-evm utils/numeric # some methods based on py-evm utils/numeric
func log2*(value: UInt256): Natural {.inline.}= func log2*[bits: static int](value: StUint[bits]): Natural {.inline.}=
# TODO: do we use ln for log2 like Nim convention? (bits - 1) - value.countLeadingZeroBits
255 - value.countLeadingZeroBits
func log256*(value: UInt256): Natural {.inline.}= func log256*(value: UInt256): Natural {.inline.}=
value.log2 shr 3 # div 8 (= log2(256), Logb x = Loga x/Loga b) value.log2 shr 3 # div 8 (= log2(256), Logb x = Loga x/Loga b)
@ -55,7 +54,7 @@ func cleanMemRef*(x: UInt256): int {.inline.} =
return high(int32) shr 2 return high(int32) shr 2
return x.toInt return x.toInt
proc rangeToPaddedUint256*(x: seq[byte], first, last: int): Uint256 = proc rangeToPadded*[T: StUint](x: openarray[byte], first, last: int): T =
## Convert take a slice of a sequence of bytes interpret it as the big endian ## Convert take a slice of a sequence of bytes interpret it as the big endian
## representation of an Uint256. Use padding for sequence shorter than 32 bytes ## representation of an Uint256. Use padding for sequence shorter than 32 bytes
## including 0-length sequences. ## including 0-length sequences.
@ -63,10 +62,10 @@ proc rangeToPaddedUint256*(x: seq[byte], first, last: int): Uint256 =
let lo = max(0, first) let lo = max(0, first)
let hi = min(x.high, last) let hi = min(x.high, last)
if not(lo < hi): if not(lo <= hi):
return # 0 return # 0
result = UInt256.fromBytesBE( result = T.fromBytesBE(
x.toOpenArray(lo, hi), x.toOpenArray(lo, hi),
allowPadding = true allowPadding = true
) )

View File

@ -101,25 +101,18 @@ proc identity*(computation: var BaseComputation) =
computation.rawOutput = computation.msg.data computation.rawOutput = computation.msg.data
debug "Identity precompile", output = computation.rawOutput.toHex debug "Identity precompile", output = computation.rawOutput.toHex
proc modExp*(computation: var BaseComputation) = proc modExpInternal(computation: var BaseComputation, base_len, exp_len, mod_len: int, T: type StUint) =
## Modular exponentiation precompiled contract
# Parsing the data
template rawMsg: untyped {.dirty.} = template rawMsg: untyped {.dirty.} =
computation.msg.data computation.msg.data
let let
base_len = rawMsg.rangeToPaddedUint256(0, 31).truncate(int) base = rawMsg.rangeToPadded[:T](96, 95 + base_len)
exp_len = rawMsg.rangeToPaddedUint256(32, 63).truncate(int) exp = rawMsg.rangeToPadded[:T](96 + base_len, 95 + base_len + exp_len)
mod_len = rawMsg.rangeToPaddedUint256(64, 95).truncate(int) modulo = rawMsg.rangeToPadded[:T](96 + base_len + exp_len, 95 + base_len + exp_len + mod_len)
start_exp = 96 + base_len
start_mod = start_exp + exp_len
base = rawMsg.rangeToPaddedUint256(96, start_exp - 1)
exp = rawMsg.rangeToPaddedUint256(start_exp, start_mod - 1)
modulo = rawMsg.rangeToPaddedUint256(start_mod, start_mod + mod_len - 1)
block: # Gas cost block: # Gas cost
func gasModExp_f(x: Natural): int = func gasModExp_f(x: Natural): int =
## Estimates the difficulty of Karatsuba multiplication
# x: maximum length in bytes between modulo and base # x: maximum length in bytes between modulo and base
# TODO: Deal with negative max_len # TODO: Deal with negative max_len
result = case x result = case x
@ -131,11 +124,11 @@ proc modExp*(computation: var BaseComputation) =
# TODO deal with negative length # TODO deal with negative length
if exp_len <= 32: if exp_len <= 32:
if exp.isZero(): 0 if exp.isZero(): 0
else: log2(exp) else: log2(exp) # highest-bit in exponent
else: else:
let extra = rawMsg.rangeToPaddedUint256(96 + base_len, 127 + base_len) let first32 = rawMsg.rangeToPadded[:Uint256](96 + base_len, 95 + base_len + exp_len)
if not extra.isZero: if not first32.isZero:
8 * (exp_len - 32) + extra.log2 8 * (exp_len - 32) + first32.log2
else: else:
8 * (exp_len - 32) 8 * (exp_len - 32)
@ -145,28 +138,62 @@ proc modExp*(computation: var BaseComputation) =
max(adj_exp_len, 1) max(adj_exp_len, 1)
) div GasQuadDivisor ) div GasQuadDivisor
computation.gasMeter.consumeGas(gasFee, reason="ModExp Precompile")
block: # Processing block: # Processing
# Start with EVM special cases # TODO: specs mentions that we should return in "M" format
# i.e. if Base and exp are uint512 and Modulo an uint256
# we should return a 256-bit big-endian byte array
# Force static evaluation # Force static evaluation
func zero256(): static array[32, byte] = discard func zero(): static array[T.bits div 8, byte] = discard
func one256(): static array[32, byte] = func one(): static array[T.bits div 8, byte] =
when cpuEndian == bigEndian: when cpuEndian == bigEndian:
result[^1] = 1 result[^1] = 1
else: else:
result[0] = 1 result[0] = 1
# Start with EVM special cases
if modulo <= 1: if modulo <= 1:
# If m == 0: EVM returns 0. # If m == 0: EVM returns 0.
# If m == 1: we can shortcut that to 0 as well # If m == 1: we can shortcut that to 0 as well
computation.rawOutput = @(zero256()) computation.rawOutput = @(zero())
elif exp.isZero(): elif exp.isZero():
# If 0^0: EVM returns 1 # If 0^0: EVM returns 1
# For all x != 0, x^0 == 1 as well # For all x != 0, x^0 == 1 as well
computation.rawOutput = @(one256()) computation.rawOutput = @(one())
else: else:
computation.rawOutput = @(powmod(base, exp, modulo).toByteArrayBE) computation.rawOutput = @(powmod(base, exp, modulo).toByteArrayBE)
proc modExp*(computation: var BaseComputation) =
## Modular exponentiation precompiled contract
## Yellow Paper Appendix E
## EIP-198 - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-198.md
# Parsing the data
template rawMsg: untyped {.dirty.} =
computation.msg.data
let # lengths Base, Exponent, Modulus
base_len = rawMsg.rangeToPadded[:Uint256](0, 31).truncate(int)
exp_len = rawMsg.rangeToPadded[:Uint256](32, 63).truncate(int)
mod_len = rawMsg.rangeToPadded[:Uint256](64, 95).truncate(int)
let maxBytes = max(base_len, max(exp_len, mod_len))
if maxBytes <= 32:
computation.modExpInternal(base_len, exp_len, mod_len, UInt256)
elif maxBytes <= 64:
computation.modExpInternal(base_len, exp_len, mod_len, StUint[512])
elif maxBytes <= 128:
computation.modExpInternal(base_len, exp_len, mod_len, StUint[1024])
elif maxBytes <= 256:
computation.modExpInternal(base_len, exp_len, mod_len, StUint[2048])
elif maxBytes <= 512:
computation.modExpInternal(base_len, exp_len, mod_len, StUint[4096])
elif maxBytes <= 1024:
computation.modExpInternal(base_len, exp_len, mod_len, StUint[8192])
else:
raise newException(ValueError, "The Nimbus VM doesn't support modular exponentiation with numbers larger than uint8192")
proc bn256ecAdd*(computation: var BaseComputation) = proc bn256ecAdd*(computation: var BaseComputation) =
var var
input: array[128, byte] input: array[128, byte]

View File

@ -14,4 +14,5 @@ import ./test_code_stream,
./test_caching_db_backend, ./test_caching_db_backend,
./test_genesis, ./test_genesis,
./test_vm_json, ./test_vm_json,
./test_precompiles,
./test_generalstate_json ./test_generalstate_json