diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 0e4fe01981..1ddfac4559 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -18,12 +18,14 @@ package vm import ( "crypto/sha256" + "encoding/binary" "errors" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/blake2b" "github.com/ethereum/go-ethereum/crypto/bn256" "github.com/ethereum/go-ethereum/params" "golang.org/x/crypto/ripemd160" @@ -70,6 +72,7 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, } // RunPrecompiledContract runs and evaluates the output of a precompiled contract. @@ -431,3 +434,67 @@ func (c *bn256PairingByzantium) RequiredGas(input []byte) uint64 { func (c *bn256PairingByzantium) Run(input []byte) ([]byte, error) { return runBn256Pairing(input) } + +type blake2F struct{} + +func (c *blake2F) RequiredGas(input []byte) uint64 { + if len(input) != blake2FInputLength { + // Input is malformed, we can't read the number of rounds. + // Precompile can't be executed so we set its price to 0. + return 0 + } + + rounds := binary.BigEndian.Uint32(input[0:4]) + return uint64(rounds) +} + +const blake2FInputLength = 213 +const blake2FFinalBlockBytes = byte(1) +const blake2FNonFinalBlockBytes = byte(0) + +var errBlake2FIncorrectInputLength = errors.New( + "input length for Blake2 F precompile should be exactly 213 bytes", +) + +var errBlake2FIncorrectFinalBlockIndicator = errors.New( + "incorrect final block indicator flag", +) + +func (c *blake2F) Run(input []byte) ([]byte, error) { + if len(input) != blake2FInputLength { + return nil, errBlake2FIncorrectInputLength + } + if input[212] != blake2FNonFinalBlockBytes && input[212] != blake2FFinalBlockBytes { + return nil, errBlake2FIncorrectFinalBlockIndicator + } + + rounds := binary.BigEndian.Uint32(input[0:4]) + + var h [8]uint64 + for i := 0; i < 8; i++ { + offset := 4 + i*8 + h[i] = binary.LittleEndian.Uint64(input[offset : offset+8]) + } + + var m [16]uint64 + for i := 0; i < 16; i++ { + offset := 68 + i*8 + m[i] = binary.LittleEndian.Uint64(input[offset : offset+8]) + } + + var t [2]uint64 + t[0] = binary.LittleEndian.Uint64(input[196:204]) + t[1] = binary.LittleEndian.Uint64(input[204:212]) + + f := (input[212] == blake2FFinalBlockBytes) + + blake2b.F(&h, m, t, f, rounds) + + var output [64]byte + for i := 0; i < 8; i++ { + offset := i * 8 + binary.LittleEndian.PutUint64(output[offset:offset+8], h[i]) + } + + return output[:], nil +} diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 96083337c9..64c8c8b44c 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -19,6 +19,7 @@ package vm import ( "fmt" "math/big" + "reflect" "testing" "github.com/ethereum/go-ethereum/common" @@ -32,6 +33,14 @@ type precompiledTest struct { noBenchmark bool // Benchmark primarily the worst-cases } +// precompiledFailureTest defines the input/error pairs for precompiled +// contract failure tests. +type precompiledFailureTest struct { + input string + expectedError error + name string +} + // modexpTests are the test and benchmark data for the modexp precompiled contract. var modexpTests = []precompiledTest{ { @@ -336,8 +345,56 @@ var bn256PairingTests = []precompiledTest{ }, } +// EIP-152 test vectors +var blake2FMalformedInputTests = []precompiledFailureTest{ + { + input: "", + expectedError: errBlake2FIncorrectInputLength, + name: "vector 0: empty input", + }, + { + input: "00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + expectedError: errBlake2FIncorrectInputLength, + name: "vector 1: less than 213 bytes input", + }, + { + input: "000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + expectedError: errBlake2FIncorrectInputLength, + name: "vector 2: more than 213 bytes input", + }, + { + input: "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002", + expectedError: errBlake2FIncorrectFinalBlockIndicator, + name: "vector 3: malformed final block indicator flag", + }, +} + +// EIP-152 test vectors +var blake2FTests = []precompiledTest{ + { + input: "0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + expected: "08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b", + name: "vector 4", + }, + { // https://tools.ietf.org/html/rfc7693#appendix-A + input: "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + expected: "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923", + name: "vector 5", + }, + { + input: "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000", + expected: "75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735", + name: "vector 6", + }, + { + input: "0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + expected: "b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421", + name: "vector 7", + }, +} + func testPrecompiled(addr string, test precompiledTest, t *testing.T) { - p := PrecompiledContractsByzantium[common.HexToAddress(addr)] + p := PrecompiledContractsIstanbul[common.HexToAddress(addr)] in := common.Hex2Bytes(test.input) contract := NewContract(AccountRef(common.HexToAddress("1337")), nil, new(big.Int), p.RequiredGas(in)) @@ -350,11 +407,25 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { }) } +func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing.T) { + p := PrecompiledContractsIstanbul[common.HexToAddress(addr)] + in := common.Hex2Bytes(test.input) + contract := NewContract(AccountRef(common.HexToAddress("31337")), + nil, new(big.Int), p.RequiredGas(in)) + + t.Run(test.name, func(t *testing.T) { + _, err := RunPrecompiledContract(p, in, contract) + if !reflect.DeepEqual(err, test.expectedError) { + t.Errorf("Expected error [%v], got [%v]", test.expectedError, err) + } + }) +} + func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { if test.noBenchmark { return } - p := PrecompiledContractsByzantium[common.HexToAddress(addr)] + p := PrecompiledContractsIstanbul[common.HexToAddress(addr)] in := common.Hex2Bytes(test.input) reqGas := p.RequiredGas(in) contract := NewContract(AccountRef(common.HexToAddress("1337")), @@ -481,3 +552,20 @@ func BenchmarkPrecompiledBn256Pairing(bench *testing.B) { benchmarkPrecompiled("08", test, bench) } } +func TestPrecompiledBlake2F(t *testing.T) { + for _, test := range blake2FTests { + testPrecompiled("09", test, t) + } +} + +func BenchmarkPrecompiledBlake2F(bench *testing.B) { + for _, test := range blake2FTests { + benchmarkPrecompiled("09", test, bench) + } +} + +func TestPrecompileBlake2FMalformedInput(t *testing.T) { + for _, test := range blake2FMalformedInputTests { + testPrecompiledFailure("09", test, t) + } +} diff --git a/crypto/blake2b/f.go b/crypto/blake2b/f.go new file mode 100644 index 0000000000..63abd6d97e --- /dev/null +++ b/crypto/blake2b/f.go @@ -0,0 +1,181 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Modified by The Keep Network Authors to adjust +// to EIP-152 precompile format. + +package blake2b + +import ( + "math/bits" +) + +// IV is an initialization vector for BLAKE2b +var IV = [8]uint64{ + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, +} + +// the precomputed values for BLAKE2b +// there are 10 16-byte arrays - one for each round +// the entries are calculated from the sigma constants. +var precomputed = [10][16]byte{ + {0, 2, 4, 6, 1, 3, 5, 7, 8, 10, 12, 14, 9, 11, 13, 15}, + {14, 4, 9, 13, 10, 8, 15, 6, 1, 0, 11, 5, 12, 2, 7, 3}, + {11, 12, 5, 15, 8, 0, 2, 13, 10, 3, 7, 9, 14, 6, 1, 4}, + {7, 3, 13, 11, 9, 1, 12, 14, 2, 5, 4, 15, 6, 10, 0, 8}, + {9, 5, 2, 10, 0, 7, 4, 15, 14, 11, 6, 3, 1, 12, 8, 13}, + {2, 6, 0, 8, 12, 10, 11, 3, 4, 7, 15, 1, 13, 5, 14, 9}, + {12, 1, 14, 4, 5, 15, 13, 10, 0, 6, 9, 8, 7, 3, 2, 11}, + {13, 7, 12, 3, 11, 14, 1, 9, 5, 15, 8, 2, 0, 4, 6, 10}, + {6, 14, 11, 0, 15, 9, 3, 8, 12, 13, 1, 10, 2, 7, 4, 5}, + {10, 8, 7, 1, 2, 4, 6, 5, 15, 9, 3, 13, 11, 14, 12, 0}, +} + +// F is a compression function for BLAKE2b. It takes as an argument the state +// vector `h`, message block vector `m`, offset counter `t`, final +// block indicator flag `f`, and number of rounds `rounds`. The state vector +// provided as the first parameter is modified by the function. +func F(h *[8]uint64, m [16]uint64, c [2]uint64, f bool, rounds uint32) { + c0, c1 := c[0], c[1] + + v0, v1, v2, v3, v4, v5, v6, v7 := h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7] + v8, v9, v10, v11, v12, v13, v14, v15 := IV[0], IV[1], IV[2], IV[3], IV[4], IV[5], IV[6], IV[7] + v12 ^= c0 + v13 ^= c1 + + if f { + v14 ^= 0xffffffffffffffff + } + + for j := uint32(0); j < rounds; j++ { + s := &(precomputed[j%10]) + + v0 += m[s[0]] + v0 += v4 + v12 ^= v0 + v12 = bits.RotateLeft64(v12, -32) + v8 += v12 + v4 ^= v8 + v4 = bits.RotateLeft64(v4, -24) + v1 += m[s[1]] + v1 += v5 + v13 ^= v1 + v13 = bits.RotateLeft64(v13, -32) + v9 += v13 + v5 ^= v9 + v5 = bits.RotateLeft64(v5, -24) + v2 += m[s[2]] + v2 += v6 + v14 ^= v2 + v14 = bits.RotateLeft64(v14, -32) + v10 += v14 + v6 ^= v10 + v6 = bits.RotateLeft64(v6, -24) + v3 += m[s[3]] + v3 += v7 + v15 ^= v3 + v15 = bits.RotateLeft64(v15, -32) + v11 += v15 + v7 ^= v11 + v7 = bits.RotateLeft64(v7, -24) + + v0 += m[s[4]] + v0 += v4 + v12 ^= v0 + v12 = bits.RotateLeft64(v12, -16) + v8 += v12 + v4 ^= v8 + v4 = bits.RotateLeft64(v4, -63) + v1 += m[s[5]] + v1 += v5 + v13 ^= v1 + v13 = bits.RotateLeft64(v13, -16) + v9 += v13 + v5 ^= v9 + v5 = bits.RotateLeft64(v5, -63) + v2 += m[s[6]] + v2 += v6 + v14 ^= v2 + v14 = bits.RotateLeft64(v14, -16) + v10 += v14 + v6 ^= v10 + v6 = bits.RotateLeft64(v6, -63) + v3 += m[s[7]] + v3 += v7 + v15 ^= v3 + v15 = bits.RotateLeft64(v15, -16) + v11 += v15 + v7 ^= v11 + v7 = bits.RotateLeft64(v7, -63) + + v0 += m[s[8]] + v0 += v5 + v15 ^= v0 + v15 = bits.RotateLeft64(v15, -32) + v10 += v15 + v5 ^= v10 + v5 = bits.RotateLeft64(v5, -24) + v1 += m[s[9]] + v1 += v6 + v12 ^= v1 + v12 = bits.RotateLeft64(v12, -32) + v11 += v12 + v6 ^= v11 + v6 = bits.RotateLeft64(v6, -24) + v2 += m[s[10]] + v2 += v7 + v13 ^= v2 + v13 = bits.RotateLeft64(v13, -32) + v8 += v13 + v7 ^= v8 + v7 = bits.RotateLeft64(v7, -24) + v3 += m[s[11]] + v3 += v4 + v14 ^= v3 + v14 = bits.RotateLeft64(v14, -32) + v9 += v14 + v4 ^= v9 + v4 = bits.RotateLeft64(v4, -24) + + v0 += m[s[12]] + v0 += v5 + v15 ^= v0 + v15 = bits.RotateLeft64(v15, -16) + v10 += v15 + v5 ^= v10 + v5 = bits.RotateLeft64(v5, -63) + v1 += m[s[13]] + v1 += v6 + v12 ^= v1 + v12 = bits.RotateLeft64(v12, -16) + v11 += v12 + v6 ^= v11 + v6 = bits.RotateLeft64(v6, -63) + v2 += m[s[14]] + v2 += v7 + v13 ^= v2 + v13 = bits.RotateLeft64(v13, -16) + v8 += v13 + v7 ^= v8 + v7 = bits.RotateLeft64(v7, -63) + v3 += m[s[15]] + v3 += v4 + v14 ^= v3 + v14 = bits.RotateLeft64(v14, -16) + v9 += v14 + v4 ^= v9 + v4 = bits.RotateLeft64(v4, -63) + + } + + h[0] ^= v0 ^ v8 + h[1] ^= v1 ^ v9 + h[2] ^= v2 ^ v10 + h[3] ^= v3 ^ v11 + h[4] ^= v4 ^ v12 + h[5] ^= v5 ^ v13 + h[6] ^= v6 ^ v14 + h[7] ^= v7 ^ v15 +} diff --git a/crypto/blake2b/f_test.go b/crypto/blake2b/f_test.go new file mode 100644 index 0000000000..5141d61cbd --- /dev/null +++ b/crypto/blake2b/f_test.go @@ -0,0 +1,109 @@ +package blake2b + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + "reflect" + "testing" +) + +func TestF(t *testing.T) { + for i, test := range testVectorsF { + t.Run(fmt.Sprintf("test vector %v", i), func(t *testing.T) { + //toEthereumTestCase(test) + + h := test.hIn + + F(&h, test.m, test.c, test.f, test.rounds) + + if !reflect.DeepEqual(test.hOut, h) { + t.Errorf("Unexpected result\nExpected: [%v]\nActual: [%v]\n", test.hOut, h) + } + }) + } +} + +type testVector struct { + hIn [8]uint64 + m [16]uint64 + c [2]uint64 + f bool + rounds uint32 + hOut [8]uint64 +} + +// https://tools.ietf.org/html/rfc7693#appendix-A +var testVectorsF = []testVector{ + { + hIn: [8]uint64{ + 0x6a09e667f2bdc948, 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, + }, + m: [16]uint64{ + 0x0000000000636261, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, + }, + c: [2]uint64{3, 0}, + f: true, + rounds: 12, + hOut: [8]uint64{ + 0x0D4D1C983FA580BA, 0xE9F6129FB697276A, 0xB7C45A68142F214C, + 0xD1A2FFDB6FBB124B, 0x2D79AB2A39C5877D, 0x95CC3345DED552C2, + 0x5A92F1DBA88AD318, 0x239900D4ED8623B9, + }, + }, +} + +// toEthereumTestCase transforms F test vector into test vector format used by +// go-ethereum precompiles +func toEthereumTestCase(vector testVector) { + var memory [213]byte + + // 4 bytes for rounds + binary.BigEndian.PutUint32(memory[0:4], uint32(vector.rounds)) + + // for h (512 bits = 64 bytes) + for i := 0; i < 8; i++ { + offset := 4 + i*8 + binary.LittleEndian.PutUint64(memory[offset:offset+8], vector.hIn[i]) + + } + + // for m (1024 bits = 128 bytes) + for i := 0; i < 16; i++ { + offset := 68 + i*8 + binary.LittleEndian.PutUint64(memory[offset:offset+8], vector.m[i]) + } + + // 8 bytes for t[0], 8 bytes for t[1] + binary.LittleEndian.PutUint64(memory[196:204], vector.c[0]) + binary.LittleEndian.PutUint64(memory[204:212], vector.c[1]) + + // 1 byte for f + if vector.f { + memory[212] = 1 + } + + fmt.Printf("input: \"%v\"\n", hex.EncodeToString(memory[:])) + + var result [64]byte + + binary.LittleEndian.PutUint64(result[0:8], vector.hOut[0]) + binary.LittleEndian.PutUint64(result[8:16], vector.hOut[1]) + binary.LittleEndian.PutUint64(result[16:24], vector.hOut[2]) + binary.LittleEndian.PutUint64(result[24:32], vector.hOut[3]) + + binary.LittleEndian.PutUint64(result[32:40], vector.hOut[4]) + binary.LittleEndian.PutUint64(result[40:48], vector.hOut[5]) + binary.LittleEndian.PutUint64(result[48:56], vector.hOut[6]) + binary.LittleEndian.PutUint64(result[56:64], vector.hOut[7]) + + fmt.Printf("expected: \"%v\"\n", hex.EncodeToString(result[:])) +} diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 9d6701868c..c0729fb1d8 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -390,7 +390,7 @@ func New(code string) (*Tracer, error) { return 1 }) tracer.vm.PushGlobalGoFunction("isPrecompiled", func(ctx *duktape.Context) int { - _, ok := vm.PrecompiledContractsByzantium[common.BytesToAddress(popSlice(ctx))] + _, ok := vm.PrecompiledContractsIstanbul[common.BytesToAddress(popSlice(ctx))] ctx.PushBoolean(ok) return 1 })