From 949c5a1dc6ff74195e33e27704656bafdec5463a Mon Sep 17 00:00:00 2001 From: mratsim Date: Tue, 4 Dec 2018 12:49:48 +0100 Subject: [PATCH 1/6] Modexp fix example 1 (returned to early for ints of length 1) --- nimbus/vm/interpreter/utils/utils_numeric.nim | 2 +- tests/fixtures/PrecompileTests/modexp.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nimbus/vm/interpreter/utils/utils_numeric.nim b/nimbus/vm/interpreter/utils/utils_numeric.nim index 4274bf861..a57b1afb7 100644 --- a/nimbus/vm/interpreter/utils/utils_numeric.nim +++ b/nimbus/vm/interpreter/utils/utils_numeric.nim @@ -63,7 +63,7 @@ proc rangeToPaddedUint256*(x: seq[byte], first, last: int): Uint256 = let lo = max(0, first) let hi = min(x.high, last) - if not(lo < hi): + if not(lo <= hi): return # 0 result = UInt256.fromBytesBE( diff --git a/tests/fixtures/PrecompileTests/modexp.json b/tests/fixtures/PrecompileTests/modexp.json index cd877bfaa..bb711b394 100644 --- a/tests/fixtures/PrecompileTests/modexp.json +++ b/tests/fixtures/PrecompileTests/modexp.json @@ -87,4 +87,4 @@ "expected": "5a0eb2bdf0ac1cae8e586689fa16cd4b07dfdedaec8a110ea1fdb059dd5253231b6132987598dfc6e11f86780428982d50cf68f67ae452622c3b336b537ef3298ca645e8f89ee39a26758206a5a3f6409afc709582f95274b57b71fae5c6b74619ae6f089a5393c5b79235d9caf699d23d88fb873f78379690ad8405e34c19f5257d596580c7a6a7206a3712825afe630c76b31cdb4a23e7f0632e10f14f4e282c81a66451a26f8df2a352b5b9f607a7198449d1b926e27036810368e691a74b91c61afa73d9d3b99453e7c8b50fd4f09c039a2f2feb5c419206694c31b92df1d9586140cb3417b38d0c503c7b508cc2ed12e813a1c795e9829eb39ee78eeaf360a169b491a1d4e419574e712402de9d48d54c1ae5e03739b7156615e8267e1fb0a897f067afd11fb33f6e24182d7aaaaa18fe5bc1982f20d6b871e5a398f0f6f718181d31ec225cfa9a0a70124ed9a70031bdf0c1c7829f708b6e17d50419ef361cf77d99c85f44607186c8d683106b8bd38a49b5d0fb503b397a83388c5678dcfcc737499d84512690701ed621a6f0172aecf037184ddf0f2453e4053024018e5ab2e30d6d5363b56e8b41509317c99042f517247474ab3abc848e00a07f69c254f46f2a05cf6ed84e5cc906a518fdcfdf2c61ce731f24c5264f1a25fc04934dc28aec112134dd523f70115074ca34e3807aa4cb925147f3a0ce152d323bd8c675ace446d0fd1ae30c4b57f0eb2c23884bc18f0964c0114796c5b6d080c3d89175665fbf63a6381a6a9da39ad070b645c8bb1779506da14439a9f5b5d481954764ea114fac688930bc68534d403cff4210673b6a6ff7ae416b7cd41404c3d3f282fcd193b86d0f54d0006c2a503b40d5c3930da980565b8f9630e9493a79d1c03e74e5f93ac8e4dc1a901ec5e3b3e57049124c7b72ea345aa359e782285d9e6a5c144a378111dd02c40855ff9c2be9b48425cb0b2fd62dc8678fd151121cf26a65e917d65d8e0dacfae108eb5508b601fb8ffa370be1f9a8b749a2d12eeab81f41079de87e2d777994fa4d28188c579ad327f9957fb7bdecec5c680844dd43cb57cf87aeb763c003e65011f73f8c63442df39a92b946a6bd968a1c1e4d5fa7d88476a68bd8e20e5b70a99259c7d3f85fb1b65cd2e93972e6264e74ebf289b8b6979b9b68a85cd5b360c1987f87235c3c845d62489e33acf85d53fa3561fe3a3aee18924588d9c6eba4edb7a4d106b31173e42929f6f0c48c80ce6a72d54eca7c0fe870068b7a7c89c63cdda593f5b32d3cb4ea8a32c39f00ab449155757172d66763ed9527019d6de6c9f2416aa6203f4d11c9ebee1e1d3845099e55504446448027212616167eb36035726daa7698b075286f5379cd3e93cb3e0cf4f9cb8d017facbb5550ed32d5ec5400ae57e47e2bf78d1eaeff9480cc765ceff39db500" } ] -} \ No newline at end of file +} From 6d93bdffeac4450f276e2ae50c51e5eb75d123a8 Mon Sep 17 00:00:00 2001 From: mratsim Date: Tue, 4 Dec 2018 14:39:10 +0100 Subject: [PATCH 2/6] modExp, support up to uint4096 --- nimbus/vm/interpreter/utils/utils_numeric.nim | 8 +-- nimbus/vm/precompiles.nim | 71 ++++++++++++------- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/nimbus/vm/interpreter/utils/utils_numeric.nim b/nimbus/vm/interpreter/utils/utils_numeric.nim index a57b1afb7..8d5d8e043 100644 --- a/nimbus/vm/interpreter/utils/utils_numeric.nim +++ b/nimbus/vm/interpreter/utils/utils_numeric.nim @@ -12,9 +12,9 @@ import # 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? - 255 - value.countLeadingZeroBits + (bits - 1) - value.countLeadingZeroBits func log256*(value: UInt256): Natural {.inline.}= value.log2 shr 3 # div 8 (= log2(256), Logb x = Loga x/Loga b) @@ -55,7 +55,7 @@ func cleanMemRef*(x: UInt256): int {.inline.} = return high(int32) shr 2 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 ## representation of an Uint256. Use padding for sequence shorter than 32 bytes ## including 0-length sequences. @@ -66,7 +66,7 @@ proc rangeToPaddedUint256*(x: seq[byte], first, last: int): Uint256 = if not(lo <= hi): return # 0 - result = UInt256.fromBytesBE( + result = T.fromBytesBE( x.toOpenArray(lo, hi), allowPadding = true ) diff --git a/nimbus/vm/precompiles.nim b/nimbus/vm/precompiles.nim index dca91742e..ed4a66062 100644 --- a/nimbus/vm/precompiles.nim +++ b/nimbus/vm/precompiles.nim @@ -30,7 +30,7 @@ proc getSignature*(computation: BaseComputation): (array[32, byte], Signature) = let v = data[63] # TODO: Endian assert v.int in 27..28 bytes[64] = v - 27 - + if recoverSignature(bytes, result[1]) != EthKeysStatus.Success: raise newException(ValidationError, "Could not recover signature computation") @@ -68,7 +68,7 @@ proc ecRecover*(computation: var BaseComputation) = if sig.recoverSignatureKey(msgHash, pubKey) != EthKeysStatus.Success: raise newException(ValidationError, "Could not derive public key from computation") - + computation.rawOutput.setLen(32) computation.rawOutput[12..31] = pubKey.toCanonicalAddress() debug "ECRecover precompile", derivedKey = pubKey.toCanonicalAddress() @@ -101,25 +101,18 @@ proc identity*(computation: var BaseComputation) = computation.rawOutput = computation.msg.data debug "Identity precompile", output = computation.rawOutput.toHex -proc modExp*(computation: var BaseComputation) = - ## Modular exponentiation precompiled contract - # Parsing the data +proc modExpInternal(computation: var BaseComputation, base_len, exp_len, mod_len: int, T: type StUint) = template rawMsg: untyped {.dirty.} = computation.msg.data + let - base_len = rawMsg.rangeToPaddedUint256(0, 31).truncate(int) - exp_len = rawMsg.rangeToPaddedUint256(32, 63).truncate(int) - mod_len = rawMsg.rangeToPaddedUint256(64, 95).truncate(int) - - 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) + base = rawMsg.rangeToPadded[:T](96, 95 + base_len) + exp = rawMsg.rangeToPadded[:T](96 + base_len, 95 + base_len + exp_len) + modulo = rawMsg.rangeToPadded[:T](96 + base_len + exp_len, 95 + base_len + exp_len + mod_len) block: # Gas cost func gasModExp_f(x: Natural): int = + ## Estimates the difficulty of Karatsuba multiplication # x: maximum length in bytes between modulo and base # TODO: Deal with negative max_len result = case x @@ -131,11 +124,11 @@ proc modExp*(computation: var BaseComputation) = # TODO deal with negative length if exp_len <= 32: if exp.isZero(): 0 - else: log2(exp) + else: log2(exp) # highest-bit in exponent else: - let extra = rawMsg.rangeToPaddedUint256(96 + base_len, 127 + base_len) - if not extra.isZero: - 8 * (exp_len - 32) + extra.log2 + let first32 = rawMsg.rangeToPadded[:Uint256](96 + base_len, 95 + base_len + exp_len) + if not first32.isZero: + 8 * (exp_len - 32) + first32.log2 else: 8 * (exp_len - 32) @@ -146,27 +139,57 @@ proc modExp*(computation: var BaseComputation) = ) div GasQuadDivisor 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 - func zero256(): static array[32, byte] = discard - func one256(): static array[32, byte] = + func zero(): static array[T.bits div 8, byte] = discard + func one(): static array[T.bits div 8, byte] = when cpuEndian == bigEndian: result[^1] = 1 else: result[0] = 1 + # Start with EVM special cases if modulo <= 1: # If m == 0: EVM returns 0. # If m == 1: we can shortcut that to 0 as well - computation.rawOutput = @(zero256()) + computation.rawOutput = @(zero()) elif exp.isZero(): # If 0^0: EVM returns 1 # For all x != 0, x^0 == 1 as well - computation.rawOutput = @(one256()) + computation.rawOutput = @(one()) else: 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]) + else: + raise newException(ValueError, "The Nimbus VM doesn't support modular exponentiation with numbers larger than uint4096") + proc bn256ecAdd*(computation: var BaseComputation) = var input: array[128, byte] From 29f8a8a0666a5a631cea4fc39ad551923d2dcbcc Mon Sep 17 00:00:00 2001 From: mratsim Date: Tue, 4 Dec 2018 15:46:33 +0100 Subject: [PATCH 3/6] modExp fixed! --- PrecompileTests.md | 4 ++-- nimbus/vm/precompiles.nim | 4 +++- tests/test_precompiles.nim | 17 +++++++++-------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/PrecompileTests.md b/PrecompileTests.md index 73046d08d..822216f07 100644 --- a/PrecompileTests.md +++ b/PrecompileTests.md @@ -6,9 +6,9 @@ PrecompileTests + bn256mul.json OK + ecrecover.json OK + identity.json OK -- modexp.json Fail ++ modexp.json OK + pairing.json OK + ripemd160.json OK + sha256.json OK ``` -OK: 7/8 Fail: 1/8 Skip: 0/8 +OK: 8/8 Fail: 0/8 Skip: 0/8 diff --git a/nimbus/vm/precompiles.nim b/nimbus/vm/precompiles.nim index ed4a66062..19c41d651 100644 --- a/nimbus/vm/precompiles.nim +++ b/nimbus/vm/precompiles.nim @@ -187,8 +187,10 @@ proc modExp*(computation: var BaseComputation) = 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 uint4096") + raise newException(ValueError, "The Nimbus VM doesn't support modular exponentiation with numbers larger than uint8192") proc bn256ecAdd*(computation: var BaseComputation) = var diff --git a/tests/test_precompiles.nim b/tests/test_precompiles.nim index 2ed291f09..763683e88 100644 --- a/tests/test_precompiles.nim +++ b/tests/test_precompiles.nim @@ -39,17 +39,18 @@ template doTest(fixture: JsonNode, address: byte, action: untyped): untyped = proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = for label, child in fixtures: case toLowerAscii(label) - of "ecrecover": child.doTest(paEcRecover.ord, ecRecover) - of "sha256": child.doTest(paSha256.ord, sha256) - of "ripemd": child.doTest(paRipeMd160.ord, ripemd160) - of "identity": child.doTest(paIdentity.ord, identity) + # of "ecrecover": child.doTest(paEcRecover.ord, ecRecover) + # of "sha256": child.doTest(paSha256.ord, sha256) + # of "ripemd": child.doTest(paRipeMd160.ord, ripemd160) + # of "identity": child.doTest(paIdentity.ord, identity) of "modexp": child.doTest(paModExp.ord, modexp) - of "bn256add": child.doTest(paEcAdd.ord, bn256ECAdd) - of "bn256mul": child.doTest(paEcMul.ord, bn256ECMul) - of "ecpairing": child.doTest(paPairing.ord, bn256ecPairing) + # of "bn256add": child.doTest(paEcAdd.ord, bn256ECAdd) + # of "bn256mul": child.doTest(paEcMul.ord, bn256ECMul) + # of "ecpairing": child.doTest(paPairing.ord, bn256ecPairing) else: #raise newException(ValueError, "Unknown test vector '" & $label & "'") - echo "Unknown test vector '" & $label & "'" + # echo "Unknown test vector '" & $label & "'" + discard suite "Precompiles": jsonTest("PrecompileTests", testFixture) From f50bb57eb6c2d840d54d604600ef8e0d9695fdb3 Mon Sep 17 00:00:00 2001 From: mratsim Date: Tue, 4 Dec 2018 15:52:59 +0100 Subject: [PATCH 4/6] Reactivate the precompiles test + consume gas --- nimbus/vm/precompiles.nim | 6 ++++-- tests/test_precompiles.nim | 17 ++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/nimbus/vm/precompiles.nim b/nimbus/vm/precompiles.nim index 19c41d651..0dd2aed07 100644 --- a/nimbus/vm/precompiles.nim +++ b/nimbus/vm/precompiles.nim @@ -138,10 +138,12 @@ proc modExpInternal(computation: var BaseComputation, base_len, exp_len, mod_len max(adj_exp_len, 1) ) div GasQuadDivisor + computation.gasMeter.consumeGas(gasFee, reason="ModExp Precompile") + block: # Processing # 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 + # 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 func zero(): static array[T.bits div 8, byte] = discard diff --git a/tests/test_precompiles.nim b/tests/test_precompiles.nim index 763683e88..2ed291f09 100644 --- a/tests/test_precompiles.nim +++ b/tests/test_precompiles.nim @@ -39,18 +39,17 @@ template doTest(fixture: JsonNode, address: byte, action: untyped): untyped = proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = for label, child in fixtures: case toLowerAscii(label) - # of "ecrecover": child.doTest(paEcRecover.ord, ecRecover) - # of "sha256": child.doTest(paSha256.ord, sha256) - # of "ripemd": child.doTest(paRipeMd160.ord, ripemd160) - # of "identity": child.doTest(paIdentity.ord, identity) + of "ecrecover": child.doTest(paEcRecover.ord, ecRecover) + of "sha256": child.doTest(paSha256.ord, sha256) + of "ripemd": child.doTest(paRipeMd160.ord, ripemd160) + of "identity": child.doTest(paIdentity.ord, identity) of "modexp": child.doTest(paModExp.ord, modexp) - # of "bn256add": child.doTest(paEcAdd.ord, bn256ECAdd) - # of "bn256mul": child.doTest(paEcMul.ord, bn256ECMul) - # of "ecpairing": child.doTest(paPairing.ord, bn256ecPairing) + of "bn256add": child.doTest(paEcAdd.ord, bn256ECAdd) + of "bn256mul": child.doTest(paEcMul.ord, bn256ECMul) + of "ecpairing": child.doTest(paPairing.ord, bn256ecPairing) else: #raise newException(ValueError, "Unknown test vector '" & $label & "'") - # echo "Unknown test vector '" & $label & "'" - discard + echo "Unknown test vector '" & $label & "'" suite "Precompiles": jsonTest("PrecompileTests", testFixture) From 24bcb3b2d872eb6fc4093f6dd94aba106be5323e Mon Sep 17 00:00:00 2001 From: mratsim Date: Tue, 4 Dec 2018 15:56:18 +0100 Subject: [PATCH 5/6] remove log2 spurious comment --- nimbus/vm/interpreter/utils/utils_numeric.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/nimbus/vm/interpreter/utils/utils_numeric.nim b/nimbus/vm/interpreter/utils/utils_numeric.nim index 8d5d8e043..9b93223bf 100644 --- a/nimbus/vm/interpreter/utils/utils_numeric.nim +++ b/nimbus/vm/interpreter/utils/utils_numeric.nim @@ -13,7 +13,6 @@ import # some methods based on py-evm utils/numeric func log2*[bits: static int](value: StUint[bits]): Natural {.inline.}= - # TODO: do we use ln for log2 like Nim convention? (bits - 1) - value.countLeadingZeroBits func log256*(value: UInt256): Natural {.inline.}= From 4d07d99f668aef28b15197d9bd1674068558cd7a Mon Sep 17 00:00:00 2001 From: mratsim Date: Tue, 4 Dec 2018 16:32:11 +0100 Subject: [PATCH 6/6] Add precompiles to all tests to prevent regressions --- tests/all_tests.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/all_tests.nim b/tests/all_tests.nim index d871460a8..faa862527 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -14,4 +14,5 @@ import ./test_code_stream, ./test_caching_db_backend, ./test_genesis, ./test_vm_json, + ./test_precompiles, ./test_generalstate_json