From d794c7bcc19012b76bfa3d10e6b51aca3935ab4b Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Sun, 12 May 2019 04:45:07 +0000 Subject: [PATCH] import ethereum accounts package copy from the following commit. commit 9b831d74fbee3d59ceca380a40e4a9af1a55cfca Author: Guillaume Ballet Date: Tue May 7 18:22:24 2019 +0200 accounts/usbwallet: fix a comment typo in trezor driver (#19535) Signed-off-by: Leo Chen --- accounts/abi/abi.go | 167 + accounts/abi/abi_test.go | 933 +++++ accounts/abi/argument.go | 354 ++ accounts/abi/bind/auth.go | 62 + accounts/abi/bind/backend.go | 112 + accounts/abi/bind/backends/simulated.go | 505 +++ accounts/abi/bind/backends/simulated_test.go | 82 + accounts/abi/bind/base.go | 366 ++ accounts/abi/bind/base_test.go | 373 ++ accounts/abi/bind/bind.go | 424 +++ accounts/abi/bind/bind_test.go | 1013 ++++++ accounts/abi/bind/template.go | 546 +++ accounts/abi/bind/topics.go | 241 ++ accounts/abi/bind/topics_test.go | 103 + accounts/abi/bind/util.go | 76 + accounts/abi/bind/util_test.go | 94 + accounts/abi/doc.go | 26 + accounts/abi/error.go | 84 + accounts/abi/event.go | 57 + accounts/abi/event_test.go | 428 +++ accounts/abi/method.go | 77 + accounts/abi/method_test.go | 61 + accounts/abi/numbers.go | 44 + accounts/abi/numbers_test.go | 33 + accounts/abi/pack.go | 81 + accounts/abi/pack_test.go | 783 +++++ accounts/abi/reflect.go | 218 ++ accounts/abi/reflect_test.go | 191 + accounts/abi/type.go | 353 ++ accounts/abi/type_test.go | 308 ++ accounts/abi/unpack.go | 295 ++ accounts/abi/unpack_test.go | 1127 ++++++ accounts/accounts.go | 222 ++ accounts/accounts_test.go | 32 + accounts/errors.go | 68 + accounts/external/backend.go | 228 ++ accounts/hd.go | 152 + accounts/hd_test.go | 79 + accounts/keystore/account_cache.go | 301 ++ accounts/keystore/account_cache_test.go | 406 +++ accounts/keystore/file_cache.go | 102 + accounts/keystore/key.go | 232 ++ accounts/keystore/keystore.go | 495 +++ accounts/keystore/keystore_test.go | 387 +++ accounts/keystore/passphrase.go | 356 ++ accounts/keystore/passphrase_test.go | 60 + accounts/keystore/plain.go | 61 + accounts/keystore/plain_test.go | 266 ++ accounts/keystore/presale.go | 147 + accounts/keystore/testdata/dupes/1 | 1 + accounts/keystore/testdata/dupes/2 | 1 + accounts/keystore/testdata/dupes/foo | 1 + .../keystore/testdata/keystore/.hiddenfile | 1 + accounts/keystore/testdata/keystore/README | 21 + ...--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 | 1 + accounts/keystore/testdata/keystore/aaa | 1 + accounts/keystore/testdata/keystore/empty | 0 .../fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e | 1 + accounts/keystore/testdata/keystore/garbage | Bin 0 -> 300 bytes .../keystore/testdata/keystore/no-address | 1 + accounts/keystore/testdata/keystore/zero | 1 + accounts/keystore/testdata/keystore/zzz | 1 + .../cb61d5a9c4896fb9658090b597ef0e7be6f7b67e | 1 + .../keystore/testdata/v1_test_vector.json | 28 + .../keystore/testdata/v3_test_vector.json | 97 + .../keystore/testdata/very-light-scrypt.json | 1 + accounts/keystore/wallet.go | 148 + accounts/keystore/watch.go | 108 + accounts/keystore/watch_fallback.go | 28 + accounts/manager.go | 214 ++ accounts/scwallet/README.md | 102 + accounts/scwallet/apdu.go | 87 + accounts/scwallet/hub.go | 302 ++ accounts/scwallet/securechannel.go | 346 ++ accounts/scwallet/wallet.go | 1082 ++++++ accounts/sort.go | 31 + accounts/url.go | 104 + accounts/url_test.go | 96 + accounts/usbwallet/hub.go | 240 ++ accounts/usbwallet/ledger.go | 464 +++ accounts/usbwallet/trezor.go | 356 ++ accounts/usbwallet/trezor/messages.pb.go | 3081 +++++++++++++++++ accounts/usbwallet/trezor/messages.proto | 905 +++++ accounts/usbwallet/trezor/trezor.go | 46 + accounts/usbwallet/trezor/types.pb.go | 1333 +++++++ accounts/usbwallet/trezor/types.proto | 278 ++ accounts/usbwallet/wallet.go | 594 ++++ 87 files changed, 23314 insertions(+) create mode 100644 accounts/abi/abi.go create mode 100644 accounts/abi/abi_test.go create mode 100644 accounts/abi/argument.go create mode 100644 accounts/abi/bind/auth.go create mode 100644 accounts/abi/bind/backend.go create mode 100644 accounts/abi/bind/backends/simulated.go create mode 100644 accounts/abi/bind/backends/simulated_test.go create mode 100644 accounts/abi/bind/base.go create mode 100644 accounts/abi/bind/base_test.go create mode 100644 accounts/abi/bind/bind.go create mode 100644 accounts/abi/bind/bind_test.go create mode 100644 accounts/abi/bind/template.go create mode 100644 accounts/abi/bind/topics.go create mode 100644 accounts/abi/bind/topics_test.go create mode 100644 accounts/abi/bind/util.go create mode 100644 accounts/abi/bind/util_test.go create mode 100644 accounts/abi/doc.go create mode 100644 accounts/abi/error.go create mode 100644 accounts/abi/event.go create mode 100644 accounts/abi/event_test.go create mode 100644 accounts/abi/method.go create mode 100644 accounts/abi/method_test.go create mode 100644 accounts/abi/numbers.go create mode 100644 accounts/abi/numbers_test.go create mode 100644 accounts/abi/pack.go create mode 100644 accounts/abi/pack_test.go create mode 100644 accounts/abi/reflect.go create mode 100644 accounts/abi/reflect_test.go create mode 100644 accounts/abi/type.go create mode 100644 accounts/abi/type_test.go create mode 100644 accounts/abi/unpack.go create mode 100644 accounts/abi/unpack_test.go create mode 100644 accounts/accounts.go create mode 100644 accounts/accounts_test.go create mode 100644 accounts/errors.go create mode 100644 accounts/external/backend.go create mode 100644 accounts/hd.go create mode 100644 accounts/hd_test.go create mode 100644 accounts/keystore/account_cache.go create mode 100644 accounts/keystore/account_cache_test.go create mode 100644 accounts/keystore/file_cache.go create mode 100644 accounts/keystore/key.go create mode 100644 accounts/keystore/keystore.go create mode 100644 accounts/keystore/keystore_test.go create mode 100644 accounts/keystore/passphrase.go create mode 100644 accounts/keystore/passphrase_test.go create mode 100644 accounts/keystore/plain.go create mode 100644 accounts/keystore/plain_test.go create mode 100644 accounts/keystore/presale.go create mode 100644 accounts/keystore/testdata/dupes/1 create mode 100644 accounts/keystore/testdata/dupes/2 create mode 100644 accounts/keystore/testdata/dupes/foo create mode 100644 accounts/keystore/testdata/keystore/.hiddenfile create mode 100644 accounts/keystore/testdata/keystore/README create mode 100644 accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 create mode 100644 accounts/keystore/testdata/keystore/aaa create mode 100644 accounts/keystore/testdata/keystore/empty create mode 100644 accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e create mode 100644 accounts/keystore/testdata/keystore/garbage create mode 100644 accounts/keystore/testdata/keystore/no-address create mode 100644 accounts/keystore/testdata/keystore/zero create mode 100644 accounts/keystore/testdata/keystore/zzz create mode 100644 accounts/keystore/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e create mode 100644 accounts/keystore/testdata/v1_test_vector.json create mode 100644 accounts/keystore/testdata/v3_test_vector.json create mode 100644 accounts/keystore/testdata/very-light-scrypt.json create mode 100644 accounts/keystore/wallet.go create mode 100644 accounts/keystore/watch.go create mode 100644 accounts/keystore/watch_fallback.go create mode 100644 accounts/manager.go create mode 100644 accounts/scwallet/README.md create mode 100644 accounts/scwallet/apdu.go create mode 100644 accounts/scwallet/hub.go create mode 100644 accounts/scwallet/securechannel.go create mode 100644 accounts/scwallet/wallet.go create mode 100644 accounts/sort.go create mode 100644 accounts/url.go create mode 100644 accounts/url_test.go create mode 100644 accounts/usbwallet/hub.go create mode 100644 accounts/usbwallet/ledger.go create mode 100644 accounts/usbwallet/trezor.go create mode 100644 accounts/usbwallet/trezor/messages.pb.go create mode 100644 accounts/usbwallet/trezor/messages.proto create mode 100644 accounts/usbwallet/trezor/trezor.go create mode 100644 accounts/usbwallet/trezor/types.pb.go create mode 100644 accounts/usbwallet/trezor/types.proto create mode 100644 accounts/usbwallet/wallet.go diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go new file mode 100644 index 000000000..c5fbc1e77 --- /dev/null +++ b/accounts/abi/abi.go @@ -0,0 +1,167 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "bytes" + "encoding/json" + "fmt" + "io" +) + +// The ABI holds information about a contract's context and available +// invokable methods. It will allow you to type check function calls and +// packs data accordingly. +type ABI struct { + Constructor Method + Methods map[string]Method + Events map[string]Event +} + +// JSON returns a parsed ABI interface and error if it failed. +func JSON(reader io.Reader) (ABI, error) { + dec := json.NewDecoder(reader) + + var abi ABI + if err := dec.Decode(&abi); err != nil { + return ABI{}, err + } + + return abi, nil +} + +// Pack the given method name to conform the ABI. Method call's data +// will consist of method_id, args0, arg1, ... argN. Method id consists +// of 4 bytes and arguments are all 32 bytes. +// Method ids are created from the first 4 bytes of the hash of the +// methods string signature. (signature = baz(uint32,string32)) +func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { + // Fetch the ABI of the requested method + if name == "" { + // constructor + arguments, err := abi.Constructor.Inputs.Pack(args...) + if err != nil { + return nil, err + } + return arguments, nil + } + method, exist := abi.Methods[name] + if !exist { + return nil, fmt.Errorf("method '%s' not found", name) + } + arguments, err := method.Inputs.Pack(args...) + if err != nil { + return nil, err + } + // Pack up the method ID too if not a constructor and return + return append(method.Id(), arguments...), nil +} + +// Unpack output in v according to the abi specification +func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) { + if len(data) == 0 { + return fmt.Errorf("abi: unmarshalling empty output") + } + // since there can't be naming collisions with contracts and events, + // we need to decide whether we're calling a method or an event + if method, ok := abi.Methods[name]; ok { + if len(data)%32 != 0 { + return fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data) + } + return method.Outputs.Unpack(v, data) + } + if event, ok := abi.Events[name]; ok { + return event.Inputs.Unpack(v, data) + } + return fmt.Errorf("abi: could not locate named method or event") +} + +// UnpackIntoMap unpacks a log into the provided map[string]interface{} +func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) { + if len(data) == 0 { + return fmt.Errorf("abi: unmarshalling empty output") + } + // since there can't be naming collisions with contracts and events, + // we need to decide whether we're calling a method or an event + if method, ok := abi.Methods[name]; ok { + if len(data)%32 != 0 { + return fmt.Errorf("abi: improperly formatted output") + } + return method.Outputs.UnpackIntoMap(v, data) + } + if event, ok := abi.Events[name]; ok { + return event.Inputs.UnpackIntoMap(v, data) + } + return fmt.Errorf("abi: could not locate named method or event") +} + +// UnmarshalJSON implements json.Unmarshaler interface +func (abi *ABI) UnmarshalJSON(data []byte) error { + var fields []struct { + Type string + Name string + Constant bool + Anonymous bool + Inputs []Argument + Outputs []Argument + } + + if err := json.Unmarshal(data, &fields); err != nil { + return err + } + + abi.Methods = make(map[string]Method) + abi.Events = make(map[string]Event) + for _, field := range fields { + switch field.Type { + case "constructor": + abi.Constructor = Method{ + Inputs: field.Inputs, + } + // empty defaults to function according to the abi spec + case "function", "": + abi.Methods[field.Name] = Method{ + Name: field.Name, + Const: field.Constant, + Inputs: field.Inputs, + Outputs: field.Outputs, + } + case "event": + abi.Events[field.Name] = Event{ + Name: field.Name, + Anonymous: field.Anonymous, + Inputs: field.Inputs, + } + } + } + + return nil +} + +// MethodById looks up a method by the 4-byte id +// returns nil if none found +func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { + if len(sigdata) < 4 { + return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata)) + } + for _, method := range abi.Methods { + if bytes.Equal(method.Id(), sigdata[:4]) { + return &method, nil + } + } + return nil, fmt.Errorf("no method with id: %#x", sigdata[:4]) +} diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go new file mode 100644 index 000000000..42b60a639 --- /dev/null +++ b/accounts/abi/abi_test.go @@ -0,0 +1,933 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "bytes" + "encoding/hex" + "fmt" + "log" + "math/big" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +const jsondata = ` +[ + { "type" : "function", "name" : "balance", "constant" : true }, + { "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } +]` + +const jsondata2 = ` +[ + { "type" : "function", "name" : "balance", "constant" : true }, + { "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, + { "type" : "function", "name" : "test", "constant" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] }, + { "type" : "function", "name" : "string", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] }, + { "type" : "function", "name" : "bool", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] }, + { "type" : "function", "name" : "address", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] }, + { "type" : "function", "name" : "uint64[2]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, + { "type" : "function", "name" : "uint64[]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, + { "type" : "function", "name" : "foo", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, + { "type" : "function", "name" : "bar", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, + { "type" : "function", "name" : "slice", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, + { "type" : "function", "name" : "slice256", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] }, + { "type" : "function", "name" : "sliceAddress", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] }, + { "type" : "function", "name" : "sliceMultiAddress", "constant" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] }, + { "type" : "function", "name" : "nestedArray", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] }, + { "type" : "function", "name" : "nestedArray2", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] }, + { "type" : "function", "name" : "nestedSlice", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] } +]` + +func TestReader(t *testing.T) { + Uint256, _ := NewType("uint256", nil) + exp := ABI{ + Methods: map[string]Method{ + "balance": { + "balance", true, nil, nil, + }, + "send": { + "send", false, []Argument{ + {"amount", Uint256, false}, + }, nil, + }, + }, + } + + abi, err := JSON(strings.NewReader(jsondata)) + if err != nil { + t.Error(err) + } + + // deep equal fails for some reason + for name, expM := range exp.Methods { + gotM, exist := abi.Methods[name] + if !exist { + t.Errorf("Missing expected method %v", name) + } + if !reflect.DeepEqual(gotM, expM) { + t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM) + } + } + + for name, gotM := range abi.Methods { + expM, exist := exp.Methods[name] + if !exist { + t.Errorf("Found extra method %v", name) + } + if !reflect.DeepEqual(gotM, expM) { + t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM) + } + } +} + +func TestTestNumbers(t *testing.T) { + abi, err := JSON(strings.NewReader(jsondata2)) + if err != nil { + t.Error(err) + t.FailNow() + } + + if _, err := abi.Pack("balance"); err != nil { + t.Error(err) + } + + if _, err := abi.Pack("balance", 1); err == nil { + t.Error("expected error for balance(1)") + } + + if _, err := abi.Pack("doesntexist", nil); err == nil { + t.Errorf("doesntexist shouldn't exist") + } + + if _, err := abi.Pack("doesntexist", 1); err == nil { + t.Errorf("doesntexist(1) shouldn't exist") + } + + if _, err := abi.Pack("send", big.NewInt(1000)); err != nil { + t.Error(err) + } + + i := new(int) + *i = 1000 + if _, err := abi.Pack("send", i); err == nil { + t.Errorf("expected send( ptr ) to throw, requires *big.Int instead of *int") + } + + if _, err := abi.Pack("test", uint32(1000)); err != nil { + t.Error(err) + } +} + +func TestTestString(t *testing.T) { + abi, err := JSON(strings.NewReader(jsondata2)) + if err != nil { + t.Error(err) + t.FailNow() + } + + if _, err := abi.Pack("string", "hello world"); err != nil { + t.Error(err) + } +} + +func TestTestBool(t *testing.T) { + abi, err := JSON(strings.NewReader(jsondata2)) + if err != nil { + t.Error(err) + t.FailNow() + } + + if _, err := abi.Pack("bool", true); err != nil { + t.Error(err) + } +} + +func TestTestSlice(t *testing.T) { + abi, err := JSON(strings.NewReader(jsondata2)) + if err != nil { + t.Error(err) + t.FailNow() + } + + slice := make([]uint64, 2) + if _, err := abi.Pack("uint64[2]", slice); err != nil { + t.Error(err) + } + + if _, err := abi.Pack("uint64[]", slice); err != nil { + t.Error(err) + } +} + +func TestMethodSignature(t *testing.T) { + String, _ := NewType("string", nil) + m := Method{"foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil} + exp := "foo(string,string)" + if m.Sig() != exp { + t.Error("signature mismatch", exp, "!=", m.Sig()) + } + + idexp := crypto.Keccak256([]byte(exp))[:4] + if !bytes.Equal(m.Id(), idexp) { + t.Errorf("expected ids to match %x != %x", m.Id(), idexp) + } + + uintt, _ := NewType("uint256", nil) + m = Method{"foo", false, []Argument{{"bar", uintt, false}}, nil} + exp = "foo(uint256)" + if m.Sig() != exp { + t.Error("signature mismatch", exp, "!=", m.Sig()) + } + + // Method with tuple arguments + s, _ := NewType("tuple", []ArgumentMarshaling{ + {Name: "a", Type: "int256"}, + {Name: "b", Type: "int256[]"}, + {Name: "c", Type: "tuple[]", Components: []ArgumentMarshaling{ + {Name: "x", Type: "int256"}, + {Name: "y", Type: "int256"}, + }}, + {Name: "d", Type: "tuple[2]", Components: []ArgumentMarshaling{ + {Name: "x", Type: "int256"}, + {Name: "y", Type: "int256"}, + }}, + }) + m = Method{"foo", false, []Argument{{"s", s, false}, {"bar", String, false}}, nil} + exp = "foo((int256,int256[],(int256,int256)[],(int256,int256)[2]),string)" + if m.Sig() != exp { + t.Error("signature mismatch", exp, "!=", m.Sig()) + } +} + +func TestMultiPack(t *testing.T) { + abi, err := JSON(strings.NewReader(jsondata2)) + if err != nil { + t.Error(err) + t.FailNow() + } + + sig := crypto.Keccak256([]byte("bar(uint32,uint16)"))[:4] + sig = append(sig, make([]byte, 64)...) + sig[35] = 10 + sig[67] = 11 + + packed, err := abi.Pack("bar", uint32(10), uint16(11)) + if err != nil { + t.Error(err) + t.FailNow() + } + + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } +} + +func ExampleJSON() { + const definition = `[{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBar","outputs":[{"name":"","type":"bool"}],"type":"function"}]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + log.Fatalln(err) + } + out, err := abi.Pack("isBar", common.HexToAddress("01")) + if err != nil { + log.Fatalln(err) + } + + fmt.Printf("%x\n", out) + // Output: + // 1f2c40920000000000000000000000000000000000000000000000000000000000000001 +} + +func TestInputVariableInputLength(t *testing.T) { + const definition = `[ + { "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] }, + { "type" : "function", "name" : "bytesOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] }, + { "type" : "function", "name" : "strTwo", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "str1", "type" : "string" } ] } + ]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + // test one string + strin := "hello world" + strpack, err := abi.Pack("strOne", strin) + if err != nil { + t.Error(err) + } + + offset := make([]byte, 32) + offset[31] = 32 + length := make([]byte, 32) + length[31] = byte(len(strin)) + value := common.RightPadBytes([]byte(strin), 32) + exp := append(offset, append(length, value...)...) + + // ignore first 4 bytes of the output. This is the function identifier + strpack = strpack[4:] + if !bytes.Equal(strpack, exp) { + t.Errorf("expected %x, got %x\n", exp, strpack) + } + + // test one bytes + btspack, err := abi.Pack("bytesOne", []byte(strin)) + if err != nil { + t.Error(err) + } + // ignore first 4 bytes of the output. This is the function identifier + btspack = btspack[4:] + if !bytes.Equal(btspack, exp) { + t.Errorf("expected %x, got %x\n", exp, btspack) + } + + // test two strings + str1 := "hello" + str2 := "world" + str2pack, err := abi.Pack("strTwo", str1, str2) + if err != nil { + t.Error(err) + } + + offset1 := make([]byte, 32) + offset1[31] = 64 + length1 := make([]byte, 32) + length1[31] = byte(len(str1)) + value1 := common.RightPadBytes([]byte(str1), 32) + + offset2 := make([]byte, 32) + offset2[31] = 128 + length2 := make([]byte, 32) + length2[31] = byte(len(str2)) + value2 := common.RightPadBytes([]byte(str2), 32) + + exp2 := append(offset1, offset2...) + exp2 = append(exp2, append(length1, value1...)...) + exp2 = append(exp2, append(length2, value2...)...) + + // ignore first 4 bytes of the output. This is the function identifier + str2pack = str2pack[4:] + if !bytes.Equal(str2pack, exp2) { + t.Errorf("expected %x, got %x\n", exp, str2pack) + } + + // test two strings, first > 32, second < 32 + str1 = strings.Repeat("a", 33) + str2pack, err = abi.Pack("strTwo", str1, str2) + if err != nil { + t.Error(err) + } + + offset1 = make([]byte, 32) + offset1[31] = 64 + length1 = make([]byte, 32) + length1[31] = byte(len(str1)) + value1 = common.RightPadBytes([]byte(str1), 64) + offset2[31] = 160 + + exp2 = append(offset1, offset2...) + exp2 = append(exp2, append(length1, value1...)...) + exp2 = append(exp2, append(length2, value2...)...) + + // ignore first 4 bytes of the output. This is the function identifier + str2pack = str2pack[4:] + if !bytes.Equal(str2pack, exp2) { + t.Errorf("expected %x, got %x\n", exp, str2pack) + } + + // test two strings, first > 32, second >32 + str1 = strings.Repeat("a", 33) + str2 = strings.Repeat("a", 33) + str2pack, err = abi.Pack("strTwo", str1, str2) + if err != nil { + t.Error(err) + } + + offset1 = make([]byte, 32) + offset1[31] = 64 + length1 = make([]byte, 32) + length1[31] = byte(len(str1)) + value1 = common.RightPadBytes([]byte(str1), 64) + + offset2 = make([]byte, 32) + offset2[31] = 160 + length2 = make([]byte, 32) + length2[31] = byte(len(str2)) + value2 = common.RightPadBytes([]byte(str2), 64) + + exp2 = append(offset1, offset2...) + exp2 = append(exp2, append(length1, value1...)...) + exp2 = append(exp2, append(length2, value2...)...) + + // ignore first 4 bytes of the output. This is the function identifier + str2pack = str2pack[4:] + if !bytes.Equal(str2pack, exp2) { + t.Errorf("expected %x, got %x\n", exp, str2pack) + } +} + +func TestInputFixedArrayAndVariableInputLength(t *testing.T) { + const definition = `[ + { "type" : "function", "name" : "fixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] }, + { "type" : "function", "name" : "fixedArrBytes", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] }, + { "type" : "function", "name" : "mixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type": "uint256[2]" }, { "name" : "dynArr", "type": "uint256[]" } ] }, + { "type" : "function", "name" : "doubleFixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "fixedArr2", "type": "uint256[3]" } ] }, + { "type" : "function", "name" : "multipleMixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] } + ]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Error(err) + } + + // test string, fixed array uint256[2] + strin := "hello world" + arrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)} + fixedArrStrPack, err := abi.Pack("fixedArrStr", strin, arrin) + if err != nil { + t.Error(err) + } + + // generate expected output + offset := make([]byte, 32) + offset[31] = 96 + length := make([]byte, 32) + length[31] = byte(len(strin)) + strvalue := common.RightPadBytes([]byte(strin), 32) + arrinvalue1 := common.LeftPadBytes(arrin[0].Bytes(), 32) + arrinvalue2 := common.LeftPadBytes(arrin[1].Bytes(), 32) + exp := append(offset, arrinvalue1...) + exp = append(exp, arrinvalue2...) + exp = append(exp, append(length, strvalue...)...) + + // ignore first 4 bytes of the output. This is the function identifier + fixedArrStrPack = fixedArrStrPack[4:] + if !bytes.Equal(fixedArrStrPack, exp) { + t.Errorf("expected %x, got %x\n", exp, fixedArrStrPack) + } + + // test byte array, fixed array uint256[2] + bytesin := []byte(strin) + arrin = [2]*big.Int{big.NewInt(1), big.NewInt(2)} + fixedArrBytesPack, err := abi.Pack("fixedArrBytes", bytesin, arrin) + if err != nil { + t.Error(err) + } + + // generate expected output + offset = make([]byte, 32) + offset[31] = 96 + length = make([]byte, 32) + length[31] = byte(len(strin)) + strvalue = common.RightPadBytes([]byte(strin), 32) + arrinvalue1 = common.LeftPadBytes(arrin[0].Bytes(), 32) + arrinvalue2 = common.LeftPadBytes(arrin[1].Bytes(), 32) + exp = append(offset, arrinvalue1...) + exp = append(exp, arrinvalue2...) + exp = append(exp, append(length, strvalue...)...) + + // ignore first 4 bytes of the output. This is the function identifier + fixedArrBytesPack = fixedArrBytesPack[4:] + if !bytes.Equal(fixedArrBytesPack, exp) { + t.Errorf("expected %x, got %x\n", exp, fixedArrBytesPack) + } + + // test string, fixed array uint256[2], dynamic array uint256[] + strin = "hello world" + fixedarrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)} + dynarrin := []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)} + mixedArrStrPack, err := abi.Pack("mixedArrStr", strin, fixedarrin, dynarrin) + if err != nil { + t.Error(err) + } + + // generate expected output + stroffset := make([]byte, 32) + stroffset[31] = 128 + strlength := make([]byte, 32) + strlength[31] = byte(len(strin)) + strvalue = common.RightPadBytes([]byte(strin), 32) + fixedarrinvalue1 := common.LeftPadBytes(fixedarrin[0].Bytes(), 32) + fixedarrinvalue2 := common.LeftPadBytes(fixedarrin[1].Bytes(), 32) + dynarroffset := make([]byte, 32) + dynarroffset[31] = byte(160 + ((len(strin)/32)+1)*32) + dynarrlength := make([]byte, 32) + dynarrlength[31] = byte(len(dynarrin)) + dynarrinvalue1 := common.LeftPadBytes(dynarrin[0].Bytes(), 32) + dynarrinvalue2 := common.LeftPadBytes(dynarrin[1].Bytes(), 32) + dynarrinvalue3 := common.LeftPadBytes(dynarrin[2].Bytes(), 32) + exp = append(stroffset, fixedarrinvalue1...) + exp = append(exp, fixedarrinvalue2...) + exp = append(exp, dynarroffset...) + exp = append(exp, append(strlength, strvalue...)...) + dynarrarg := append(dynarrlength, dynarrinvalue1...) + dynarrarg = append(dynarrarg, dynarrinvalue2...) + dynarrarg = append(dynarrarg, dynarrinvalue3...) + exp = append(exp, dynarrarg...) + + // ignore first 4 bytes of the output. This is the function identifier + mixedArrStrPack = mixedArrStrPack[4:] + if !bytes.Equal(mixedArrStrPack, exp) { + t.Errorf("expected %x, got %x\n", exp, mixedArrStrPack) + } + + // test string, fixed array uint256[2], fixed array uint256[3] + strin = "hello world" + fixedarrin1 := [2]*big.Int{big.NewInt(1), big.NewInt(2)} + fixedarrin2 := [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)} + doubleFixedArrStrPack, err := abi.Pack("doubleFixedArrStr", strin, fixedarrin1, fixedarrin2) + if err != nil { + t.Error(err) + } + + // generate expected output + stroffset = make([]byte, 32) + stroffset[31] = 192 + strlength = make([]byte, 32) + strlength[31] = byte(len(strin)) + strvalue = common.RightPadBytes([]byte(strin), 32) + fixedarrin1value1 := common.LeftPadBytes(fixedarrin1[0].Bytes(), 32) + fixedarrin1value2 := common.LeftPadBytes(fixedarrin1[1].Bytes(), 32) + fixedarrin2value1 := common.LeftPadBytes(fixedarrin2[0].Bytes(), 32) + fixedarrin2value2 := common.LeftPadBytes(fixedarrin2[1].Bytes(), 32) + fixedarrin2value3 := common.LeftPadBytes(fixedarrin2[2].Bytes(), 32) + exp = append(stroffset, fixedarrin1value1...) + exp = append(exp, fixedarrin1value2...) + exp = append(exp, fixedarrin2value1...) + exp = append(exp, fixedarrin2value2...) + exp = append(exp, fixedarrin2value3...) + exp = append(exp, append(strlength, strvalue...)...) + + // ignore first 4 bytes of the output. This is the function identifier + doubleFixedArrStrPack = doubleFixedArrStrPack[4:] + if !bytes.Equal(doubleFixedArrStrPack, exp) { + t.Errorf("expected %x, got %x\n", exp, doubleFixedArrStrPack) + } + + // test string, fixed array uint256[2], dynamic array uint256[], fixed array uint256[3] + strin = "hello world" + fixedarrin1 = [2]*big.Int{big.NewInt(1), big.NewInt(2)} + dynarrin = []*big.Int{big.NewInt(1), big.NewInt(2)} + fixedarrin2 = [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)} + multipleMixedArrStrPack, err := abi.Pack("multipleMixedArrStr", strin, fixedarrin1, dynarrin, fixedarrin2) + if err != nil { + t.Error(err) + } + + // generate expected output + stroffset = make([]byte, 32) + stroffset[31] = 224 + strlength = make([]byte, 32) + strlength[31] = byte(len(strin)) + strvalue = common.RightPadBytes([]byte(strin), 32) + fixedarrin1value1 = common.LeftPadBytes(fixedarrin1[0].Bytes(), 32) + fixedarrin1value2 = common.LeftPadBytes(fixedarrin1[1].Bytes(), 32) + dynarroffset = U256(big.NewInt(int64(256 + ((len(strin)/32)+1)*32))) + dynarrlength = make([]byte, 32) + dynarrlength[31] = byte(len(dynarrin)) + dynarrinvalue1 = common.LeftPadBytes(dynarrin[0].Bytes(), 32) + dynarrinvalue2 = common.LeftPadBytes(dynarrin[1].Bytes(), 32) + fixedarrin2value1 = common.LeftPadBytes(fixedarrin2[0].Bytes(), 32) + fixedarrin2value2 = common.LeftPadBytes(fixedarrin2[1].Bytes(), 32) + fixedarrin2value3 = common.LeftPadBytes(fixedarrin2[2].Bytes(), 32) + exp = append(stroffset, fixedarrin1value1...) + exp = append(exp, fixedarrin1value2...) + exp = append(exp, dynarroffset...) + exp = append(exp, fixedarrin2value1...) + exp = append(exp, fixedarrin2value2...) + exp = append(exp, fixedarrin2value3...) + exp = append(exp, append(strlength, strvalue...)...) + dynarrarg = append(dynarrlength, dynarrinvalue1...) + dynarrarg = append(dynarrarg, dynarrinvalue2...) + exp = append(exp, dynarrarg...) + + // ignore first 4 bytes of the output. This is the function identifier + multipleMixedArrStrPack = multipleMixedArrStrPack[4:] + if !bytes.Equal(multipleMixedArrStrPack, exp) { + t.Errorf("expected %x, got %x\n", exp, multipleMixedArrStrPack) + } +} + +func TestDefaultFunctionParsing(t *testing.T) { + const definition = `[{ "name" : "balance" }]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + if _, ok := abi.Methods["balance"]; !ok { + t.Error("expected 'balance' to be present") + } +} + +func TestBareEvents(t *testing.T) { + const definition = `[ + { "type" : "event", "name" : "balance" }, + { "type" : "event", "name" : "anon", "anonymous" : true}, + { "type" : "event", "name" : "args", "inputs" : [{ "indexed":false, "name":"arg0", "type":"uint256" }, { "indexed":true, "name":"arg1", "type":"address" }] }, + { "type" : "event", "name" : "tuple", "inputs" : [{ "indexed":false, "name":"t", "type":"tuple", "components":[{"name":"a", "type":"uint256"}] }, { "indexed":true, "name":"arg1", "type":"address" }] } + ]` + + arg0, _ := NewType("uint256", nil) + arg1, _ := NewType("address", nil) + tuple, _ := NewType("tuple", []ArgumentMarshaling{{Name: "a", Type: "uint256"}}) + + expectedEvents := map[string]struct { + Anonymous bool + Args []Argument + }{ + "balance": {false, nil}, + "anon": {true, nil}, + "args": {false, []Argument{ + {Name: "arg0", Type: arg0, Indexed: false}, + {Name: "arg1", Type: arg1, Indexed: true}, + }}, + "tuple": {false, []Argument{ + {Name: "t", Type: tuple, Indexed: false}, + {Name: "arg1", Type: arg1, Indexed: true}, + }}, + } + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + if len(abi.Events) != len(expectedEvents) { + t.Fatalf("invalid number of events after parsing, want %d, got %d", len(expectedEvents), len(abi.Events)) + } + + for name, exp := range expectedEvents { + got, ok := abi.Events[name] + if !ok { + t.Errorf("could not found event %s", name) + continue + } + if got.Anonymous != exp.Anonymous { + t.Errorf("invalid anonymous indication for event %s, want %v, got %v", name, exp.Anonymous, got.Anonymous) + } + if len(got.Inputs) != len(exp.Args) { + t.Errorf("invalid number of args, want %d, got %d", len(exp.Args), len(got.Inputs)) + continue + } + for i, arg := range exp.Args { + if arg.Name != got.Inputs[i].Name { + t.Errorf("events[%s].Input[%d] has an invalid name, want %s, got %s", name, i, arg.Name, got.Inputs[i].Name) + } + if arg.Indexed != got.Inputs[i].Indexed { + t.Errorf("events[%s].Input[%d] has an invalid indexed indication, want %v, got %v", name, i, arg.Indexed, got.Inputs[i].Indexed) + } + if arg.Type.T != got.Inputs[i].Type.T { + t.Errorf("events[%s].Input[%d] has an invalid type, want %x, got %x", name, i, arg.Type.T, got.Inputs[i].Type.T) + } + } + } +} + +// TestUnpackEvent is based on this contract: +// contract T { +// event received(address sender, uint amount, bytes memo); +// event receivedAddr(address sender); +// function receive(bytes memo) external payable { +// received(msg.sender, msg.value, memo); +// receivedAddr(msg.sender); +// } +// } +// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt: +// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} +func TestUnpackEvent(t *testing.T) { + const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` + abi, err := JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + + const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) + if err != nil { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + + type ReceivedEvent struct { + Sender common.Address + Amount *big.Int + Memo []byte + } + var ev ReceivedEvent + + err = abi.Unpack(&ev, "received", data) + if err != nil { + t.Error(err) + } + + type ReceivedAddrEvent struct { + Sender common.Address + } + var receivedAddrEv ReceivedAddrEvent + err = abi.Unpack(&receivedAddrEv, "receivedAddr", data) + if err != nil { + t.Error(err) + } +} + +func TestUnpackEventIntoMap(t *testing.T) { + const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` + abi, err := JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + + const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) + if err != nil { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + + receivedMap := map[string]interface{}{} + expectedReceivedMap := map[string]interface{}{ + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err := abi.UnpackIntoMap(receivedMap, "received", data); err != nil { + t.Error(err) + } + if len(receivedMap) != 3 { + t.Error("unpacked `received` map expected to have length 3") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked `received` map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked `received` map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked `received` map does not match expected map") + } + + receivedAddrMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(receivedAddrMap, "receivedAddr", data); err != nil { + t.Error(err) + } + if len(receivedAddrMap) != 1 { + t.Error("unpacked `receivedAddr` map expected to have length 1") + } + if receivedAddrMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked `receivedAddr` map does not match expected map") + } +} + +func TestUnpackMethodIntoMap(t *testing.T) { + const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]` + abi, err := JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + const hexdata = `00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000015800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000158000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001580000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000015800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) + if err != nil { + t.Fatal(err) + } + if len(data)%32 != 0 { + t.Errorf("len(data) is %d, want a multiple of 32", len(data)) + } + + // Tests a method with no outputs + receiveMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(receiveMap, "receive", data); err != nil { + t.Error(err) + } + if len(receiveMap) > 0 { + t.Error("unpacked `receive` map expected to have length 0") + } + + // Tests a method with only outputs + sendMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(sendMap, "send", data); err != nil { + t.Error(err) + } + if len(sendMap) != 1 { + t.Error("unpacked `send` map expected to have length 1") + } + if sendMap["amount"].(*big.Int).Cmp(big.NewInt(1)) != 0 { + t.Error("unpacked `send` map expected `amount` value of 1") + } + + // Tests a method with outputs and inputs + getMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(getMap, "get", data); err != nil { + t.Error(err) + } + if len(sendMap) != 1 { + t.Error("unpacked `get` map expected to have length 1") + } + expectedBytes := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 0} + if !bytes.Equal(getMap["hash"].([]byte), expectedBytes) { + t.Errorf("unpacked `get` map expected `hash` value of %v", expectedBytes) + } +} + +func TestUnpackIntoMapNamingConflict(t *testing.T) { + // Two methods have the same name + var abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"get","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]` + abi, err := JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + var hexdata = `00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) + if err != nil { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + getMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(getMap, "get", data); err == nil { + t.Error("naming conflict between two methods; error expected") + } + + // Two events have the same name + abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"received","type":"event"}]` + abi, err = JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err = hex.DecodeString(hexdata) + if err != nil { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + receivedMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(receivedMap, "received", data); err != nil { + t.Error("naming conflict between two events; no error expected") + } + if len(receivedMap) != 1 { + t.Error("naming conflict between two events; event defined latest in the abi expected to be used") + } + + // Method and event have the same name + abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"received","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` + abi, err = JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + if err = abi.UnpackIntoMap(receivedMap, "received", data); err == nil { + t.Error("naming conflict between an event and a method; error expected") + } + + // Conflict is case sensitive + abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"received","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"Received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` + abi, err = JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + expectedReceivedMap := map[string]interface{}{ + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err = abi.UnpackIntoMap(receivedMap, "Received", data); err != nil { + t.Error(err) + } + if len(receivedMap) != 3 { + t.Error("unpacked `received` map expected to have length 3") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked `received` map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked `received` map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked `received` map does not match expected map") + } +} + +func TestABI_MethodById(t *testing.T) { + const abiJSON = `[ + {"type":"function","name":"receive","constant":false,"inputs":[{"name":"memo","type":"bytes"}],"outputs":[],"payable":true,"stateMutability":"payable"}, + {"type":"event","name":"received","anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}]}, + {"type":"function","name":"fixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"}]}, + {"type":"function","name":"fixedArrBytes","constant":true,"inputs":[{"name":"str","type":"bytes"},{"name":"fixedArr","type":"uint256[2]"}]}, + {"type":"function","name":"mixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"}]}, + {"type":"function","name":"doubleFixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"fixedArr2","type":"uint256[3]"}]}, + {"type":"function","name":"multipleMixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"},{"name":"fixedArr2","type":"uint256[3]"}]}, + {"type":"function","name":"balance","constant":true}, + {"type":"function","name":"send","constant":false,"inputs":[{"name":"amount","type":"uint256"}]}, + {"type":"function","name":"test","constant":false,"inputs":[{"name":"number","type":"uint32"}]}, + {"type":"function","name":"string","constant":false,"inputs":[{"name":"inputs","type":"string"}]}, + {"type":"function","name":"bool","constant":false,"inputs":[{"name":"inputs","type":"bool"}]}, + {"type":"function","name":"address","constant":false,"inputs":[{"name":"inputs","type":"address"}]}, + {"type":"function","name":"uint64[2]","constant":false,"inputs":[{"name":"inputs","type":"uint64[2]"}]}, + {"type":"function","name":"uint64[]","constant":false,"inputs":[{"name":"inputs","type":"uint64[]"}]}, + {"type":"function","name":"foo","constant":false,"inputs":[{"name":"inputs","type":"uint32"}]}, + {"type":"function","name":"bar","constant":false,"inputs":[{"name":"inputs","type":"uint32"},{"name":"string","type":"uint16"}]}, + {"type":"function","name":"_slice","constant":false,"inputs":[{"name":"inputs","type":"uint32[2]"}]}, + {"type":"function","name":"__slice256","constant":false,"inputs":[{"name":"inputs","type":"uint256[2]"}]}, + {"type":"function","name":"sliceAddress","constant":false,"inputs":[{"name":"inputs","type":"address[]"}]}, + {"type":"function","name":"sliceMultiAddress","constant":false,"inputs":[{"name":"a","type":"address[]"},{"name":"b","type":"address[]"}]} + ] +` + abi, err := JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + for name, m := range abi.Methods { + a := fmt.Sprintf("%v", m) + m2, err := abi.MethodById(m.Id()) + if err != nil { + t.Fatalf("Failed to look up ABI method: %v", err) + } + b := fmt.Sprintf("%v", m2) + if a != b { + t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, common.ToHex(m.Id())) + } + } + // Also test empty + if _, err := abi.MethodById([]byte{0x00}); err == nil { + t.Errorf("Expected error, too short to decode data") + } + if _, err := abi.MethodById([]byte{}); err == nil { + t.Errorf("Expected error, too short to decode data") + } + if _, err := abi.MethodById(nil); err == nil { + t.Errorf("Expected error, nil is short to decode data") + } +} diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go new file mode 100644 index 000000000..501cb1621 --- /dev/null +++ b/accounts/abi/argument.go @@ -0,0 +1,354 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// Argument holds the name of the argument and the corresponding type. +// Types are used when packing and testing arguments. +type Argument struct { + Name string + Type Type + Indexed bool // indexed is only used by events +} + +type Arguments []Argument + +type ArgumentMarshaling struct { + Name string + Type string + Components []ArgumentMarshaling + Indexed bool +} + +// UnmarshalJSON implements json.Unmarshaler interface +func (argument *Argument) UnmarshalJSON(data []byte) error { + var arg ArgumentMarshaling + err := json.Unmarshal(data, &arg) + if err != nil { + return fmt.Errorf("argument json err: %v", err) + } + + argument.Type, err = NewType(arg.Type, arg.Components) + if err != nil { + return err + } + argument.Name = arg.Name + argument.Indexed = arg.Indexed + + return nil +} + +// LengthNonIndexed returns the number of arguments when not counting 'indexed' ones. Only events +// can ever have 'indexed' arguments, it should always be false on arguments for method input/output +func (arguments Arguments) LengthNonIndexed() int { + out := 0 + for _, arg := range arguments { + if !arg.Indexed { + out++ + } + } + return out +} + +// NonIndexed returns the arguments with indexed arguments filtered out +func (arguments Arguments) NonIndexed() Arguments { + var ret []Argument + for _, arg := range arguments { + if !arg.Indexed { + ret = append(ret, arg) + } + } + return ret +} + +// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[] +func (arguments Arguments) isTuple() bool { + return len(arguments) > 1 +} + +// Unpack performs the operation hexdata -> Go format +func (arguments Arguments) Unpack(v interface{}, data []byte) error { + // make sure the passed value is arguments pointer + if reflect.Ptr != reflect.ValueOf(v).Kind() { + return fmt.Errorf("abi: Unpack(non-pointer %T)", v) + } + marshalledValues, err := arguments.UnpackValues(data) + if err != nil { + return err + } + if arguments.isTuple() { + return arguments.unpackTuple(v, marshalledValues) + } + return arguments.unpackAtomic(v, marshalledValues[0]) +} + +// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value +func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error { + marshalledValues, err := arguments.UnpackValues(data) + if err != nil { + return err + } + + return arguments.unpackIntoMap(v, marshalledValues) +} + +// unpack sets the unmarshalled value to go format. +// Note the dst here must be settable. +func unpack(t *Type, dst interface{}, src interface{}) error { + var ( + dstVal = reflect.ValueOf(dst).Elem() + srcVal = reflect.ValueOf(src) + ) + + if t.T != TupleTy && !((t.T == SliceTy || t.T == ArrayTy) && t.Elem.T == TupleTy) { + return set(dstVal, srcVal) + } + + switch t.T { + case TupleTy: + if dstVal.Kind() != reflect.Struct { + return fmt.Errorf("abi: invalid dst value for unpack, want struct, got %s", dstVal.Kind()) + } + fieldmap, err := mapArgNamesToStructFields(t.TupleRawNames, dstVal) + if err != nil { + return err + } + for i, elem := range t.TupleElems { + fname := fieldmap[t.TupleRawNames[i]] + field := dstVal.FieldByName(fname) + if !field.IsValid() { + return fmt.Errorf("abi: field %s can't found in the given value", t.TupleRawNames[i]) + } + if err := unpack(elem, field.Addr().Interface(), srcVal.Field(i).Interface()); err != nil { + return err + } + } + return nil + case SliceTy: + if dstVal.Kind() != reflect.Slice { + return fmt.Errorf("abi: invalid dst value for unpack, want slice, got %s", dstVal.Kind()) + } + slice := reflect.MakeSlice(dstVal.Type(), srcVal.Len(), srcVal.Len()) + for i := 0; i < slice.Len(); i++ { + if err := unpack(t.Elem, slice.Index(i).Addr().Interface(), srcVal.Index(i).Interface()); err != nil { + return err + } + } + dstVal.Set(slice) + case ArrayTy: + if dstVal.Kind() != reflect.Array { + return fmt.Errorf("abi: invalid dst value for unpack, want array, got %s", dstVal.Kind()) + } + array := reflect.New(dstVal.Type()).Elem() + for i := 0; i < array.Len(); i++ { + if err := unpack(t.Elem, array.Index(i).Addr().Interface(), srcVal.Index(i).Interface()); err != nil { + return err + } + } + dstVal.Set(array) + } + return nil +} + +// unpackIntoMap unpacks marshalledValues into the provided map[string]interface{} +func (arguments Arguments) unpackIntoMap(v map[string]interface{}, marshalledValues []interface{}) error { + // Make sure map is not nil + if v == nil { + return fmt.Errorf("abi: cannot unpack into a nil map") + } + + for i, arg := range arguments.NonIndexed() { + v[arg.Name] = marshalledValues[i] + } + return nil +} + +// unpackAtomic unpacks ( hexdata -> go ) a single value +func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error { + if arguments.LengthNonIndexed() == 0 { + return nil + } + argument := arguments.NonIndexed()[0] + elem := reflect.ValueOf(v).Elem() + + if elem.Kind() == reflect.Struct { + fieldmap, err := mapArgNamesToStructFields([]string{argument.Name}, elem) + if err != nil { + return err + } + field := elem.FieldByName(fieldmap[argument.Name]) + if !field.IsValid() { + return fmt.Errorf("abi: field %s can't be found in the given value", argument.Name) + } + return unpack(&argument.Type, field.Addr().Interface(), marshalledValues) + } + return unpack(&argument.Type, elem.Addr().Interface(), marshalledValues) +} + +// unpackTuple unpacks ( hexdata -> go ) a batch of values. +func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error { + var ( + value = reflect.ValueOf(v).Elem() + typ = value.Type() + kind = value.Kind() + ) + if err := requireUnpackKind(value, typ, kind, arguments); err != nil { + return err + } + + // If the interface is a struct, get of abi->struct_field mapping + var abi2struct map[string]string + if kind == reflect.Struct { + var ( + argNames []string + err error + ) + for _, arg := range arguments.NonIndexed() { + argNames = append(argNames, arg.Name) + } + abi2struct, err = mapArgNamesToStructFields(argNames, value) + if err != nil { + return err + } + } + for i, arg := range arguments.NonIndexed() { + switch kind { + case reflect.Struct: + field := value.FieldByName(abi2struct[arg.Name]) + if !field.IsValid() { + return fmt.Errorf("abi: field %s can't be found in the given value", arg.Name) + } + if err := unpack(&arg.Type, field.Addr().Interface(), marshalledValues[i]); err != nil { + return err + } + case reflect.Slice, reflect.Array: + if value.Len() < i { + return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len()) + } + v := value.Index(i) + if err := requireAssignable(v, reflect.ValueOf(marshalledValues[i])); err != nil { + return err + } + if err := unpack(&arg.Type, v.Addr().Interface(), marshalledValues[i]); err != nil { + return err + } + default: + return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", typ) + } + } + return nil + +} + +// UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification, +// without supplying a struct to unpack into. Instead, this method returns a list containing the +// values. An atomic argument will be a list with one element. +func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { + retval := make([]interface{}, 0, arguments.LengthNonIndexed()) + virtualArgs := 0 + for index, arg := range arguments.NonIndexed() { + marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) + if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) { + // If we have a static array, like [3]uint256, these are coded as + // just like uint256,uint256,uint256. + // This means that we need to add two 'virtual' arguments when + // we count the index from now on. + // + // Array values nested multiple levels deep are also encoded inline: + // [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256 + // + // Calculate the full array size to get the correct offset for the next argument. + // Decrement it by 1, as the normal index increment is still applied. + virtualArgs += getTypeSize(arg.Type)/32 - 1 + } else if arg.Type.T == TupleTy && !isDynamicType(arg.Type) { + // If we have a static tuple, like (uint256, bool, uint256), these are + // coded as just like uint256,bool,uint256 + virtualArgs += getTypeSize(arg.Type)/32 - 1 + } + if err != nil { + return nil, err + } + retval = append(retval, marshalledValue) + } + return retval, nil +} + +// PackValues performs the operation Go format -> Hexdata +// It is the semantic opposite of UnpackValues +func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) { + return arguments.Pack(args...) +} + +// Pack performs the operation Go format -> Hexdata +func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { + // Make sure arguments match up and pack them + abiArgs := arguments + if len(args) != len(abiArgs) { + return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs)) + } + // variable input is the output appended at the end of packed + // output. This is used for strings and bytes types input. + var variableInput []byte + + // input offset is the bytes offset for packed output + inputOffset := 0 + for _, abiArg := range abiArgs { + inputOffset += getTypeSize(abiArg.Type) + } + var ret []byte + for i, a := range args { + input := abiArgs[i] + // pack the input + packed, err := input.Type.pack(reflect.ValueOf(a)) + if err != nil { + return nil, err + } + // check for dynamic types + if isDynamicType(input.Type) { + // set the offset + ret = append(ret, packNum(reflect.ValueOf(inputOffset))...) + // calculate next offset + inputOffset += len(packed) + // append to variable input + variableInput = append(variableInput, packed...) + } else { + // append the packed value to the input + ret = append(ret, packed...) + } + } + // append the variable input at the end of the packed input + ret = append(ret, variableInput...) + + return ret, nil +} + +// ToCamelCase converts an under-score string to a camel-case string +func ToCamelCase(input string) string { + parts := strings.Split(input, "_") + for i, s := range parts { + if len(s) > 0 { + parts[i] = strings.ToUpper(s[:1]) + s[1:] + } + } + return strings.Join(parts, "") +} diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go new file mode 100644 index 000000000..e6bb0c3b5 --- /dev/null +++ b/accounts/abi/bind/auth.go @@ -0,0 +1,62 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bind + +import ( + "crypto/ecdsa" + "errors" + "io" + "io/ioutil" + + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +// NewTransactor is a utility method to easily create a transaction signer from +// an encrypted json key stream and the associated passphrase. +func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { + json, err := ioutil.ReadAll(keyin) + if err != nil { + return nil, err + } + key, err := keystore.DecryptKey(json, passphrase) + if err != nil { + return nil, err + } + return NewKeyedTransactor(key.PrivateKey), nil +} + +// NewKeyedTransactor is a utility method to easily create a transaction signer +// from a single private key. +func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { + keyAddr := crypto.PubkeyToAddress(key.PublicKey) + return &TransactOpts{ + From: keyAddr, + Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != keyAddr { + return nil, errors.New("not authorized to sign this account") + } + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + } +} diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go new file mode 100644 index 000000000..ca60cc1b4 --- /dev/null +++ b/accounts/abi/bind/backend.go @@ -0,0 +1,112 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bind + +import ( + "context" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +var ( + // ErrNoCode is returned by call and transact operations for which the requested + // recipient contract to operate on does not exist in the state db or does not + // have any code associated with it (i.e. suicided). + ErrNoCode = errors.New("no contract code at given address") + + // This error is raised when attempting to perform a pending state action + // on a backend that doesn't implement PendingContractCaller. + ErrNoPendingState = errors.New("backend does not support pending state") + + // This error is returned by WaitDeployed if contract creation leaves an + // empty contract behind. + ErrNoCodeAfterDeploy = errors.New("no contract code after deployment") +) + +// ContractCaller defines the methods needed to allow operating with contract on a read +// only basis. +type ContractCaller interface { + // CodeAt returns the code of the given account. This is needed to differentiate + // between contract internal errors and the local chain being out of sync. + CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) + // ContractCall executes an Ethereum contract call with the specified data as the + // input. + CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) +} + +// PendingContractCaller defines methods to perform contract calls on the pending state. +// Call will try to discover this interface when access to the pending state is requested. +// If the backend does not support the pending state, Call returns ErrNoPendingState. +type PendingContractCaller interface { + // PendingCodeAt returns the code of the given account in the pending state. + PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) + // PendingCallContract executes an Ethereum contract call against the pending state. + PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) +} + +// ContractTransactor defines the methods needed to allow operating with contract +// on a write only basis. Beside the transacting method, the remainder are helpers +// used when the user does not provide some needed values, but rather leaves it up +// to the transactor to decide. +type ContractTransactor interface { + // PendingCodeAt returns the code of the given account in the pending state. + PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) + // PendingNonceAt retrieves the current pending nonce associated with an account. + PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) + // SuggestGasPrice retrieves the currently suggested gas price to allow a timely + // execution of a transaction. + SuggestGasPrice(ctx context.Context) (*big.Int, error) + // EstimateGas tries to estimate the gas needed to execute a specific + // transaction based on the current pending state of the backend blockchain. + // There is no guarantee that this is the true gas limit requirement as other + // transactions may be added or removed by miners, but it should provide a basis + // for setting a reasonable default. + EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) + // SendTransaction injects the transaction into the pending pool for execution. + SendTransaction(ctx context.Context, tx *types.Transaction) error +} + +// ContractFilterer defines the methods needed to access log events using one-off +// queries or continuous event subscriptions. +type ContractFilterer interface { + // FilterLogs executes a log filter operation, blocking during execution and + // returning all the results in one batch. + // + // TODO(karalabe): Deprecate when the subscription one can return past data too. + FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) + + // SubscribeFilterLogs creates a background log filtering operation, returning + // a subscription immediately, which can be used to stream the found events. + SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) +} + +// DeployBackend wraps the operations needed by WaitMined and WaitDeployed. +type DeployBackend interface { + TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) + CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) +} + +// ContractBackend defines the methods needed to work with contracts on a read-write basis. +type ContractBackend interface { + ContractCaller + ContractTransactor + ContractFilterer +} diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go new file mode 100644 index 000000000..ac4602fe3 --- /dev/null +++ b/accounts/abi/bind/backends/simulated.go @@ -0,0 +1,505 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package backends + +import ( + "context" + "errors" + "fmt" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend. +var _ bind.ContractBackend = (*SimulatedBackend)(nil) + +var errBlockNumberUnsupported = errors.New("SimulatedBackend cannot access blocks other than the latest block") +var errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction") + +// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in +// the background. Its main purpose is to allow easily testing contract bindings. +type SimulatedBackend struct { + database ethdb.Database // In memory database to store our testing data + blockchain *core.BlockChain // Ethereum blockchain to handle the consensus + + mu sync.Mutex + pendingBlock *types.Block // Currently pending block that will be imported on request + pendingState *state.StateDB // Currently pending state that will be the active on on request + + events *filters.EventSystem // Event system for filtering log events live + + config *params.ChainConfig +} + +// NewSimulatedBackend creates a new binding backend using a simulated blockchain +// for testing purposes. +func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { + database := rawdb.NewMemoryDatabase() + genesis := core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: gasLimit, Alloc: alloc} + genesis.MustCommit(database) + blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}, nil) + + backend := &SimulatedBackend{ + database: database, + blockchain: blockchain, + config: genesis.Config, + events: filters.NewEventSystem(new(event.TypeMux), &filterBackend{database, blockchain}, false), + } + backend.rollback() + return backend +} + +// Commit imports all the pending transactions as a single block and starts a +// fresh new state. +func (b *SimulatedBackend) Commit() { + b.mu.Lock() + defer b.mu.Unlock() + + if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil { + panic(err) // This cannot happen unless the simulator is wrong, fail in that case + } + b.rollback() +} + +// Rollback aborts all pending transactions, reverting to the last committed state. +func (b *SimulatedBackend) Rollback() { + b.mu.Lock() + defer b.mu.Unlock() + + b.rollback() +} + +func (b *SimulatedBackend) rollback() { + blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {}) + statedb, _ := b.blockchain.State() + + b.pendingBlock = blocks[0] + b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database()) +} + +// CodeAt returns the code associated with a certain account in the blockchain. +func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { + return nil, errBlockNumberUnsupported + } + statedb, _ := b.blockchain.State() + return statedb.GetCode(contract), nil +} + +// BalanceAt returns the wei balance of a certain account in the blockchain. +func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (*big.Int, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { + return nil, errBlockNumberUnsupported + } + statedb, _ := b.blockchain.State() + return statedb.GetBalance(contract), nil +} + +// NonceAt returns the nonce of a certain account in the blockchain. +func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (uint64, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { + return 0, errBlockNumberUnsupported + } + statedb, _ := b.blockchain.State() + return statedb.GetNonce(contract), nil +} + +// StorageAt returns the value of key in the storage of an account in the blockchain. +func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { + return nil, errBlockNumberUnsupported + } + statedb, _ := b.blockchain.State() + val := statedb.GetState(contract, key) + return val[:], nil +} + +// TransactionReceipt returns the receipt of a transaction. +func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash, b.config) + return receipt, nil +} + +// TransactionByHash checks the pool of pending transactions in addition to the +// blockchain. The isPending return value indicates whether the transaction has been +// mined yet. Note that the transaction may not be part of the canonical chain even if +// it's not pending. +func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + b.mu.Lock() + defer b.mu.Unlock() + + tx := b.pendingBlock.Transaction(txHash) + if tx != nil { + return tx, true, nil + } + tx, _, _, _ = rawdb.ReadTransaction(b.database, txHash) + if tx != nil { + return tx, false, nil + } + return nil, false, ethereum.NotFound +} + +// PendingCodeAt returns the code associated with an account in the pending state. +func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + return b.pendingState.GetCode(contract), nil +} + +// CallContract executes a contract call. +func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { + return nil, errBlockNumberUnsupported + } + state, err := b.blockchain.State() + if err != nil { + return nil, err + } + rval, _, _, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state) + return rval, err +} + +// PendingCallContract executes a contract call on the pending state. +func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot()) + + rval, _, _, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) + return rval, err +} + +// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving +// the nonce currently pending for the account. +func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + b.mu.Lock() + defer b.mu.Unlock() + + return b.pendingState.GetOrNewStateObject(account).Nonce(), nil +} + +// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated +// chain doesn't have miners, we just return a gas price of 1 for any call. +func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + return big.NewInt(1), nil +} + +// EstimateGas executes the requested code against the currently pending block/state and +// returns the used amount of gas. +func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { + b.mu.Lock() + defer b.mu.Unlock() + + // Determine the lowest and highest possible gas limits to binary search in between + var ( + lo uint64 = params.TxGas - 1 + hi uint64 + cap uint64 + ) + if call.Gas >= params.TxGas { + hi = call.Gas + } else { + hi = b.pendingBlock.GasLimit() + } + cap = hi + + // Create a helper to check if a gas allowance results in an executable transaction + executable := func(gas uint64) bool { + call.Gas = gas + + snapshot := b.pendingState.Snapshot() + _, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) + b.pendingState.RevertToSnapshot(snapshot) + + if err != nil || failed { + return false + } + return true + } + // Execute the binary search and hone in on an executable gas limit + for lo+1 < hi { + mid := (hi + lo) / 2 + if !executable(mid) { + lo = mid + } else { + hi = mid + } + } + // Reject the transaction as invalid if it still fails at the highest allowance + if hi == cap { + if !executable(hi) { + return 0, errGasEstimationFailed + } + } + return hi, nil +} + +// callContract implements common code between normal and pending contract calls. +// state is modified during execution, make sure to copy it if necessary. +func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) { + // Ensure message is initialized properly. + if call.GasPrice == nil { + call.GasPrice = big.NewInt(1) + } + if call.Gas == 0 { + call.Gas = 50000000 + } + if call.Value == nil { + call.Value = new(big.Int) + } + // Set infinite balance to the fake caller account. + from := statedb.GetOrNewStateObject(call.From) + from.SetBalance(math.MaxBig256) + // Execute the call. + msg := callmsg{call} + + evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain, nil) + // Create a new environment which holds all relevant information + // about the transaction and calling mechanisms. + vmenv := vm.NewEVM(evmContext, statedb, b.config, vm.Config{}) + gaspool := new(core.GasPool).AddGas(math.MaxUint64) + + return core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() +} + +// SendTransaction updates the pending block to include the given transaction. +// It panics if the transaction is invalid. +func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { + b.mu.Lock() + defer b.mu.Unlock() + + sender, err := types.Sender(types.HomesteadSigner{}, tx) + if err != nil { + panic(fmt.Errorf("invalid transaction: %v", err)) + } + nonce := b.pendingState.GetNonce(sender) + if tx.Nonce() != nonce { + panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce)) + } + + blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + for _, tx := range b.pendingBlock.Transactions() { + block.AddTxWithChain(b.blockchain, tx) + } + block.AddTxWithChain(b.blockchain, tx) + }) + statedb, _ := b.blockchain.State() + + b.pendingBlock = blocks[0] + b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database()) + return nil +} + +// FilterLogs executes a log filter operation, blocking during execution and +// returning all the results in one batch. +// +// TODO(karalabe): Deprecate when the subscription one can return past data too. +func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) { + var filter *filters.Filter + if query.BlockHash != nil { + // Block filter requested, construct a single-shot filter + filter = filters.NewBlockFilter(&filterBackend{b.database, b.blockchain}, *query.BlockHash, query.Addresses, query.Topics) + } else { + // Initialize unset filter boundaried to run from genesis to chain head + from := int64(0) + if query.FromBlock != nil { + from = query.FromBlock.Int64() + } + to := int64(-1) + if query.ToBlock != nil { + to = query.ToBlock.Int64() + } + // Construct the range filter + filter = filters.NewRangeFilter(&filterBackend{b.database, b.blockchain}, from, to, query.Addresses, query.Topics) + } + // Run the filter and return all the logs + logs, err := filter.Logs(ctx) + if err != nil { + return nil, err + } + res := make([]types.Log, len(logs)) + for i, log := range logs { + res[i] = *log + } + return res, nil +} + +// SubscribeFilterLogs creates a background log filtering operation, returning a +// subscription immediately, which can be used to stream the found events. +func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + // Subscribe to contract events + sink := make(chan []*types.Log) + + sub, err := b.events.SubscribeLogs(query, sink) + if err != nil { + return nil, err + } + // Since we're getting logs in batches, we need to flatten them into a plain stream + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case logs := <-sink: + for _, log := range logs { + select { + case ch <- *log: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// AdjustTime adds a time shift to the simulated clock. +func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { + b.mu.Lock() + defer b.mu.Unlock() + blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + for _, tx := range b.pendingBlock.Transactions() { + block.AddTx(tx) + } + block.OffsetTime(int64(adjustment.Seconds())) + }) + statedb, _ := b.blockchain.State() + + b.pendingBlock = blocks[0] + b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database()) + + return nil +} + +// callmsg implements core.Message to allow passing it as a transaction simulator. +type callmsg struct { + ethereum.CallMsg +} + +func (m callmsg) From() common.Address { return m.CallMsg.From } +func (m callmsg) Nonce() uint64 { return 0 } +func (m callmsg) CheckNonce() bool { return false } +func (m callmsg) To() *common.Address { return m.CallMsg.To } +func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } +func (m callmsg) Gas() uint64 { return m.CallMsg.Gas } +func (m callmsg) Value() *big.Int { return m.CallMsg.Value } +func (m callmsg) Data() []byte { return m.CallMsg.Data } + +// filterBackend implements filters.Backend to support filtering for logs without +// taking bloom-bits acceleration structures into account. +type filterBackend struct { + db ethdb.Database + bc *core.BlockChain +} + +func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db } +func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") } + +func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumber) (*types.Header, error) { + if block == rpc.LatestBlockNumber { + return fb.bc.CurrentHeader(), nil + } + return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil +} + +func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + return fb.bc.GetHeaderByHash(hash), nil +} + +func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { + number := rawdb.ReadHeaderNumber(fb.db, hash) + if number == nil { + return nil, nil + } + return rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config()), nil +} + +func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) { + number := rawdb.ReadHeaderNumber(fb.db, hash) + if number == nil { + return nil, nil + } + receipts := rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config()) + if receipts == nil { + return nil, nil + } + logs := make([][]*types.Log, len(receipts)) + for i, receipt := range receipts { + logs[i] = receipt.Logs + } + return logs, nil +} + +func (fb *filterBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { + return event.NewSubscription(func(quit <-chan struct{}) error { + <-quit + return nil + }) +} +func (fb *filterBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { + return fb.bc.SubscribeChainEvent(ch) +} +func (fb *filterBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { + return fb.bc.SubscribeRemovedLogsEvent(ch) +} +func (fb *filterBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { + return fb.bc.SubscribeLogsEvent(ch) +} + +func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 } +func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) { + panic("not supported") +} diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go new file mode 100644 index 000000000..bd75807d7 --- /dev/null +++ b/accounts/abi/bind/backends/simulated_test.go @@ -0,0 +1,82 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package backends_test + +import ( + "context" + "math/big" + "testing" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestSimulatedBackend(t *testing.T) { + var gasLimit uint64 = 8000029 + key, _ := crypto.GenerateKey() // nolint: gosec + auth := bind.NewKeyedTransactor(key) + genAlloc := make(core.GenesisAlloc) + genAlloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(9223372036854775807)} + + sim := backends.NewSimulatedBackend(genAlloc, gasLimit) + + // should return an error if the tx is not found + txHash := common.HexToHash("2") + _, isPending, err := sim.TransactionByHash(context.Background(), txHash) + + if isPending { + t.Fatal("transaction should not be pending") + } + if err != ethereum.NotFound { + t.Fatalf("err should be `ethereum.NotFound` but received %v", err) + } + + // generate a transaction and confirm you can retrieve it + code := `6060604052600a8060106000396000f360606040526008565b00` + var gas uint64 = 3000000 + tx := types.NewContractCreation(0, big.NewInt(0), gas, big.NewInt(1), common.FromHex(code)) + tx, _ = types.SignTx(tx, types.HomesteadSigner{}, key) + + err = sim.SendTransaction(context.Background(), tx) + if err != nil { + t.Fatal("error sending transaction") + } + + txHash = tx.Hash() + _, isPending, err = sim.TransactionByHash(context.Background(), txHash) + if err != nil { + t.Fatalf("error getting transaction with hash: %v", txHash.String()) + } + if !isPending { + t.Fatal("transaction should have pending status") + } + + sim.Commit() + tx, isPending, err = sim.TransactionByHash(context.Background(), txHash) + if err != nil { + t.Fatalf("error getting transaction with hash: %v", txHash.String()) + } + if isPending { + t.Fatal("transaction should not have pending status") + } + +} diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go new file mode 100644 index 000000000..f70f911d3 --- /dev/null +++ b/accounts/abi/bind/base.go @@ -0,0 +1,366 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bind + +import ( + "context" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" +) + +// SignerFn is a signer function callback when a contract requires a method to +// sign the transaction before submission. +type SignerFn func(types.Signer, common.Address, *types.Transaction) (*types.Transaction, error) + +// CallOpts is the collection of options to fine tune a contract call request. +type CallOpts struct { + Pending bool // Whether to operate on the pending state or the last known one + From common.Address // Optional the sender address, otherwise the first account is used + BlockNumber *big.Int // Optional the block number on which the call should be performed + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) +} + +// TransactOpts is the collection of authorization data required to create a +// valid Ethereum transaction. +type TransactOpts struct { + From common.Address // Ethereum account to send the transaction from + Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state) + Signer SignerFn // Method to use for signing the transaction (mandatory) + + Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds) + GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle) + GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate) + + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) +} + +// FilterOpts is the collection of options to fine tune filtering for events +// within a bound contract. +type FilterOpts struct { + Start uint64 // Start of the queried range + End *uint64 // End of the range (nil = latest) + + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) +} + +// WatchOpts is the collection of options to fine tune subscribing for events +// within a bound contract. +type WatchOpts struct { + Start *uint64 // Start of the queried range (nil = latest) + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) +} + +// BoundContract is the base wrapper object that reflects a contract on the +// Ethereum network. It contains a collection of methods that are used by the +// higher level contract bindings to operate. +type BoundContract struct { + address common.Address // Deployment address of the contract on the Ethereum blockchain + abi abi.ABI // Reflect based ABI to access the correct Ethereum methods + caller ContractCaller // Read interface to interact with the blockchain + transactor ContractTransactor // Write interface to interact with the blockchain + filterer ContractFilterer // Event filtering to interact with the blockchain +} + +// NewBoundContract creates a low level contract interface through which calls +// and transactions may be made through. +func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor, filterer ContractFilterer) *BoundContract { + return &BoundContract{ + address: address, + abi: abi, + caller: caller, + transactor: transactor, + filterer: filterer, + } +} + +// DeployContract deploys a contract onto the Ethereum blockchain and binds the +// deployment address with a Go wrapper. +func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (common.Address, *types.Transaction, *BoundContract, error) { + // Otherwise try to deploy the contract + c := NewBoundContract(common.Address{}, abi, backend, backend, backend) + + input, err := c.abi.Pack("", params...) + if err != nil { + return common.Address{}, nil, nil, err + } + tx, err := c.transact(opts, nil, append(bytecode, input...)) + if err != nil { + return common.Address{}, nil, nil, err + } + c.address = crypto.CreateAddress(opts.From, tx.Nonce()) + return c.address, tx, c, nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, params ...interface{}) error { + // Don't crash on a lazy user + if opts == nil { + opts = new(CallOpts) + } + // Pack the input, call and unpack the results + input, err := c.abi.Pack(method, params...) + if err != nil { + return err + } + var ( + msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input} + ctx = ensureContext(opts.Context) + code []byte + output []byte + ) + if opts.Pending { + pb, ok := c.caller.(PendingContractCaller) + if !ok { + return ErrNoPendingState + } + output, err = pb.PendingCallContract(ctx, msg) + if err == nil && len(output) == 0 { + // Make sure we have a contract to operate on, and bail out otherwise. + if code, err = pb.PendingCodeAt(ctx, c.address); err != nil { + return err + } else if len(code) == 0 { + return ErrNoCode + } + } + } else { + output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber) + if err == nil && len(output) == 0 { + // Make sure we have a contract to operate on, and bail out otherwise. + if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil { + return err + } else if len(code) == 0 { + return ErrNoCode + } + } + } + if err != nil { + return err + } + return c.abi.Unpack(result, method, output) +} + +// Transact invokes the (paid) contract method with params as input values. +func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + // Otherwise pack up the parameters and invoke the contract + input, err := c.abi.Pack(method, params...) + if err != nil { + return nil, err + } + return c.transact(opts, &c.address, input) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) { + return c.transact(opts, &c.address, nil) +} + +// transact executes an actual transaction invocation, first deriving any missing +// authorization fields, and then scheduling the transaction for execution. +func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) { + var err error + + // Ensure a valid value field and resolve the account nonce + value := opts.Value + if value == nil { + value = new(big.Int) + } + var nonce uint64 + if opts.Nonce == nil { + nonce, err = c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From) + if err != nil { + return nil, fmt.Errorf("failed to retrieve account nonce: %v", err) + } + } else { + nonce = opts.Nonce.Uint64() + } + // Figure out the gas allowance and gas price values + gasPrice := opts.GasPrice + if gasPrice == nil { + gasPrice, err = c.transactor.SuggestGasPrice(ensureContext(opts.Context)) + if err != nil { + return nil, fmt.Errorf("failed to suggest gas price: %v", err) + } + } + gasLimit := opts.GasLimit + if gasLimit == 0 { + // Gas estimation cannot succeed without code for method invocations + if contract != nil { + if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil { + return nil, err + } else if len(code) == 0 { + return nil, ErrNoCode + } + } + // If the contract surely has code (or code is not needed), estimate the transaction + msg := ethereum.CallMsg{From: opts.From, To: contract, Value: value, Data: input} + gasLimit, err = c.transactor.EstimateGas(ensureContext(opts.Context), msg) + if err != nil { + return nil, fmt.Errorf("failed to estimate gas needed: %v", err) + } + } + // Create the transaction, sign it and schedule it for execution + var rawTx *types.Transaction + if contract == nil { + rawTx = types.NewContractCreation(nonce, value, gasLimit, gasPrice, input) + } else { + rawTx = types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input) + } + if opts.Signer == nil { + return nil, errors.New("no signer to authorize the transaction with") + } + signedTx, err := opts.Signer(types.HomesteadSigner{}, opts.From, rawTx) + if err != nil { + return nil, err + } + if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { + return nil, err + } + return signedTx, nil +} + +// FilterLogs filters contract logs for past blocks, returning the necessary +// channels to construct a strongly typed bound iterator on top of them. +func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) { + // Don't crash on a lazy user + if opts == nil { + opts = new(FilterOpts) + } + // Append the event selector to the query parameters and construct the topic set + query = append([][]interface{}{{c.abi.Events[name].Id()}}, query...) + + topics, err := makeTopics(query...) + if err != nil { + return nil, nil, err + } + // Start the background filtering + logs := make(chan types.Log, 128) + + config := ethereum.FilterQuery{ + Addresses: []common.Address{c.address}, + Topics: topics, + FromBlock: new(big.Int).SetUint64(opts.Start), + } + if opts.End != nil { + config.ToBlock = new(big.Int).SetUint64(*opts.End) + } + /* TODO(karalabe): Replace the rest of the method below with this when supported + sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) + */ + buff, err := c.filterer.FilterLogs(ensureContext(opts.Context), config) + if err != nil { + return nil, nil, err + } + sub, err := event.NewSubscription(func(quit <-chan struct{}) error { + for _, log := range buff { + select { + case logs <- log: + case <-quit: + return nil + } + } + return nil + }), nil + + if err != nil { + return nil, nil, err + } + return logs, sub, nil +} + +// WatchLogs filters subscribes to contract logs for future blocks, returning a +// subscription object that can be used to tear down the watcher. +func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) { + // Don't crash on a lazy user + if opts == nil { + opts = new(WatchOpts) + } + // Append the event selector to the query parameters and construct the topic set + query = append([][]interface{}{{c.abi.Events[name].Id()}}, query...) + + topics, err := makeTopics(query...) + if err != nil { + return nil, nil, err + } + // Start the background filtering + logs := make(chan types.Log, 128) + + config := ethereum.FilterQuery{ + Addresses: []common.Address{c.address}, + Topics: topics, + } + if opts.Start != nil { + config.FromBlock = new(big.Int).SetUint64(*opts.Start) + } + sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) + if err != nil { + return nil, nil, err + } + return logs, sub, nil +} + +// UnpackLog unpacks a retrieved log into the provided output structure. +func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) error { + if len(log.Data) > 0 { + if err := c.abi.Unpack(out, event, log.Data); err != nil { + return err + } + } + var indexed abi.Arguments + for _, arg := range c.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + return parseTopics(out, indexed, log.Topics[1:]) +} + +// UnpackLogIntoMap unpacks a retrieved log into the provided map. +func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event string, log types.Log) error { + if len(log.Data) > 0 { + if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil { + return err + } + } + var indexed abi.Arguments + for _, arg := range c.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + return parseTopicsIntoMap(out, indexed, log.Topics[1:]) +} + +// ensureContext is a helper method to ensure a context is not nil, even if the +// user specified it as such. +func ensureContext(ctx context.Context) context.Context { + if ctx == nil { + return context.TODO() + } + return ctx +} diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go new file mode 100644 index 000000000..f65c9e9b4 --- /dev/null +++ b/accounts/abi/bind/base_test.go @@ -0,0 +1,373 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bind_test + +import ( + "bytes" + "context" + "math/big" + "strings" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" +) + +type mockCaller struct { + codeAtBlockNumber *big.Int + callContractBlockNumber *big.Int +} + +func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + mc.codeAtBlockNumber = blockNumber + return []byte{1, 2, 3}, nil +} + +func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + mc.callContractBlockNumber = blockNumber + return nil, nil +} +func TestPassingBlockNumber(t *testing.T) { + + mc := &mockCaller{} + + bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{ + Methods: map[string]abi.Method{ + "something": { + Name: "something", + Outputs: abi.Arguments{}, + }, + }, + }, mc, nil, nil) + var ret string + + blockNumber := big.NewInt(42) + + bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, &ret, "something") + + if mc.callContractBlockNumber != blockNumber { + t.Fatalf("CallContract() was not passed the block number") + } + + if mc.codeAtBlockNumber != blockNumber { + t.Fatalf("CodeAt() was not passed the block number") + } + + bc.Call(&bind.CallOpts{}, &ret, "something") + + if mc.callContractBlockNumber != nil { + t.Fatalf("CallContract() was passed a block number when it should not have been") + } + + if mc.codeAtBlockNumber != nil { + t.Fatalf("CodeAt() was passed a block number when it should not have been") + } +} + +const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158" + +func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) { + hash := crypto.Keccak256Hash([]byte("testName")) + mockLog := types.Log{ + Address: common.HexToAddress("0x0"), + Topics: []common.Hash{ + common.HexToHash("0x0"), + hash, + }, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: common.HexToHash("0x0"), + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, + } + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + receivedMap := make(map[string]interface{}) + expectedReceivedMap := map[string]interface{}{ + "name": hash, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { + t.Error(err) + } + + if len(receivedMap) != 4 { + t.Fatal("unpacked map expected to have length 4") + } + if receivedMap["name"] != expectedReceivedMap["name"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked map does not match expected map") + } +} + +func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) { + sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"}) + if err != nil { + t.Fatal(err) + } + hash := crypto.Keccak256Hash(sliceBytes) + mockLog := types.Log{ + Address: common.HexToAddress("0x0"), + Topics: []common.Hash{ + common.HexToHash("0x0"), + hash, + }, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: common.HexToHash("0x0"), + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, + } + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"names","type":"string[]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + receivedMap := make(map[string]interface{}) + expectedReceivedMap := map[string]interface{}{ + "names": hash, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { + t.Error(err) + } + + if len(receivedMap) != 4 { + t.Fatal("unpacked map expected to have length 4") + } + if receivedMap["names"] != expectedReceivedMap["names"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked map does not match expected map") + } +} + +func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) { + arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")}) + if err != nil { + t.Fatal(err) + } + hash := crypto.Keccak256Hash(arrBytes) + mockLog := types.Log{ + Address: common.HexToAddress("0x0"), + Topics: []common.Hash{ + common.HexToHash("0x0"), + hash, + }, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: common.HexToHash("0x0"), + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, + } + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"addresses","type":"address[2]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + receivedMap := make(map[string]interface{}) + expectedReceivedMap := map[string]interface{}{ + "addresses": hash, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { + t.Error(err) + } + + if len(receivedMap) != 4 { + t.Fatal("unpacked map expected to have length 4") + } + if receivedMap["addresses"] != expectedReceivedMap["addresses"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked map does not match expected map") + } +} + +func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) { + mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2") + addrBytes := mockAddress.Bytes() + hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)")) + functionSelector := hash[:4] + functionTyBytes := append(addrBytes, functionSelector...) + var functionTy [24]byte + copy(functionTy[:], functionTyBytes[0:24]) + mockLog := types.Log{ + Address: common.HexToAddress("0x0"), + Topics: []common.Hash{ + common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"), + common.BytesToHash(functionTyBytes), + }, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"), + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, + } + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"function","type":"function"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + receivedMap := make(map[string]interface{}) + expectedReceivedMap := map[string]interface{}{ + "function": functionTy, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { + t.Error(err) + } + + if len(receivedMap) != 4 { + t.Fatal("unpacked map expected to have length 4") + } + if receivedMap["function"] != expectedReceivedMap["function"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked map does not match expected map") + } +} + +func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) { + byts := []byte{1, 2, 3, 4, 5} + hash := crypto.Keccak256Hash(byts) + mockLog := types.Log{ + Address: common.HexToAddress("0x0"), + Topics: []common.Hash{ + common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"), + hash, + }, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"), + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, + } + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"content","type":"bytes"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + receivedMap := make(map[string]interface{}) + expectedReceivedMap := map[string]interface{}{ + "content": hash, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { + t.Error(err) + } + + if len(receivedMap) != 4 { + t.Fatal("unpacked map expected to have length 4") + } + if receivedMap["content"] != expectedReceivedMap["content"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked map does not match expected map") + } +} + +func TestUnpackIntoMapNamingConflict(t *testing.T) { + hash := crypto.Keccak256Hash([]byte("testName")) + mockLog := types.Log{ + Address: common.HexToAddress("0x0"), + Topics: []common.Hash{ + common.HexToHash("0x0"), + hash, + }, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: common.HexToHash("0x0"), + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, + } + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + receivedMap := make(map[string]interface{}) + if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err == nil { + t.Error("naming conflict between two events; error expected") + } +} diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go new file mode 100644 index 000000000..5ee30d024 --- /dev/null +++ b/accounts/abi/bind/bind.go @@ -0,0 +1,424 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package bind generates Ethereum contract Go bindings. +// +// Detailed usage document and tutorial available on the go-ethereum Wiki page: +// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts +package bind + +import ( + "bytes" + "fmt" + "go/format" + "regexp" + "strings" + "text/template" + "unicode" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +// Lang is a target programming language selector to generate bindings for. +type Lang int + +const ( + LangGo Lang = iota + LangJava + LangObjC +) + +// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant +// to be used as is in client code, but rather as an intermediate struct which +// enforces compile time type safety and naming convention opposed to having to +// manually maintain hard coded strings that break on runtime. +func Bind(types []string, abis []string, bytecodes []string, pkg string, lang Lang) (string, error) { + // Process each individual contract requested binding + contracts := make(map[string]*tmplContract) + + for i := 0; i < len(types); i++ { + // Parse the actual ABI to generate the binding for + evmABI, err := abi.JSON(strings.NewReader(abis[i])) + if err != nil { + return "", err + } + // Strip any whitespace from the JSON ABI + strippedABI := strings.Map(func(r rune) rune { + if unicode.IsSpace(r) { + return -1 + } + return r + }, abis[i]) + + // Extract the call and transact methods; events; and sort them alphabetically + var ( + calls = make(map[string]*tmplMethod) + transacts = make(map[string]*tmplMethod) + events = make(map[string]*tmplEvent) + ) + for _, original := range evmABI.Methods { + // Normalize the method for capital cases and non-anonymous inputs/outputs + normalized := original + normalized.Name = methodNormalizer[lang](original.Name) + + normalized.Inputs = make([]abi.Argument, len(original.Inputs)) + copy(normalized.Inputs, original.Inputs) + for j, input := range normalized.Inputs { + if input.Name == "" { + normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) + } + } + normalized.Outputs = make([]abi.Argument, len(original.Outputs)) + copy(normalized.Outputs, original.Outputs) + for j, output := range normalized.Outputs { + if output.Name != "" { + normalized.Outputs[j].Name = capitalise(output.Name) + } + } + // Append the methods to the call or transact lists + if original.Const { + calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + } else { + transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + } + } + for _, original := range evmABI.Events { + // Skip anonymous events as they don't support explicit filtering + if original.Anonymous { + continue + } + // Normalize the event for capital cases and non-anonymous outputs + normalized := original + normalized.Name = methodNormalizer[lang](original.Name) + + normalized.Inputs = make([]abi.Argument, len(original.Inputs)) + copy(normalized.Inputs, original.Inputs) + for j, input := range normalized.Inputs { + // Indexed fields are input, non-indexed ones are outputs + if input.Indexed { + if input.Name == "" { + normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) + } + } + } + // Append the event to the accumulator list + events[original.Name] = &tmplEvent{Original: original, Normalized: normalized} + } + contracts[types[i]] = &tmplContract{ + Type: capitalise(types[i]), + InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1), + InputBin: strings.TrimSpace(bytecodes[i]), + Constructor: evmABI.Constructor, + Calls: calls, + Transacts: transacts, + Events: events, + } + } + // Generate the contract template data content and render it + data := &tmplData{ + Package: pkg, + Contracts: contracts, + } + buffer := new(bytes.Buffer) + + funcs := map[string]interface{}{ + "bindtype": bindType[lang], + "bindtopictype": bindTopicType[lang], + "namedtype": namedType[lang], + "capitalise": capitalise, + "decapitalise": decapitalise, + } + tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) + if err := tmpl.Execute(buffer, data); err != nil { + return "", err + } + // For Go bindings pass the code through gofmt to clean it up + if lang == LangGo { + code, err := format.Source(buffer.Bytes()) + if err != nil { + return "", fmt.Errorf("%v\n%s", err, buffer) + } + return string(code), nil + } + // For all others just return as is for now + return buffer.String(), nil +} + +// bindType is a set of type binders that convert Solidity types to some supported +// programming language types. +var bindType = map[Lang]func(kind abi.Type) string{ + LangGo: bindTypeGo, + LangJava: bindTypeJava, +} + +// Helper function for the binding generators. +// It reads the unmatched characters after the inner type-match, +// (since the inner type is a prefix of the total type declaration), +// looks for valid arrays (possibly a dynamic one) wrapping the inner type, +// and returns the sizes of these arrays. +// +// Returned array sizes are in the same order as solidity signatures; inner array size first. +// Array sizes may also be "", indicating a dynamic array. +func wrapArray(stringKind string, innerLen int, innerMapping string) (string, []string) { + remainder := stringKind[innerLen:] + //find all the sizes + matches := regexp.MustCompile(`\[(\d*)\]`).FindAllStringSubmatch(remainder, -1) + parts := make([]string, 0, len(matches)) + for _, match := range matches { + //get group 1 from the regex match + parts = append(parts, match[1]) + } + return innerMapping, parts +} + +// Translates the array sizes to a Go-lang declaration of a (nested) array of the inner type. +// Simply returns the inner type if arraySizes is empty. +func arrayBindingGo(inner string, arraySizes []string) string { + out := "" + //prepend all array sizes, from outer (end arraySizes) to inner (start arraySizes) + for i := len(arraySizes) - 1; i >= 0; i-- { + out += "[" + arraySizes[i] + "]" + } + out += inner + return out +} + +// bindTypeGo converts a Solidity type to a Go one. Since there is no clear mapping +// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly +// mapped will use an upscaled type (e.g. *big.Int). +func bindTypeGo(kind abi.Type) string { + stringKind := kind.String() + innerLen, innerMapping := bindUnnestedTypeGo(stringKind) + return arrayBindingGo(wrapArray(stringKind, innerLen, innerMapping)) +} + +// The inner function of bindTypeGo, this finds the inner type of stringKind. +// (Or just the type itself if it is not an array or slice) +// The length of the matched part is returned, with the translated type. +func bindUnnestedTypeGo(stringKind string) (int, string) { + + switch { + case strings.HasPrefix(stringKind, "address"): + return len("address"), "common.Address" + + case strings.HasPrefix(stringKind, "bytes"): + parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind) + return len(parts[0]), fmt.Sprintf("[%s]byte", parts[1]) + + case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): + parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind) + switch parts[2] { + case "8", "16", "32", "64": + return len(parts[0]), fmt.Sprintf("%sint%s", parts[1], parts[2]) + } + return len(parts[0]), "*big.Int" + + case strings.HasPrefix(stringKind, "bool"): + return len("bool"), "bool" + + case strings.HasPrefix(stringKind, "string"): + return len("string"), "string" + + default: + return len(stringKind), stringKind + } +} + +// Translates the array sizes to a Java declaration of a (nested) array of the inner type. +// Simply returns the inner type if arraySizes is empty. +func arrayBindingJava(inner string, arraySizes []string) string { + // Java array type declarations do not include the length. + return inner + strings.Repeat("[]", len(arraySizes)) +} + +// bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping +// from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly +// mapped will use an upscaled type (e.g. BigDecimal). +func bindTypeJava(kind abi.Type) string { + stringKind := kind.String() + innerLen, innerMapping := bindUnnestedTypeJava(stringKind) + return arrayBindingJava(wrapArray(stringKind, innerLen, innerMapping)) +} + +// The inner function of bindTypeJava, this finds the inner type of stringKind. +// (Or just the type itself if it is not an array or slice) +// The length of the matched part is returned, with the translated type. +func bindUnnestedTypeJava(stringKind string) (int, string) { + + switch { + case strings.HasPrefix(stringKind, "address"): + parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind) + if len(parts) != 2 { + return len(stringKind), stringKind + } + if parts[1] == "" { + return len("address"), "Address" + } + return len(parts[0]), "Addresses" + + case strings.HasPrefix(stringKind, "bytes"): + parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind) + if len(parts) != 2 { + return len(stringKind), stringKind + } + return len(parts[0]), "byte[]" + + case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): + //Note that uint and int (without digits) are also matched, + // these are size 256, and will translate to BigInt (the default). + parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind) + if len(parts) != 3 { + return len(stringKind), stringKind + } + + namedSize := map[string]string{ + "8": "byte", + "16": "short", + "32": "int", + "64": "long", + }[parts[2]] + + //default to BigInt + if namedSize == "" { + namedSize = "BigInt" + } + return len(parts[0]), namedSize + + case strings.HasPrefix(stringKind, "bool"): + return len("bool"), "boolean" + + case strings.HasPrefix(stringKind, "string"): + return len("string"), "String" + + default: + return len(stringKind), stringKind + } +} + +// bindTopicType is a set of type binders that convert Solidity types to some +// supported programming language topic types. +var bindTopicType = map[Lang]func(kind abi.Type) string{ + LangGo: bindTopicTypeGo, + LangJava: bindTopicTypeJava, +} + +// bindTypeGo converts a Solidity topic type to a Go one. It is almost the same +// funcionality as for simple types, but dynamic types get converted to hashes. +func bindTopicTypeGo(kind abi.Type) string { + bound := bindTypeGo(kind) + if bound == "string" || bound == "[]byte" { + bound = "common.Hash" + } + return bound +} + +// bindTypeGo converts a Solidity topic type to a Java one. It is almost the same +// funcionality as for simple types, but dynamic types get converted to hashes. +func bindTopicTypeJava(kind abi.Type) string { + bound := bindTypeJava(kind) + if bound == "String" || bound == "Bytes" { + bound = "Hash" + } + return bound +} + +// namedType is a set of functions that transform language specific types to +// named versions that my be used inside method names. +var namedType = map[Lang]func(string, abi.Type) string{ + LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, + LangJava: namedTypeJava, +} + +// namedTypeJava converts some primitive data types to named variants that can +// be used as parts of method names. +func namedTypeJava(javaKind string, solKind abi.Type) string { + switch javaKind { + case "byte[]": + return "Binary" + case "byte[][]": + return "Binaries" + case "string": + return "String" + case "string[]": + return "Strings" + case "boolean": + return "Bool" + case "boolean[]": + return "Bools" + case "BigInt[]": + return "BigInts" + default: + parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String()) + if len(parts) != 4 { + return javaKind + } + switch parts[2] { + case "8", "16", "32", "64": + if parts[3] == "" { + return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2])) + } + return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2])) + + default: + return javaKind + } + } +} + +// methodNormalizer is a name transformer that modifies Solidity method names to +// conform to target language naming concentions. +var methodNormalizer = map[Lang]func(string) string{ + LangGo: abi.ToCamelCase, + LangJava: decapitalise, +} + +// capitalise makes a camel-case string which starts with an upper case character. +func capitalise(input string) string { + return abi.ToCamelCase(input) +} + +// decapitalise makes a camel-case string which starts with a lower case character. +func decapitalise(input string) string { + if len(input) == 0 { + return input + } + + goForm := abi.ToCamelCase(input) + return strings.ToLower(goForm[:1]) + goForm[1:] +} + +// structured checks whether a list of ABI data types has enough information to +// operate through a proper Go struct or if flat returns are needed. +func structured(args abi.Arguments) bool { + if len(args) < 2 { + return false + } + exists := make(map[string]bool) + for _, out := range args { + // If the name is anonymous, we can't organize into a struct + if out.Name == "" { + return false + } + // If the field name is empty when normalized or collides (var, Var, _var, _Var), + // we can't organize into a struct + field := capitalise(out.Name) + if field == "" || exists[field] { + return false + } + exists[field] = true + } + return true +} diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go new file mode 100644 index 000000000..eae12b3f4 --- /dev/null +++ b/accounts/abi/bind/bind_test.go @@ -0,0 +1,1013 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bind + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +var bindTests = []struct { + name string + contract string + bytecode string + abi string + imports string + tester string +}{ + // Test that the binding is available in combined and separate forms too + { + `Empty`, + `contract NilContract {}`, + `606060405260068060106000396000f3606060405200`, + `[]`, + `"github.com/ethereum/go-ethereum/common"`, + ` + if b, err := NewEmpty(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("combined binding (%v) nil or error (%v) not nil", b, nil) + } + if b, err := NewEmptyCaller(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("caller binding (%v) nil or error (%v) not nil", b, nil) + } + if b, err := NewEmptyTransactor(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("transactor binding (%v) nil or error (%v) not nil", b, nil) + } + `, + }, + // Test that all the official sample contracts bind correctly + { + `Token`, + `https://ethereum.org/token`, + `60606040526040516107fd3803806107fd83398101604052805160805160a05160c051929391820192909101600160a060020a0333166000908152600360209081526040822086905581548551838052601f6002600019610100600186161502019093169290920482018390047f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56390810193919290918801908390106100e857805160ff19168380011785555b506101189291505b8082111561017157600081556001016100b4565b50506002805460ff19168317905550505050610658806101a56000396000f35b828001600101855582156100ac579182015b828111156100ac5782518260005055916020019190600101906100fa565b50508060016000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017557805160ff19168380011785555b506100c89291506100b4565b5090565b82800160010185558215610165579182015b8281111561016557825182600050559160200191906001019061018756606060405236156100775760e060020a600035046306fdde03811461007f57806323b872dd146100dc578063313ce5671461010e57806370a082311461011a57806395d89b4114610132578063a9059cbb1461018e578063cae9ca51146101bd578063dc3080f21461031c578063dd62ed3e14610341575b610365610002565b61036760008054602060026001831615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156104eb5780601f106104c0576101008083540402835291602001916104eb565b6103d5600435602435604435600160a060020a038316600090815260036020526040812054829010156104f357610002565b6103e760025460ff1681565b6103d560043560036020526000908152604090205481565b610367600180546020600282841615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156104eb5780601f106104c0576101008083540402835291602001916104eb565b610365600435602435600160a060020a033316600090815260036020526040902054819010156103f157610002565b60806020604435600481810135601f8101849004909302840160405260608381526103d5948235946024803595606494939101919081908382808284375094965050505050505060006000836004600050600033600160a060020a03168152602001908152602001600020600050600087600160a060020a031681526020019081526020016000206000508190555084905080600160a060020a0316638f4ffcb1338630876040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156102f25780820380516001836020036101000a031916815260200191505b50955050505050506000604051808303816000876161da5a03f11561000257505050509392505050565b6005602090815260043560009081526040808220909252602435815220546103d59081565b60046020818152903560009081526040808220909252602435815220546103d59081565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156103c75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051918252519081900360200190f35b6060908152602090f35b600160a060020a03821660009081526040902054808201101561041357610002565b806003600050600033600160a060020a03168152602001908152602001600020600082828250540392505081905550806003600050600084600160a060020a0316815260200190815260200160002060008282825054019250508190555081600160a060020a031633600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b820191906000526020600020905b8154815290600101906020018083116104ce57829003601f168201915b505050505081565b600160a060020a03831681526040812054808301101561051257610002565b600160a060020a0380851680835260046020908152604080852033949094168086529382528085205492855260058252808520938552929052908220548301111561055c57610002565b816003600050600086600160a060020a03168152602001908152602001600020600082828250540392505081905550816003600050600085600160a060020a03168152602001908152602001600020600082828250540192505081905550816005600050600086600160a060020a03168152602001908152602001600020600050600033600160a060020a0316815260200190815260200160002060008282825054019250508190555082600160a060020a031633600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3939250505056`, + `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"spentAllowance","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"initialSupply","type":"uint256"},{"name":"tokenName","type":"string"},{"name":"decimalUnits","type":"uint8"},{"name":"tokenSymbol","type":"string"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]`, + `"github.com/ethereum/go-ethereum/common"`, + ` + if b, err := NewToken(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) + } + `, + }, + { + `Crowdsale`, + `https://ethereum.org/crowdsale`, + `606060408190526007805460ff1916905560a0806105a883396101006040529051608051915160c05160e05160008054600160a060020a03199081169095178155670de0b6b3a7640000958602600155603c9093024201600355930260045560058054909216909217905561052f90819061007990396000f36060604052361561006c5760e060020a600035046301cb3b20811461008257806329dcb0cf1461014457806338af3eed1461014d5780636e66f6e91461015f5780637a3a0e84146101715780637b3e5e7b1461017a578063a035b1fe14610183578063dc0d3dff1461018c575b61020060075460009060ff161561032357610002565b61020060035460009042106103205760025460015490106103cb576002548154600160a060020a0316908290606082818181858883f150915460025460408051600160a060020a039390931683526020830191909152818101869052517fe842aea7a5f1b01049d752008c53c52890b1a6daf660cf39e8eec506112bbdf6945090819003909201919050a15b60405160008054600160a060020a039081169230909116319082818181858883f150506007805460ff1916600117905550505050565b6103a160035481565b6103ab600054600160a060020a031681565b6103ab600554600160a060020a031681565b6103a160015481565b6103a160025481565b6103a160045481565b6103be60043560068054829081101561000257506000526002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f8101547ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d409190910154600160a060020a03919091169082565b005b505050815481101561000257906000526020600020906002020160005060008201518160000160006101000a815481600160a060020a030219169083021790555060208201518160010160005055905050806002600082828250540192505081905550600560009054906101000a9004600160a060020a0316600160a060020a031663a9059cbb3360046000505484046040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506000604051808303816000876161da5a03f11561000257505060408051600160a060020a03331681526020810184905260018183015290517fe842aea7a5f1b01049d752008c53c52890b1a6daf660cf39e8eec506112bbdf692509081900360600190a15b50565b5060a0604052336060908152346080819052600680546001810180835592939282908280158290116102025760020281600202836000526020600020918201910161020291905b8082111561039d57805473ffffffffffffffffffffffffffffffffffffffff19168155600060019190910190815561036a565b5090565b6060908152602090f35b600160a060020a03166060908152602090f35b6060918252608052604090f35b5b60065481101561010e576006805482908110156100025760009182526002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f0190600680549254600160a060020a0316928490811015610002576002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d40015460405190915082818181858883f19350505050507fe842aea7a5f1b01049d752008c53c52890b1a6daf660cf39e8eec506112bbdf660066000508281548110156100025760008290526002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f01548154600160a060020a039190911691908490811015610002576002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d40015460408051600160a060020a0394909416845260208401919091526000838201525191829003606001919050a16001016103cc56`, + `[{"constant":false,"inputs":[],"name":"checkGoalReached","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"deadline","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"beneficiary","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"tokenReward","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"fundingGoal","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"amountRaised","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"price","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"funders","outputs":[{"name":"addr","type":"address"},{"name":"amount","type":"uint256"}],"type":"function"},{"inputs":[{"name":"ifSuccessfulSendTo","type":"address"},{"name":"fundingGoalInEthers","type":"uint256"},{"name":"durationInMinutes","type":"uint256"},{"name":"etherCostOfEachToken","type":"uint256"},{"name":"addressOfTokenUsedAsReward","type":"address"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"backer","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"isContribution","type":"bool"}],"name":"FundTransfer","type":"event"}]`, + `"github.com/ethereum/go-ethereum/common"`, + ` + if b, err := NewCrowdsale(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) + } + `, + }, + { + `DAO`, + `https://ethereum.org/dao`, + `606060405260405160808061145f833960e06040529051905160a05160c05160008054600160a060020a03191633179055600184815560028490556003839055600780549182018082558280158290116100b8576003028160030283600052602060002091820191016100b891906101c8565b50506060919091015160029190910155600160a060020a0381166000146100a65760008054600160a060020a031916821790555b505050506111f18061026e6000396000f35b505060408051608081018252600080825260208281018290528351908101845281815292820192909252426060820152600780549194509250811015610002579081527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c6889050815181546020848101517401000000000000000000000000000000000000000002600160a060020a03199290921690921760a060020a60ff021916178255604083015180516001848101805460008281528690209195600293821615610100026000190190911692909204601f9081018390048201949192919091019083901061023e57805160ff19168380011785555b50610072929150610226565b5050600060028201556001015b8082111561023a578054600160a860020a031916815560018181018054600080835592600290821615610100026000190190911604601f81901061020c57506101bb565b601f0160209004906000526020600020908101906101bb91905b8082111561023a5760008155600101610226565b5090565b828001600101855582156101af579182015b828111156101af57825182600050559160200191906001019061025056606060405236156100b95760e060020a6000350463013cf08b81146100bb578063237e9492146101285780633910682114610281578063400e3949146102995780635daf08ca146102a257806369bd34361461032f5780638160f0b5146103385780638da5cb5b146103415780639644fcbd14610353578063aa02a90f146103be578063b1050da5146103c7578063bcca1fd3146104b5578063d3c0715b146104dc578063eceb29451461058d578063f2fde38b1461067b575b005b61069c6004356004805482908110156100025790600052602060002090600a02016000506005810154815460018301546003840154600485015460068601546007870154600160a060020a03959095169750929560020194919360ff828116946101009093041692919089565b60408051602060248035600481810135601f81018590048502860185019096528585526107759581359591946044949293909201918190840183828082843750949650505050505050600060006004600050848154811015610002575090527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19e600a8402908101547f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b909101904210806101e65750600481015460ff165b8061026757508060000160009054906101000a9004600160a060020a03168160010160005054846040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f15090500193505050506040518091039020816007016000505414155b8061027757506001546005820154105b1561109257610002565b61077560043560066020526000908152604090205481565b61077560055481565b61078760043560078054829081101561000257506000526003026000805160206111d18339815191528101547fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68a820154600160a060020a0382169260a060020a90920460ff16917fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c689019084565b61077560025481565b61077560015481565b610830600054600160a060020a031681565b604080516020604435600481810135601f81018490048402850184019095528484526100b9948135946024803595939460649492939101918190840183828082843750949650505050505050600080548190600160a060020a03908116339091161461084d57610002565b61077560035481565b604080516020604435600481810135601f8101849004840285018401909552848452610775948135946024803595939460649492939101918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976084979196506024909101945090925082915084018382808284375094965050505050505033600160a060020a031660009081526006602052604081205481908114806104ab5750604081205460078054909190811015610002579082526003026000805160206111d1833981519152015460a060020a900460ff16155b15610ce557610002565b6100b960043560243560443560005433600160a060020a03908116911614610b1857610002565b604080516020604435600481810135601f810184900484028501840190955284845261077594813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a031660009081526006602052604081205481908114806105835750604081205460078054909190811015610002579082526003026000805160206111d18339815191520181505460a060020a900460ff16155b15610f1d57610002565b604080516020606435600481810135601f81018490048402850184019095528484526107759481359460248035956044359560849492019190819084018382808284375094965050505050505060006000600460005086815481101561000257908252600a027f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b01815090508484846040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f150905001935050505060405180910390208160070160005054149150610cdc565b6100b960043560005433600160a060020a03908116911614610f0857610002565b604051808a600160a060020a031681526020018981526020018060200188815260200187815260200186815260200185815260200184815260200183815260200182810382528981815460018160011615610100020316600290048152602001915080546001816001161561010002031660029004801561075e5780601f106107335761010080835404028352916020019161075e565b820191906000526020600020905b81548152906001019060200180831161074157829003601f168201915b50509a505050505050505050505060405180910390f35b60408051918252519081900360200190f35b60408051600160a060020a038616815260208101859052606081018390526080918101828152845460026001821615610100026000190190911604928201839052909160a08301908590801561081e5780601f106107f35761010080835404028352916020019161081e565b820191906000526020600020905b81548152906001019060200180831161080157829003601f168201915b50509550505050505060405180910390f35b60408051600160a060020a03929092168252519081900360200190f35b600160a060020a03851660009081526006602052604081205414156108a957604060002060078054918290556001820180825582801582901161095c5760030281600302836000526020600020918201910161095c9190610a4f565b600160a060020a03851660009081526006602052604090205460078054919350908390811015610002575060005250600381026000805160206111d183398151915201805474ff0000000000000000000000000000000000000000191660a060020a85021781555b60408051600160a060020a03871681526020810186905281517f27b022af4a8347100c7a041ce5ccf8e14d644ff05de696315196faae8cd50c9b929181900390910190a15050505050565b505050915081506080604051908101604052808681526020018581526020018481526020014281526020015060076000508381548110156100025790600052602060002090600302016000508151815460208481015160a060020a02600160a060020a03199290921690921774ff00000000000000000000000000000000000000001916178255604083015180516001848101805460008281528690209195600293821615610100026000190190911692909204601f90810183900482019491929190910190839010610ad357805160ff19168380011785555b50610b03929150610abb565b5050600060028201556001015b80821115610acf57805474ffffffffffffffffffffffffffffffffffffffffff1916815560018181018054600080835592600290821615610100026000190190911604601f819010610aa15750610a42565b601f016020900490600052602060002090810190610a4291905b80821115610acf5760008155600101610abb565b5090565b82800160010185558215610a36579182015b82811115610a36578251826000505591602001919060010190610ae5565b50506060919091015160029190910155610911565b600183905560028290556003819055604080518481526020810184905280820183905290517fa439d3fa452be5e0e1e24a8145e715f4fd8b9c08c96a42fd82a855a85e5d57de9181900360600190a1505050565b50508585846040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f150905001935050505060405180910390208160070160005081905550600260005054603c024201816003016000508190555060008160040160006101000a81548160ff0219169083021790555060008160040160016101000a81548160ff02191690830217905550600081600501600050819055507f646fec02522b41e7125cfc859a64fd4f4cefd5dc3b6237ca0abe251ded1fa881828787876040518085815260200184600160a060020a03168152602001838152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015610cc45780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a1600182016005555b50949350505050565b6004805460018101808355909190828015829011610d1c57600a0281600a028360005260206000209182019101610d1c9190610db8565b505060048054929450918491508110156100025790600052602060002090600a02016000508054600160a060020a031916871781556001818101879055855160028381018054600082815260209081902096975091959481161561010002600019011691909104601f90810182900484019391890190839010610ed857805160ff19168380011785555b50610b6c929150610abb565b50506001015b80821115610acf578054600160a060020a03191681556000600182810182905560028381018054848255909281161561010002600019011604601f819010610e9c57505b5060006003830181905560048301805461ffff191690556005830181905560068301819055600783018190556008830180548282559082526020909120610db2916002028101905b80821115610acf57805474ffffffffffffffffffffffffffffffffffffffffff1916815560018181018054600080835592600290821615610100026000190190911604601f819010610eba57505b5050600101610e44565b601f016020900490600052602060002090810190610dfc9190610abb565b601f016020900490600052602060002090810190610e929190610abb565b82800160010185558215610da6579182015b82811115610da6578251826000505591602001919060010190610eea565b60008054600160a060020a0319168217905550565b600480548690811015610002576000918252600a027f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b01905033600160a060020a0316600090815260098201602052604090205490915060ff1660011415610f8457610002565b33600160a060020a031660009081526009820160205260409020805460ff1916600190811790915560058201805490910190558315610fcd576006810180546001019055610fda565b6006810180546000190190555b7fc34f869b7ff431b034b7b9aea9822dac189a685e0b015c7d1be3add3f89128e8858533866040518085815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f16801561107a5780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a1509392505050565b6006810154600354901315611158578060000160009054906101000a9004600160a060020a0316600160a060020a03168160010160005054670de0b6b3a76400000284604051808280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156111225780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f15050505060048101805460ff191660011761ff00191661010017905561116d565b60048101805460ff191660011761ff00191690555b60068101546005820154600483015460408051888152602081019490945283810192909252610100900460ff166060830152517fd220b7272a8b6d0d7d6bcdace67b936a8f175e6d5c1b3ee438b72256b32ab3af9181900360800190a1509291505056a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688`, + `[{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"proposals","outputs":[{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"},{"name":"description","type":"string"},{"name":"votingDeadline","type":"uint256"},{"name":"executed","type":"bool"},{"name":"proposalPassed","type":"bool"},{"name":"numberOfVotes","type":"uint256"},{"name":"currentResult","type":"int256"},{"name":"proposalHash","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"proposalNumber","type":"uint256"},{"name":"transactionBytecode","type":"bytes"}],"name":"executeProposal","outputs":[{"name":"result","type":"int256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"memberId","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"numProposals","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"members","outputs":[{"name":"member","type":"address"},{"name":"canVote","type":"bool"},{"name":"name","type":"string"},{"name":"memberSince","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"debatingPeriodInMinutes","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"minimumQuorum","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"targetMember","type":"address"},{"name":"canVote","type":"bool"},{"name":"memberName","type":"string"}],"name":"changeMembership","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"majorityMargin","outputs":[{"name":"","type":"int256"}],"type":"function"},{"constant":false,"inputs":[{"name":"beneficiary","type":"address"},{"name":"etherAmount","type":"uint256"},{"name":"JobDescription","type":"string"},{"name":"transactionBytecode","type":"bytes"}],"name":"newProposal","outputs":[{"name":"proposalID","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"minimumQuorumForProposals","type":"uint256"},{"name":"minutesForDebate","type":"uint256"},{"name":"marginOfVotesForMajority","type":"int256"}],"name":"changeVotingRules","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"proposalNumber","type":"uint256"},{"name":"supportsProposal","type":"bool"},{"name":"justificationText","type":"string"}],"name":"vote","outputs":[{"name":"voteID","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"proposalNumber","type":"uint256"},{"name":"beneficiary","type":"address"},{"name":"etherAmount","type":"uint256"},{"name":"transactionBytecode","type":"bytes"}],"name":"checkProposalCode","outputs":[{"name":"codeChecksOut","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"type":"function"},{"inputs":[{"name":"minimumQuorumForProposals","type":"uint256"},{"name":"minutesForDebate","type":"uint256"},{"name":"marginOfVotesForMajority","type":"int256"},{"name":"congressLeader","type":"address"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"proposalID","type":"uint256"},{"indexed":false,"name":"recipient","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"description","type":"string"}],"name":"ProposalAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"proposalID","type":"uint256"},{"indexed":false,"name":"position","type":"bool"},{"indexed":false,"name":"voter","type":"address"},{"indexed":false,"name":"justification","type":"string"}],"name":"Voted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"proposalID","type":"uint256"},{"indexed":false,"name":"result","type":"int256"},{"indexed":false,"name":"quorum","type":"uint256"},{"indexed":false,"name":"active","type":"bool"}],"name":"ProposalTallied","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"member","type":"address"},{"indexed":false,"name":"isMember","type":"bool"}],"name":"MembershipChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"minimumQuorum","type":"uint256"},{"indexed":false,"name":"debatingPeriodInMinutes","type":"uint256"},{"indexed":false,"name":"majorityMargin","type":"int256"}],"name":"ChangeOfRules","type":"event"}]`, + `"github.com/ethereum/go-ethereum/common"`, + ` + if b, err := NewDAO(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) + } + `, + }, + // Test that named and anonymous inputs are handled correctly + { + `InputChecker`, ``, ``, + ` + [ + {"type":"function","name":"noInput","constant":true,"inputs":[],"outputs":[]}, + {"type":"function","name":"namedInput","constant":true,"inputs":[{"name":"str","type":"string"}],"outputs":[]}, + {"type":"function","name":"anonInput","constant":true,"inputs":[{"name":"","type":"string"}],"outputs":[]}, + {"type":"function","name":"namedInputs","constant":true,"inputs":[{"name":"str1","type":"string"},{"name":"str2","type":"string"}],"outputs":[]}, + {"type":"function","name":"anonInputs","constant":true,"inputs":[{"name":"","type":"string"},{"name":"","type":"string"}],"outputs":[]}, + {"type":"function","name":"mixedInputs","constant":true,"inputs":[{"name":"","type":"string"},{"name":"str","type":"string"}],"outputs":[]} + ] + `, + ` + "fmt" + + "github.com/ethereum/go-ethereum/common" + `, + `if b, err := NewInputChecker(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) + } else if false { // Don't run, just compile and test types + var err error + + err = b.NoInput(nil) + err = b.NamedInput(nil, "") + err = b.AnonInput(nil, "") + err = b.NamedInputs(nil, "", "") + err = b.AnonInputs(nil, "", "") + err = b.MixedInputs(nil, "", "") + + fmt.Println(err) + }`, + }, + // Test that named and anonymous outputs are handled correctly + { + `OutputChecker`, ``, ``, + ` + [ + {"type":"function","name":"noOutput","constant":true,"inputs":[],"outputs":[]}, + {"type":"function","name":"namedOutput","constant":true,"inputs":[],"outputs":[{"name":"str","type":"string"}]}, + {"type":"function","name":"anonOutput","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"}]}, + {"type":"function","name":"namedOutputs","constant":true,"inputs":[],"outputs":[{"name":"str1","type":"string"},{"name":"str2","type":"string"}]}, + {"type":"function","name":"collidingOutputs","constant":true,"inputs":[],"outputs":[{"name":"str","type":"string"},{"name":"Str","type":"string"}]}, + {"type":"function","name":"anonOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"","type":"string"}]}, + {"type":"function","name":"mixedOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"str","type":"string"}]} + ] + `, + ` + "fmt" + + "github.com/ethereum/go-ethereum/common" + `, + `if b, err := NewOutputChecker(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) + } else if false { // Don't run, just compile and test types + var str1, str2 string + var err error + + err = b.NoOutput(nil) + str1, err = b.NamedOutput(nil) + str1, err = b.AnonOutput(nil) + res, _ := b.NamedOutputs(nil) + str1, str2, err = b.CollidingOutputs(nil) + str1, str2, err = b.AnonOutputs(nil) + str1, str2, err = b.MixedOutputs(nil) + + fmt.Println(str1, str2, res.Str1, res.Str2, err) + }`, + }, + // Tests that named, anonymous and indexed events are handled correctly + { + `EventChecker`, ``, ``, + ` + [ + {"type":"event","name":"empty","inputs":[]}, + {"type":"event","name":"indexed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int256","indexed":true}]}, + {"type":"event","name":"mixed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int256"}]}, + {"type":"event","name":"anonymous","anonymous":true,"inputs":[]}, + {"type":"event","name":"dynamic","inputs":[{"name":"idxStr","type":"string","indexed":true},{"name":"idxDat","type":"bytes","indexed":true},{"name":"str","type":"string"},{"name":"dat","type":"bytes"}]} + ] + `, + ` + "fmt" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/common" + `, + `if e, err := NewEventChecker(common.Address{}, nil); e == nil || err != nil { + t.Fatalf("binding (%v) nil or error (%v) not nil", e, nil) + } else if false { // Don't run, just compile and test types + var ( + err error + res bool + str string + dat []byte + hash common.Hash + ) + _, err = e.FilterEmpty(nil) + _, err = e.FilterIndexed(nil, []common.Address{}, []*big.Int{}) + + mit, err := e.FilterMixed(nil, []common.Address{}) + + res = mit.Next() // Make sure the iterator has a Next method + err = mit.Error() // Make sure the iterator has an Error method + err = mit.Close() // Make sure the iterator has a Close method + + fmt.Println(mit.Event.Raw.BlockHash) // Make sure the raw log is contained within the results + fmt.Println(mit.Event.Num) // Make sure the unpacked non-indexed fields are present + fmt.Println(mit.Event.Addr) // Make sure the reconstructed indexed fields are present + + dit, err := e.FilterDynamic(nil, []string{}, [][]byte{}) + + str = dit.Event.Str // Make sure non-indexed strings retain their type + dat = dit.Event.Dat // Make sure non-indexed bytes retain their type + hash = dit.Event.IdxStr // Make sure indexed strings turn into hashes + hash = dit.Event.IdxDat // Make sure indexed bytes turn into hashes + + sink := make(chan *EventCheckerMixed) + sub, err := e.WatchMixed(nil, sink, []common.Address{}) + defer sub.Unsubscribe() + + event := <-sink + fmt.Println(event.Raw.BlockHash) // Make sure the raw log is contained within the results + fmt.Println(event.Num) // Make sure the unpacked non-indexed fields are present + fmt.Println(event.Addr) // Make sure the reconstructed indexed fields are present + + fmt.Println(res, str, dat, hash, err) + } + // Run a tiny reflection test to ensure disallowed methods don't appear + if _, ok := reflect.TypeOf(&EventChecker{}).MethodByName("FilterAnonymous"); ok { + t.Errorf("binding has disallowed method (FilterAnonymous)") + }`, + }, + // Test that contract interactions (deploy, transact and call) generate working code + { + `Interactor`, + ` + contract Interactor { + string public deployString; + string public transactString; + + function Interactor(string str) { + deployString = str; + } + + function transact(string str) { + transactString = str; + } + } + `, + `6060604052604051610328380380610328833981016040528051018060006000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10608d57805160ff19168380011785555b50607c9291505b8082111560ba57838155600101606b565b50505061026a806100be6000396000f35b828001600101855582156064579182015b828111156064578251826000505591602001919060010190609e565b509056606060405260e060020a60003504630d86a0e181146100315780636874e8091461008d578063d736c513146100ea575b005b610190600180546020600282841615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156102295780601f106101fe57610100808354040283529160200191610229565b61019060008054602060026001831615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156102295780601f106101fe57610100808354040283529160200191610229565b60206004803580820135601f81018490049093026080908101604052606084815261002f946024939192918401918190838280828437509496505050505050508060016000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061023157805160ff19168380011785555b506102619291505b808211156102665760008155830161017d565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156101f05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b820191906000526020600020905b81548152906001019060200180831161020c57829003601f168201915b505050505081565b82800160010185558215610175579182015b82811115610175578251826000505591602001919060010190610243565b505050565b509056`, + `[{"constant":true,"inputs":[],"name":"transactString","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":true,"inputs":[],"name":"deployString","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":false,"inputs":[{"name":"str","type":"string"}],"name":"transact","outputs":[],"type":"function"},{"inputs":[{"name":"str","type":"string"}],"type":"constructor"}]`, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + + // Deploy an interaction tester contract and call a transaction on it + _, _, interactor, err := DeployInteractor(auth, sim, "Deploy string") + if err != nil { + t.Fatalf("Failed to deploy interactor contract: %v", err) + } + if _, err := interactor.Transact(auth, "Transact string"); err != nil { + t.Fatalf("Failed to transact with interactor contract: %v", err) + } + // Commit all pending transactions in the simulator and check the contract state + sim.Commit() + + if str, err := interactor.DeployString(nil); err != nil { + t.Fatalf("Failed to retrieve deploy string: %v", err) + } else if str != "Deploy string" { + t.Fatalf("Deploy string mismatch: have '%s', want 'Deploy string'", str) + } + if str, err := interactor.TransactString(nil); err != nil { + t.Fatalf("Failed to retrieve transact string: %v", err) + } else if str != "Transact string" { + t.Fatalf("Transact string mismatch: have '%s', want 'Transact string'", str) + } + `, + }, + // Tests that plain values can be properly returned and deserialized + { + `Getter`, + ` + contract Getter { + function getter() constant returns (string, int, bytes32) { + return ("Hi", 1, sha3("")); + } + } + `, + `606060405260dc8060106000396000f3606060405260e060020a6000350463993a04b78114601a575b005b600060605260c0604052600260809081527f486900000000000000000000000000000000000000000000000000000000000060a05260017fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47060e0829052610100819052606060c0908152600261012081905281906101409060a09080838184600060046012f1505081517fffff000000000000000000000000000000000000000000000000000000000000169091525050604051610160819003945092505050f3`, + `[{"constant":true,"inputs":[],"name":"getter","outputs":[{"name":"","type":"string"},{"name":"","type":"int256"},{"name":"","type":"bytes32"}],"type":"function"}]`, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + + // Deploy a tuple tester contract and execute a structured call on it + _, _, getter, err := DeployGetter(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy getter contract: %v", err) + } + sim.Commit() + + if str, num, _, err := getter.Getter(nil); err != nil { + t.Fatalf("Failed to call anonymous field retriever: %v", err) + } else if str != "Hi" || num.Cmp(big.NewInt(1)) != 0 { + t.Fatalf("Retrieved value mismatch: have %v/%v, want %v/%v", str, num, "Hi", 1) + } + `, + }, + // Tests that tuples can be properly returned and deserialized + { + `Tupler`, + ` + contract Tupler { + function tuple() constant returns (string a, int b, bytes32 c) { + return ("Hi", 1, sha3("")); + } + } + `, + `606060405260dc8060106000396000f3606060405260e060020a60003504633175aae28114601a575b005b600060605260c0604052600260809081527f486900000000000000000000000000000000000000000000000000000000000060a05260017fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47060e0829052610100819052606060c0908152600261012081905281906101409060a09080838184600060046012f1505081517fffff000000000000000000000000000000000000000000000000000000000000169091525050604051610160819003945092505050f3`, + `[{"constant":true,"inputs":[],"name":"tuple","outputs":[{"name":"a","type":"string"},{"name":"b","type":"int256"},{"name":"c","type":"bytes32"}],"type":"function"}]`, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + + // Deploy a tuple tester contract and execute a structured call on it + _, _, tupler, err := DeployTupler(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy tupler contract: %v", err) + } + sim.Commit() + + if res, err := tupler.Tuple(nil); err != nil { + t.Fatalf("Failed to call structure retriever: %v", err) + } else if res.A != "Hi" || res.B.Cmp(big.NewInt(1)) != 0 { + t.Fatalf("Retrieved value mismatch: have %v/%v, want %v/%v", res.A, res.B, "Hi", 1) + } + `, + }, + // Tests that arrays/slices can be properly returned and deserialized. + // Only addresses are tested, remainder just compiled to keep the test small. + { + `Slicer`, + ` + contract Slicer { + function echoAddresses(address[] input) constant returns (address[] output) { + return input; + } + function echoInts(int[] input) constant returns (int[] output) { + return input; + } + function echoFancyInts(uint24[23] input) constant returns (uint24[23] output) { + return input; + } + function echoBools(bool[] input) constant returns (bool[] output) { + return input; + } + } + `, + `606060405261015c806100126000396000f3606060405260e060020a6000350463be1127a3811461003c578063d88becc014610092578063e15a3db71461003c578063f637e5891461003c575b005b604080516020600480358082013583810285810185019096528085526100ee959294602494909392850192829185019084908082843750949650505050505050604080516020810190915260009052805b919050565b604080516102e0818101909252610138916004916102e491839060179083908390808284375090955050505050506102e0604051908101604052806017905b60008152602001906001900390816100d15790505081905061008d565b60405180806020018281038252838181518152602001915080519060200190602002808383829060006004602084601f0104600f02600301f1509050019250505060405180910390f35b60405180826102e0808381846000600461015cf15090500191505060405180910390f3`, + `[{"constant":true,"inputs":[{"name":"input","type":"address[]"}],"name":"echoAddresses","outputs":[{"name":"output","type":"address[]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"uint24[23]"}],"name":"echoFancyInts","outputs":[{"name":"output","type":"uint24[23]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"int256[]"}],"name":"echoInts","outputs":[{"name":"output","type":"int256[]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"bool[]"}],"name":"echoBools","outputs":[{"name":"output","type":"bool[]"}],"type":"function"}]`, + ` + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + + // Deploy a slice tester contract and execute a n array call on it + _, _, slicer, err := DeploySlicer(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy slicer contract: %v", err) + } + sim.Commit() + + if out, err := slicer.EchoAddresses(nil, []common.Address{auth.From, common.Address{}}); err != nil { + t.Fatalf("Failed to call slice echoer: %v", err) + } else if !reflect.DeepEqual(out, []common.Address{auth.From, common.Address{}}) { + t.Fatalf("Slice return mismatch: have %v, want %v", out, []common.Address{auth.From, common.Address{}}) + } + `, + }, + // Tests that anonymous default methods can be correctly invoked + { + `Defaulter`, + ` + contract Defaulter { + address public caller; + + function() { + caller = msg.sender; + } + } + `, + `6060604052606a8060106000396000f360606040523615601d5760e060020a6000350463fc9c8d3981146040575b605e6000805473ffffffffffffffffffffffffffffffffffffffff191633179055565b606060005473ffffffffffffffffffffffffffffffffffffffff1681565b005b6060908152602090f3`, + `[{"constant":true,"inputs":[],"name":"caller","outputs":[{"name":"","type":"address"}],"type":"function"}]`, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + + // Deploy a default method invoker contract and execute its default method + _, _, defaulter, err := DeployDefaulter(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy defaulter contract: %v", err) + } + if _, err := (&DefaulterRaw{defaulter}).Transfer(auth); err != nil { + t.Fatalf("Failed to invoke default method: %v", err) + } + sim.Commit() + + if caller, err := defaulter.Caller(nil); err != nil { + t.Fatalf("Failed to call address retriever: %v", err) + } else if (caller != auth.From) { + t.Fatalf("Address mismatch: have %v, want %v", caller, auth.From) + } + `, + }, + // Tests that non-existent contracts are reported as such (though only simulator test) + { + `NonExistent`, + ` + contract NonExistent { + function String() constant returns(string) { + return "I don't exist"; + } + } + `, + `6060604052609f8060106000396000f3606060405260e060020a6000350463f97a60058114601a575b005b600060605260c0604052600d60809081527f4920646f6e27742065786973740000000000000000000000000000000000000060a052602060c0908152600d60e081905281906101009060a09080838184600060046012f15050815172ffffffffffffffffffffffffffffffffffffff1916909152505060405161012081900392509050f3`, + `[{"constant":true,"inputs":[],"name":"String","outputs":[{"name":"","type":"string"}],"type":"function"}]`, + ` + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + `, + ` + // Create a simulator and wrap a non-deployed contract + sim := backends.NewSimulatedBackend(nil, uint64(10000000000)) + + nonexistent, err := NewNonExistent(common.Address{}, sim) + if err != nil { + t.Fatalf("Failed to access non-existent contract: %v", err) + } + // Ensure that contract calls fail with the appropriate error + if res, err := nonexistent.String(nil); err == nil { + t.Fatalf("Call succeeded on non-existent contract: %v", res) + } else if (err != bind.ErrNoCode) { + t.Fatalf("Error mismatch: have %v, want %v", err, bind.ErrNoCode) + } + `, + }, + // Tests that gas estimation works for contracts with weird gas mechanics too. + { + `FunkyGasPattern`, + ` + contract FunkyGasPattern { + string public field; + + function SetField(string value) { + // This check will screw gas estimation! Good, good! + if (msg.gas < 100000) { + throw; + } + field = value; + } + } + `, + `606060405261021c806100126000396000f3606060405260e060020a600035046323fcf32a81146100265780634f28bf0e1461007b575b005b6040805160206004803580820135601f8101849004840285018401909552848452610024949193602493909291840191908190840183828082843750949650505050505050620186a05a101561014e57610002565b6100db60008054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281529291908301828280156102145780601f106101e957610100808354040283529160200191610214565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561013b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b505050565b8060006000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106101b557805160ff19168380011785555b506101499291505b808211156101e557600081556001016101a1565b82800160010185558215610199579182015b828111156101995782518260005055916020019190600101906101c7565b5090565b820191906000526020600020905b8154815290600101906020018083116101f757829003601f168201915b50505050508156`, + `[{"constant":false,"inputs":[{"name":"value","type":"string"}],"name":"SetField","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"field","outputs":[{"name":"","type":"string"}],"type":"function"}]`, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + + // Deploy a funky gas pattern contract + _, _, limiter, err := DeployFunkyGasPattern(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy funky contract: %v", err) + } + sim.Commit() + + // Set the field with automatic estimation and check that it succeeds + if _, err := limiter.SetField(auth, "automatic"); err != nil { + t.Fatalf("Failed to call automatically gased transaction: %v", err) + } + sim.Commit() + + if field, _ := limiter.Field(nil); field != "automatic" { + t.Fatalf("Field mismatch: have %v, want %v", field, "automatic") + } + `, + }, + // Test that constant functions can be called from an (optional) specified address + { + `CallFrom`, + ` + contract CallFrom { + function callFrom() constant returns(address) { + return msg.sender; + } + } + `, `6060604052346000575b6086806100176000396000f300606060405263ffffffff60e060020a60003504166349f8e98281146022575b6000565b34600057602c6055565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b335b905600a165627a7a72305820aef6b7685c0fa24ba6027e4870404a57df701473fe4107741805c19f5138417c0029`, + `[{"constant":true,"inputs":[],"name":"callFrom","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}]`, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + + // Deploy a sender tester contract and execute a structured call on it + _, _, callfrom, err := DeployCallFrom(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy sender contract: %v", err) + } + sim.Commit() + + if res, err := callfrom.CallFrom(nil); err != nil { + t.Errorf("Failed to call constant function: %v", err) + } else if res != (common.Address{}) { + t.Errorf("Invalid address returned, want: %x, got: %x", (common.Address{}), res) + } + + for _, addr := range []common.Address{common.Address{}, common.Address{1}, common.Address{2}} { + if res, err := callfrom.CallFrom(&bind.CallOpts{From: addr}); err != nil { + t.Fatalf("Failed to call constant function: %v", err) + } else if res != addr { + t.Fatalf("Invalid address returned, want: %x, got: %x", addr, res) + } + } + `, + }, + // Tests that methods and returns with underscores inside work correctly. + { + `Underscorer`, + ` + contract Underscorer { + function UnderscoredOutput() constant returns (int _int, string _string) { + return (314, "pi"); + } + function LowerLowerCollision() constant returns (int _res, int res) { + return (1, 2); + } + function LowerUpperCollision() constant returns (int _res, int Res) { + return (1, 2); + } + function UpperLowerCollision() constant returns (int _Res, int res) { + return (1, 2); + } + function UpperUpperCollision() constant returns (int _Res, int Res) { + return (1, 2); + } + function PurelyUnderscoredOutput() constant returns (int _, int res) { + return (1, 2); + } + function AllPurelyUnderscoredOutput() constant returns (int _, int __) { + return (1, 2); + } + function _under_scored_func() constant returns (int _int) { + return 0; + } + } + `, `6060604052341561000f57600080fd5b6103858061001e6000396000f30060606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806303a592131461009357806346546dbe146100c357806367e6633d146100ec5780639df4848514610181578063af7486ab146101b1578063b564b34d146101e1578063e02ab24d14610211578063e409ca4514610241575b600080fd5b341561009e57600080fd5b6100a6610271565b604051808381526020018281526020019250505060405180910390f35b34156100ce57600080fd5b6100d6610286565b6040518082815260200191505060405180910390f35b34156100f757600080fd5b6100ff61028e565b6040518083815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561014557808201518184015260208101905061012a565b50505050905090810190601f1680156101725780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b341561018c57600080fd5b6101946102dc565b604051808381526020018281526020019250505060405180910390f35b34156101bc57600080fd5b6101c46102f1565b604051808381526020018281526020019250505060405180910390f35b34156101ec57600080fd5b6101f4610306565b604051808381526020018281526020019250505060405180910390f35b341561021c57600080fd5b61022461031b565b604051808381526020018281526020019250505060405180910390f35b341561024c57600080fd5b610254610330565b604051808381526020018281526020019250505060405180910390f35b60008060016002819150809050915091509091565b600080905090565b6000610298610345565b61013a8090506040805190810160405280600281526020017f7069000000000000000000000000000000000000000000000000000000000000815250915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b6020604051908101604052806000815250905600a165627a7a72305820d1a53d9de9d1e3d55cb3dc591900b63c4f1ded79114f7b79b332684840e186a40029`, + `[{"constant":true,"inputs":[],"name":"LowerUpperCollision","outputs":[{"name":"_res","type":"int256"},{"name":"Res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_under_scored_func","outputs":[{"name":"_int","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"UnderscoredOutput","outputs":[{"name":"_int","type":"int256"},{"name":"_string","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PurelyUnderscoredOutput","outputs":[{"name":"_","type":"int256"},{"name":"res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"UpperLowerCollision","outputs":[{"name":"_Res","type":"int256"},{"name":"res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"AllPurelyUnderscoredOutput","outputs":[{"name":"_","type":"int256"},{"name":"__","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"UpperUpperCollision","outputs":[{"name":"_Res","type":"int256"},{"name":"Res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"LowerLowerCollision","outputs":[{"name":"_res","type":"int256"},{"name":"res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"}]`, + ` + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + + // Deploy a underscorer tester contract and execute a structured call on it + _, _, underscorer, err := DeployUnderscorer(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy underscorer contract: %v", err) + } + sim.Commit() + + // Verify that underscored return values correctly parse into structs + if res, err := underscorer.UnderscoredOutput(nil); err != nil { + t.Errorf("Failed to call constant function: %v", err) + } else if res.Int.Cmp(big.NewInt(314)) != 0 || res.String != "pi" { + t.Errorf("Invalid result, want: {314, \"pi\"}, got: %+v", res) + } + // Verify that underscored and non-underscored name collisions force tuple outputs + var a, b *big.Int + + a, b, _ = underscorer.LowerLowerCollision(nil) + a, b, _ = underscorer.LowerUpperCollision(nil) + a, b, _ = underscorer.UpperLowerCollision(nil) + a, b, _ = underscorer.UpperUpperCollision(nil) + a, b, _ = underscorer.PurelyUnderscoredOutput(nil) + a, b, _ = underscorer.AllPurelyUnderscoredOutput(nil) + a, _ = underscorer.UnderScoredFunc(nil) + + fmt.Println(a, b, err) + `, + }, + // Tests that logs can be successfully filtered and decoded. + { + `Eventer`, + ` + contract Eventer { + event SimpleEvent ( + address indexed Addr, + bytes32 indexed Id, + bool indexed Flag, + uint Value + ); + function raiseSimpleEvent(address addr, bytes32 id, bool flag, uint value) { + SimpleEvent(addr, id, flag, value); + } + + event NodataEvent ( + uint indexed Number, + int16 indexed Short, + uint32 indexed Long + ); + function raiseNodataEvent(uint number, int16 short, uint32 long) { + NodataEvent(number, short, long); + } + + event DynamicEvent ( + string indexed IndexedString, + bytes indexed IndexedBytes, + string NonIndexedString, + bytes NonIndexedBytes + ); + function raiseDynamicEvent(string str, bytes blob) { + DynamicEvent(str, blob, str, blob); + } + + event FixedBytesEvent ( + bytes24 indexed IndexedBytes, + bytes24 NonIndexedBytes + ); + function raiseFixedBytesEvent(bytes24 blob) { + FixedBytesEvent(blob, blob); + } + } + `, + `608060405234801561001057600080fd5b5061043f806100206000396000f3006080604052600436106100615763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663528300ff8114610066578063630c31e2146100ff5780636cc6b94014610138578063c7d116dd1461015b575b600080fd5b34801561007257600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526100fd94369492936024939284019190819084018382808284375050604080516020601f89358b018035918201839004830284018301909452808352979a9998810197919650918201945092508291508401838280828437509497506101829650505050505050565b005b34801561010b57600080fd5b506100fd73ffffffffffffffffffffffffffffffffffffffff60043516602435604435151560643561033c565b34801561014457600080fd5b506100fd67ffffffffffffffff1960043516610394565b34801561016757600080fd5b506100fd60043560243560010b63ffffffff604435166103d6565b806040518082805190602001908083835b602083106101b25780518252601f199092019160209182019101610193565b51815160209384036101000a6000190180199092169116179052604051919093018190038120875190955087945090928392508401908083835b6020831061020b5780518252601f1990920191602091820191016101ec565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f3281fd4f5e152dd3385df49104a3f633706e21c9e80672e88d3bcddf33101f008484604051808060200180602001838103835285818151815260200191508051906020019080838360005b8381101561029c578181015183820152602001610284565b50505050905090810190601f1680156102c95780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b838110156102fc5781810151838201526020016102e4565b50505050905090810190601f1680156103295780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a35050565b60408051828152905183151591859173ffffffffffffffffffffffffffffffffffffffff8816917f1f097de4289df643bd9c11011cc61367aa12983405c021056e706eb5ba1250c8919081900360200190a450505050565b6040805167ffffffffffffffff19831680825291517fcdc4c1b1aed5524ffb4198d7a5839a34712baef5fa06884fac7559f4a5854e0a9181900360200190a250565b8063ffffffff168260010b847f3ca7f3a77e5e6e15e781850bc82e32adfa378a2a609370db24b4d0fae10da2c960405160405180910390a45050505600a165627a7a72305820468b5843bf653145bd924b323c64ef035d3dd922c170644b44d61aa666ea6eee0029`, + `[{"constant":false,"inputs":[{"name":"str","type":"string"},{"name":"blob","type":"bytes"}],"name":"raiseDynamicEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"},{"name":"id","type":"bytes32"},{"name":"flag","type":"bool"},{"name":"value","type":"uint256"}],"name":"raiseSimpleEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"blob","type":"bytes24"}],"name":"raiseFixedBytesEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"number","type":"uint256"},{"name":"short","type":"int16"},{"name":"long","type":"uint32"}],"name":"raiseNodataEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"Addr","type":"address"},{"indexed":true,"name":"Id","type":"bytes32"},{"indexed":true,"name":"Flag","type":"bool"},{"indexed":false,"name":"Value","type":"uint256"}],"name":"SimpleEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"Number","type":"uint256"},{"indexed":true,"name":"Short","type":"int16"},{"indexed":true,"name":"Long","type":"uint32"}],"name":"NodataEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"IndexedString","type":"string"},{"indexed":true,"name":"IndexedBytes","type":"bytes"},{"indexed":false,"name":"NonIndexedString","type":"string"},{"indexed":false,"name":"NonIndexedBytes","type":"bytes"}],"name":"DynamicEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"IndexedBytes","type":"bytes24"},{"indexed":false,"name":"NonIndexedBytes","type":"bytes24"}],"name":"FixedBytesEvent","type":"event"}]`, + ` + "math/big" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + + // Deploy an eventer contract + _, _, eventer, err := DeployEventer(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy eventer contract: %v", err) + } + sim.Commit() + + // Inject a few events into the contract, gradually more in each block + for i := 1; i <= 3; i++ { + for j := 1; j <= i; j++ { + if _, err := eventer.RaiseSimpleEvent(auth, common.Address{byte(j)}, [32]byte{byte(j)}, true, big.NewInt(int64(10*i+j))); err != nil { + t.Fatalf("block %d, event %d: raise failed: %v", i, j, err) + } + } + sim.Commit() + } + // Test filtering for certain events and ensure they can be found + sit, err := eventer.FilterSimpleEvent(nil, []common.Address{common.Address{1}, common.Address{3}}, [][32]byte{{byte(1)}, {byte(2)}, {byte(3)}}, []bool{true}) + if err != nil { + t.Fatalf("failed to filter for simple events: %v", err) + } + defer sit.Close() + + sit.Next() + if sit.Event.Value.Uint64() != 11 || !sit.Event.Flag { + t.Errorf("simple log content mismatch: have %v, want {11, true}", sit.Event) + } + sit.Next() + if sit.Event.Value.Uint64() != 21 || !sit.Event.Flag { + t.Errorf("simple log content mismatch: have %v, want {21, true}", sit.Event) + } + sit.Next() + if sit.Event.Value.Uint64() != 31 || !sit.Event.Flag { + t.Errorf("simple log content mismatch: have %v, want {31, true}", sit.Event) + } + sit.Next() + if sit.Event.Value.Uint64() != 33 || !sit.Event.Flag { + t.Errorf("simple log content mismatch: have %v, want {33, true}", sit.Event) + } + + if sit.Next() { + t.Errorf("unexpected simple event found: %+v", sit.Event) + } + if err = sit.Error(); err != nil { + t.Fatalf("simple event iteration failed: %v", err) + } + // Test raising and filtering for an event with no data component + if _, err := eventer.RaiseNodataEvent(auth, big.NewInt(314), 141, 271); err != nil { + t.Fatalf("failed to raise nodata event: %v", err) + } + sim.Commit() + + nit, err := eventer.FilterNodataEvent(nil, []*big.Int{big.NewInt(314)}, []int16{140, 141, 142}, []uint32{271}) + if err != nil { + t.Fatalf("failed to filter for nodata events: %v", err) + } + defer nit.Close() + + if !nit.Next() { + t.Fatalf("nodata log not found: %v", nit.Error()) + } + if nit.Event.Number.Uint64() != 314 { + t.Errorf("nodata log content mismatch: have %v, want 314", nit.Event.Number) + } + if nit.Next() { + t.Errorf("unexpected nodata event found: %+v", nit.Event) + } + if err = nit.Error(); err != nil { + t.Fatalf("nodata event iteration failed: %v", err) + } + // Test raising and filtering for events with dynamic indexed components + if _, err := eventer.RaiseDynamicEvent(auth, "Hello", []byte("World")); err != nil { + t.Fatalf("failed to raise dynamic event: %v", err) + } + sim.Commit() + + dit, err := eventer.FilterDynamicEvent(nil, []string{"Hi", "Hello", "Bye"}, [][]byte{[]byte("World")}) + if err != nil { + t.Fatalf("failed to filter for dynamic events: %v", err) + } + defer dit.Close() + + if !dit.Next() { + t.Fatalf("dynamic log not found: %v", dit.Error()) + } + if dit.Event.NonIndexedString != "Hello" || string(dit.Event.NonIndexedBytes) != "World" || dit.Event.IndexedString != common.HexToHash("0x06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2") || dit.Event.IndexedBytes != common.HexToHash("0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18") { + t.Errorf("dynamic log content mismatch: have %v, want {'0x06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2, '0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18', 'Hello', 'World'}", dit.Event) + } + if dit.Next() { + t.Errorf("unexpected dynamic event found: %+v", dit.Event) + } + if err = dit.Error(); err != nil { + t.Fatalf("dynamic event iteration failed: %v", err) + } + // Test raising and filtering for events with fixed bytes components + var fblob [24]byte + copy(fblob[:], []byte("Fixed Bytes")) + + if _, err := eventer.RaiseFixedBytesEvent(auth, fblob); err != nil { + t.Fatalf("failed to raise fixed bytes event: %v", err) + } + sim.Commit() + + fit, err := eventer.FilterFixedBytesEvent(nil, [][24]byte{fblob}) + if err != nil { + t.Fatalf("failed to filter for fixed bytes events: %v", err) + } + defer fit.Close() + + if !fit.Next() { + t.Fatalf("fixed bytes log not found: %v", fit.Error()) + } + if fit.Event.NonIndexedBytes != fblob || fit.Event.IndexedBytes != fblob { + t.Errorf("fixed bytes log content mismatch: have %v, want {'%x', '%x'}", fit.Event, fblob, fblob) + } + if fit.Next() { + t.Errorf("unexpected fixed bytes event found: %+v", fit.Event) + } + if err = fit.Error(); err != nil { + t.Fatalf("fixed bytes event iteration failed: %v", err) + } + // Test subscribing to an event and raising it afterwards + ch := make(chan *EventerSimpleEvent, 16) + sub, err := eventer.WatchSimpleEvent(nil, ch, nil, nil, nil) + if err != nil { + t.Fatalf("failed to subscribe to simple events: %v", err) + } + if _, err := eventer.RaiseSimpleEvent(auth, common.Address{255}, [32]byte{255}, true, big.NewInt(255)); err != nil { + t.Fatalf("failed to raise subscribed simple event: %v", err) + } + sim.Commit() + + select { + case event := <-ch: + if event.Value.Uint64() != 255 { + t.Errorf("simple log content mismatch: have %v, want 255", event) + } + case <-time.After(250 * time.Millisecond): + t.Fatalf("subscribed simple event didn't arrive") + } + // Unsubscribe from the event and make sure we're not delivered more + sub.Unsubscribe() + + if _, err := eventer.RaiseSimpleEvent(auth, common.Address{254}, [32]byte{254}, true, big.NewInt(254)); err != nil { + t.Fatalf("failed to raise subscribed simple event: %v", err) + } + sim.Commit() + + select { + case event := <-ch: + t.Fatalf("unsubscribed simple event arrived: %v", event) + case <-time.After(250 * time.Millisecond): + } + `, + }, + { + `DeeplyNestedArray`, + ` + contract DeeplyNestedArray { + uint64[3][4][5] public deepUint64Array; + function storeDeepUintArray(uint64[3][4][5] arr) public { + deepUint64Array = arr; + } + function retrieveDeepArray() public view returns (uint64[3][4][5]) { + return deepUint64Array; + } + } + `, + `6060604052341561000f57600080fd5b6106438061001e6000396000f300606060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063344248551461005c5780638ed4573a1461011457806398ed1856146101ab575b600080fd5b341561006757600080fd5b610112600480806107800190600580602002604051908101604052809291906000905b828210156101055783826101800201600480602002604051908101604052809291906000905b828210156100f25783826060020160038060200260405190810160405280929190826003602002808284378201915050505050815260200190600101906100b0565b505050508152602001906001019061008a565b5050505091905050610208565b005b341561011f57600080fd5b61012761021d565b604051808260056000925b8184101561019b578284602002015160046000925b8184101561018d5782846020020151600360200280838360005b8381101561017c578082015181840152602081019050610161565b505050509050019260010192610147565b925050509260010192610132565b9250505091505060405180910390f35b34156101b657600080fd5b6101de6004808035906020019091908035906020019091908035906020019091905050610309565b604051808267ffffffffffffffff1667ffffffffffffffff16815260200191505060405180910390f35b80600090600561021992919061035f565b5050565b6102256103b0565b6000600580602002604051908101604052809291906000905b8282101561030057838260040201600480602002604051908101604052809291906000905b828210156102ed578382016003806020026040519081016040528092919082600380156102d9576020028201916000905b82829054906101000a900467ffffffffffffffff1667ffffffffffffffff16815260200190600801906020826007010492830192600103820291508084116102945790505b505050505081526020019060010190610263565b505050508152602001906001019061023e565b50505050905090565b60008360058110151561031857fe5b600402018260048110151561032957fe5b018160038110151561033757fe5b6004918282040191900660080292509250509054906101000a900467ffffffffffffffff1681565b826005600402810192821561039f579160200282015b8281111561039e5782518290600461038e9291906103df565b5091602001919060040190610375565b5b5090506103ac919061042d565b5090565b610780604051908101604052806005905b6103c9610459565b8152602001906001900390816103c15790505090565b826004810192821561041c579160200282015b8281111561041b5782518290600361040b929190610488565b50916020019190600101906103f2565b5b5090506104299190610536565b5090565b61045691905b8082111561045257600081816104499190610562565b50600401610433565b5090565b90565b610180604051908101604052806004905b6104726105a7565b81526020019060019003908161046a5790505090565b82600380016004900481019282156105255791602002820160005b838211156104ef57835183826101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555092602001926008016020816007010492830192600103026104a3565b80156105235782816101000a81549067ffffffffffffffff02191690556008016020816007010492830192600103026104ef565b505b50905061053291906105d9565b5090565b61055f91905b8082111561055b57600081816105529190610610565b5060010161053c565b5090565b90565b50600081816105719190610610565b50600101600081816105839190610610565b50600101600081816105959190610610565b5060010160006105a59190610610565b565b6060604051908101604052806003905b600067ffffffffffffffff168152602001906001900390816105b75790505090565b61060d91905b8082111561060957600081816101000a81549067ffffffffffffffff0219169055506001016105df565b5090565b90565b50600090555600a165627a7a7230582087e5a43f6965ab6ef7a4ff056ab80ed78fd8c15cff57715a1bf34ec76a93661c0029`, + `[{"constant":false,"inputs":[{"name":"arr","type":"uint64[3][4][5]"}],"name":"storeDeepUintArray","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"retrieveDeepArray","outputs":[{"name":"","type":"uint64[3][4][5]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"name":"deepUint64Array","outputs":[{"name":"","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"}]`, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + + //deploy the test contract + _, _, testContract, err := DeployDeeplyNestedArray(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy test contract: %v", err) + } + + // Finish deploy. + sim.Commit() + + //Create coordinate-filled array, for testing purposes. + testArr := [5][4][3]uint64{} + for i := 0; i < 5; i++ { + testArr[i] = [4][3]uint64{} + for j := 0; j < 4; j++ { + testArr[i][j] = [3]uint64{} + for k := 0; k < 3; k++ { + //pack the coordinates, each array value will be unique, and can be validated easily. + testArr[i][j][k] = uint64(i) << 16 | uint64(j) << 8 | uint64(k) + } + } + } + + if _, err := testContract.StoreDeepUintArray(&bind.TransactOpts{ + From: auth.From, + Signer: auth.Signer, + }, testArr); err != nil { + t.Fatalf("Failed to store nested array in test contract: %v", err) + } + + sim.Commit() + + retrievedArr, err := testContract.RetrieveDeepArray(&bind.CallOpts{ + From: auth.From, + Pending: false, + }) + if err != nil { + t.Fatalf("Failed to retrieve nested array from test contract: %v", err) + } + + //quick check to see if contents were copied + // (See accounts/abi/unpack_test.go for more extensive testing) + if retrievedArr[4][3][2] != testArr[4][3][2] { + t.Fatalf("Retrieved value does not match expected value! got: %d, expected: %d. %v", retrievedArr[4][3][2], testArr[4][3][2], err) + } + `, + }, +} + +// Tests that packages generated by the binder can be successfully compiled and +// the requested tester run against it. +func TestBindings(t *testing.T) { + // Skip the test if no Go command can be found + gocmd := runtime.GOROOT() + "/bin/go" + if !common.FileExist(gocmd) { + t.Skip("go sdk not found for testing") + } + // Create a temporary workspace for the test suite + ws, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("failed to create temporary workspace: %v", err) + } + defer os.RemoveAll(ws) + + pkg := filepath.Join(ws, "bindtest") + if err = os.MkdirAll(pkg, 0700); err != nil { + t.Fatalf("failed to create package: %v", err) + } + // Generate the test suite for all the contracts + for i, tt := range bindTests { + // Generate the binding and create a Go source file in the workspace + bind, err := Bind([]string{tt.name}, []string{tt.abi}, []string{tt.bytecode}, "bindtest", LangGo) + if err != nil { + t.Fatalf("test %d: failed to generate binding: %v", i, err) + } + if err = ioutil.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+".go"), []byte(bind), 0600); err != nil { + t.Fatalf("test %d: failed to write binding: %v", i, err) + } + // Generate the test file with the injected test code + code := fmt.Sprintf(` + package bindtest + + import ( + "testing" + %s + ) + + func Test%s(t *testing.T) { + %s + } + `, tt.imports, tt.name, tt.tester) + if err := ioutil.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+"_test.go"), []byte(code), 0600); err != nil { + t.Fatalf("test %d: failed to write tests: %v", i, err) + } + } + // Test the entire package and report any failures + cmd := exec.Command(gocmd, "test", "-v", "-count", "1") + cmd.Dir = pkg + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to run binding test: %v\n%s", err, out) + } +} diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go new file mode 100644 index 000000000..02d0258e0 --- /dev/null +++ b/accounts/abi/bind/template.go @@ -0,0 +1,546 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bind + +import "github.com/ethereum/go-ethereum/accounts/abi" + +// tmplData is the data structure required to fill the binding template. +type tmplData struct { + Package string // Name of the package to place the generated file in + Contracts map[string]*tmplContract // List of contracts to generate into this file +} + +// tmplContract contains the data needed to generate an individual contract binding. +type tmplContract struct { + Type string // Type name of the main contract binding + InputABI string // JSON ABI used as the input to generate the binding from + InputBin string // Optional EVM bytecode used to denetare deploy code from + Constructor abi.Method // Contract constructor for deploy parametrization + Calls map[string]*tmplMethod // Contract calls that only read state data + Transacts map[string]*tmplMethod // Contract calls that write state data + Events map[string]*tmplEvent // Contract events accessors +} + +// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed +// and cached data fields. +type tmplMethod struct { + Original abi.Method // Original method as parsed by the abi package + Normalized abi.Method // Normalized version of the parsed method (capitalized names, non-anonymous args/returns) + Structured bool // Whether the returns should be accumulated into a struct +} + +// tmplEvent is a wrapper around an a +type tmplEvent struct { + Original abi.Event // Original event as parsed by the abi package + Normalized abi.Event // Normalized version of the parsed fields +} + +// tmplSource is language to template mapping containing all the supported +// programming languages the package can generate to. +var tmplSource = map[Lang]string{ + LangGo: tmplSourceGo, + LangJava: tmplSourceJava, +} + +// tmplSourceGo is the Go source template use to generate the contract binding +// based on. +const tmplSourceGo = ` +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package {{.Package}} + +import ( + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = abi.U256 + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +{{range $contract := .Contracts}} + // {{.Type}}ABI is the input ABI used to generate the binding from. + const {{.Type}}ABI = "{{.InputABI}}" + + {{if .InputBin}} + // {{.Type}}Bin is the compiled bytecode used for deploying new contracts. + const {{.Type}}Bin = ` + "`" + `{{.InputBin}}` + "`" + ` + + // Deploy{{.Type}} deploys a new Ethereum contract, binding an instance of {{.Type}} to it. + func Deploy{{.Type}}(auth *bind.TransactOpts, backend bind.ContractBackend {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type}}{{end}}) (common.Address, *types.Transaction, *{{.Type}}, error) { + parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}}) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil + } + {{end}} + + // {{.Type}} is an auto generated Go binding around an Ethereum contract. + type {{.Type}} struct { + {{.Type}}Caller // Read-only binding to the contract + {{.Type}}Transactor // Write-only binding to the contract + {{.Type}}Filterer // Log filterer for contract events + } + + // {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract. + type {{.Type}}Caller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls + } + + // {{.Type}}Transactor is an auto generated write-only Go binding around an Ethereum contract. + type {{.Type}}Transactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls + } + + // {{.Type}}Filterer is an auto generated log filtering Go binding around an Ethereum contract events. + type {{.Type}}Filterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls + } + + // {{.Type}}Session is an auto generated Go binding around an Ethereum contract, + // with pre-set call and transact options. + type {{.Type}}Session struct { + Contract *{{.Type}} // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session + } + + // {{.Type}}CallerSession is an auto generated read-only Go binding around an Ethereum contract, + // with pre-set call options. + type {{.Type}}CallerSession struct { + Contract *{{.Type}}Caller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + } + + // {{.Type}}TransactorSession is an auto generated write-only Go binding around an Ethereum contract, + // with pre-set transact options. + type {{.Type}}TransactorSession struct { + Contract *{{.Type}}Transactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session + } + + // {{.Type}}Raw is an auto generated low-level Go binding around an Ethereum contract. + type {{.Type}}Raw struct { + Contract *{{.Type}} // Generic contract binding to access the raw methods on + } + + // {{.Type}}CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. + type {{.Type}}CallerRaw struct { + Contract *{{.Type}}Caller // Generic read-only contract binding to access the raw methods on + } + + // {{.Type}}TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. + type {{.Type}}TransactorRaw struct { + Contract *{{.Type}}Transactor // Generic write-only contract binding to access the raw methods on + } + + // New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract. + func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) { + contract, err := bind{{.Type}}(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil + } + + // New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract. + func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) { + contract, err := bind{{.Type}}(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &{{.Type}}Caller{contract: contract}, nil + } + + // New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract. + func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) { + contract, err := bind{{.Type}}(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &{{.Type}}Transactor{contract: contract}, nil + } + + // New{{.Type}}Filterer creates a new log filterer instance of {{.Type}}, bound to a specific deployed contract. + func New{{.Type}}Filterer(address common.Address, filterer bind.ContractFilterer) (*{{.Type}}Filterer, error) { + contract, err := bind{{.Type}}(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &{{.Type}}Filterer{contract: contract}, nil + } + + // bind{{.Type}} binds a generic wrapper to an already deployed contract. + func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil + } + + // Call invokes the (constant) contract method with params as input values and + // sets the output to result. The result type might be a single field for simple + // returns, a slice of interfaces for anonymous returns and a struct for named + // returns. + func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _{{$contract.Type}}.Contract.{{$contract.Type}}Caller.contract.Call(opts, result, method, params...) + } + + // Transfer initiates a plain transaction to move funds to the contract, calling + // its default method if one is available. + func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transfer(opts) + } + + // Transact invokes the (paid) contract method with params as input values. + func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transact(opts, method, params...) + } + + // Call invokes the (constant) contract method with params as input values and + // sets the output to result. The result type might be a single field for simple + // returns, a slice of interfaces for anonymous returns and a struct for named + // returns. + func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _{{$contract.Type}}.Contract.contract.Call(opts, result, method, params...) + } + + // Transfer initiates a plain transaction to move funds to the contract, calling + // its default method if one is available. + func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.contract.Transfer(opts) + } + + // Transact invokes the (paid) contract method with params as input values. + func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.contract.Transact(opts, method, params...) + } + + {{range .Calls}} + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type}},{{end}}{{end}} error) { + {{if .Structured}}ret := new(struct{ + {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}} + {{end}} + }){{else}}var ( + {{range $i, $_ := .Normalized.Outputs}}ret{{$i}} = new({{bindtype .Type}}) + {{end}} + ){{end}} + out := {{if .Structured}}ret{{else}}{{if eq (len .Normalized.Outputs) 1}}ret0{{else}}&[]interface{}{ + {{range $i, $_ := .Normalized.Outputs}}ret{{$i}}, + {{end}} + }{{end}}{{end}} + err := _{{$contract.Type}}.contract.Call(opts, out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + return {{if .Structured}}*ret,{{else}}{{range $i, $_ := .Normalized.Outputs}}*ret{{$i}},{{end}}{{end}} err + } + + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type}},{{end}} {{end}} error) { + return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + } + + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type}},{{end}} {{end}} error) { + return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + } + {{end}} + + {{range .Transacts}} + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type}} {{end}}) (*types.Transaction, error) { + return _{{$contract.Type}}.contract.Transact(opts, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + } + + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}}) + } + + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}}) + } + {{end}} + + {{range .Events}} + // {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract. + type {{$contract.Type}}{{.Normalized.Name}}Iterator struct { + Event *{{$contract.Type}}{{.Normalized.Name}} // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration + } + // Next advances the iterator to the subsequent event, returning whether there + // are any more events found. In case of a retrieval or parsing error, false is + // returned and Error() can be queried for the exact failure. + func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Next() bool { + // If the iterator failed, stop iterating + if (it.fail != nil) { + return false + } + // If the iterator completed, deliver directly whatever's available + if (it.done) { + select { + case log := <-it.logs: + it.Event = new({{$contract.Type}}{{.Normalized.Name}}) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new({{$contract.Type}}{{.Normalized.Name}}) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } + } + // Error returns any retrieval or parsing error occurred during filtering. + func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Error() error { + return it.fail + } + // Close terminates the iteration process, releasing any pending underlying + // resources. + func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Close() error { + it.sub.Unsubscribe() + return nil + } + + // {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract. + type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}} + {{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type}}{{else}}{{bindtype .Type}}{{end}}; {{end}} + Raw types.Log // Blockchain specific contextual infos + } + + // Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.Id}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) { + {{range .Normalized.Inputs}} + {{if .Indexed}}var {{.Name}}Rule []interface{} + for _, {{.Name}}Item := range {{.Name}} { + {{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item) + }{{end}}{{end}} + + logs, sub, err := _{{$contract.Type}}.contract.FilterLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}}) + if err != nil { + return nil, err + } + return &{{$contract.Type}}{{.Normalized.Name}}Iterator{contract: _{{$contract.Type}}.contract, event: "{{.Original.Name}}", logs: logs, sub: sub}, nil + } + + // Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.Id}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type}}{{end}}{{end}}) (event.Subscription, error) { + {{range .Normalized.Inputs}} + {{if .Indexed}}var {{.Name}}Rule []interface{} + for _, {{.Name}}Item := range {{.Name}} { + {{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item) + }{{end}}{{end}} + + logs, sub, err := _{{$contract.Type}}.contract.WatchLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}}) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new({{$contract.Type}}{{.Normalized.Name}}) + if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil + } + {{end}} +{{end}} +` + +// tmplSourceJava is the Java source template use to generate the contract binding +// based on. +const tmplSourceJava = ` +// This file is an automatically generated Java binding. Do not modify as any +// change will likely be lost upon the next re-generation! + +package {{.Package}}; + +import org.ethereum.geth.*; +import org.ethereum.geth.internal.*; + +{{range $contract := .Contracts}} + public class {{.Type}} { + // ABI is the input ABI used to generate the binding from. + public final static String ABI = "{{.InputABI}}"; + + {{if .InputBin}} + // BYTECODE is the compiled bytecode used for deploying new contracts. + public final static byte[] BYTECODE = "{{.InputBin}}".getBytes(); + + // deploy deploys a new Ethereum contract, binding an instance of {{.Type}} to it. + public static {{.Type}} deploy(TransactOpts auth, EthereumClient client{{range .Constructor.Inputs}}, {{bindtype .Type}} {{.Name}}{{end}}) throws Exception { + Interfaces args = Geth.newInterfaces({{(len .Constructor.Inputs)}}); + {{range $index, $element := .Constructor.Inputs}} + args.set({{$index}}, Geth.newInterface()); args.get({{$index}}).set{{namedtype (bindtype .Type) .Type}}({{.Name}}); + {{end}} + return new {{.Type}}(Geth.deployContract(auth, ABI, BYTECODE, client, args)); + } + + // Internal constructor used by contract deployment. + private {{.Type}}(BoundContract deployment) { + this.Address = deployment.getAddress(); + this.Deployer = deployment.getDeployer(); + this.Contract = deployment; + } + {{end}} + + // Ethereum address where this contract is located at. + public final Address Address; + + // Ethereum transaction in which this contract was deployed (if known!). + public final Transaction Deployer; + + // Contract instance bound to a blockchain address. + private final BoundContract Contract; + + // Creates a new instance of {{.Type}}, bound to a specific deployed contract. + public {{.Type}}(Address address, EthereumClient client) throws Exception { + this(Geth.bindContract(address, ABI, client)); + } + + {{range .Calls}} + {{if gt (len .Normalized.Outputs) 1}} + // {{capitalise .Normalized.Name}}Results is the output of a call to {{.Normalized.Name}}. + public class {{capitalise .Normalized.Name}}Results { + {{range $index, $item := .Normalized.Outputs}}public {{bindtype .Type}} {{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}}; + {{end}} + } + {{end}} + + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}. + // + // Solidity: {{.Original.String}} + public {{if gt (len .Normalized.Outputs) 1}}{{capitalise .Normalized.Name}}Results{{else}}{{range .Normalized.Outputs}}{{bindtype .Type}}{{end}}{{end}} {{.Normalized.Name}}(CallOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type}} {{.Name}}{{end}}) throws Exception { + Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}}); + {{range $index, $item := .Normalized.Inputs}}args.set({{$index}}, Geth.newInterface()); args.get({{$index}}).set{{namedtype (bindtype .Type) .Type}}({{.Name}}); + {{end}} + + Interfaces results = Geth.newInterfaces({{(len .Normalized.Outputs)}}); + {{range $index, $item := .Normalized.Outputs}}Interface result{{$index}} = Geth.newInterface(); result{{$index}}.setDefault{{namedtype (bindtype .Type) .Type}}(); results.set({{$index}}, result{{$index}}); + {{end}} + + if (opts == null) { + opts = Geth.newCallOpts(); + } + this.Contract.call(opts, results, "{{.Original.Name}}", args); + {{if gt (len .Normalized.Outputs) 1}} + {{capitalise .Normalized.Name}}Results result = new {{capitalise .Normalized.Name}}Results(); + {{range $index, $item := .Normalized.Outputs}}result.{{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}} = results.get({{$index}}).get{{namedtype (bindtype .Type) .Type}}(); + {{end}} + return result; + {{else}}{{range .Normalized.Outputs}}return results.get(0).get{{namedtype (bindtype .Type) .Type}}();{{end}} + {{end}} + } + {{end}} + + {{range .Transacts}} + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}. + // + // Solidity: {{.Original.String}} + public Transaction {{.Normalized.Name}}(TransactOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type}} {{.Name}}{{end}}) throws Exception { + Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}}); + {{range $index, $item := .Normalized.Inputs}}args.set({{$index}}, Geth.newInterface()); args.get({{$index}}).set{{namedtype (bindtype .Type) .Type}}({{.Name}}); + {{end}} + + return this.Contract.transact(opts, "{{.Original.Name}}" , args); + } + {{end}} + } +{{end}} +` diff --git a/accounts/abi/bind/topics.go b/accounts/abi/bind/topics.go new file mode 100644 index 000000000..c7657b4a4 --- /dev/null +++ b/accounts/abi/bind/topics.go @@ -0,0 +1,241 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bind + +import ( + "encoding/binary" + "errors" + "fmt" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// makeTopics converts a filter query argument list into a filter topic set. +func makeTopics(query ...[]interface{}) ([][]common.Hash, error) { + topics := make([][]common.Hash, len(query)) + for i, filter := range query { + for _, rule := range filter { + var topic common.Hash + + // Try to generate the topic based on simple types + switch rule := rule.(type) { + case common.Hash: + copy(topic[:], rule[:]) + case common.Address: + copy(topic[common.HashLength-common.AddressLength:], rule[:]) + case *big.Int: + blob := rule.Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case bool: + if rule { + topic[common.HashLength-1] = 1 + } + case int8: + blob := big.NewInt(int64(rule)).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case int16: + blob := big.NewInt(int64(rule)).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case int32: + blob := big.NewInt(int64(rule)).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case int64: + blob := big.NewInt(rule).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case uint8: + blob := new(big.Int).SetUint64(uint64(rule)).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case uint16: + blob := new(big.Int).SetUint64(uint64(rule)).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case uint32: + blob := new(big.Int).SetUint64(uint64(rule)).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case uint64: + blob := new(big.Int).SetUint64(rule).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case string: + hash := crypto.Keccak256Hash([]byte(rule)) + copy(topic[:], hash[:]) + case []byte: + hash := crypto.Keccak256Hash(rule) + copy(topic[:], hash[:]) + + default: + // Attempt to generate the topic from funky types + val := reflect.ValueOf(rule) + + switch { + + // static byte array + case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8: + reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val) + + default: + return nil, fmt.Errorf("unsupported indexed type: %T", rule) + } + } + topics[i] = append(topics[i], topic) + } + } + return topics, nil +} + +// Big batch of reflect types for topic reconstruction. +var ( + reflectHash = reflect.TypeOf(common.Hash{}) + reflectAddress = reflect.TypeOf(common.Address{}) + reflectBigInt = reflect.TypeOf(new(big.Int)) +) + +// parseTopics converts the indexed topic fields into actual log field values. +// +// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256 +// hashes as the topic value! +func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error { + // Sanity check that the fields and topics match up + if len(fields) != len(topics) { + return errors.New("topic/field count mismatch") + } + // Iterate over all the fields and reconstruct them from topics + for _, arg := range fields { + if !arg.Indexed { + return errors.New("non-indexed field in topic reconstruction") + } + field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name)) + + // Try to parse the topic back into the fields based on primitive types + switch field.Kind() { + case reflect.Bool: + if topics[0][common.HashLength-1] == 1 { + field.Set(reflect.ValueOf(true)) + } + case reflect.Int8: + num := new(big.Int).SetBytes(topics[0][:]) + field.Set(reflect.ValueOf(int8(num.Int64()))) + + case reflect.Int16: + num := new(big.Int).SetBytes(topics[0][:]) + field.Set(reflect.ValueOf(int16(num.Int64()))) + + case reflect.Int32: + num := new(big.Int).SetBytes(topics[0][:]) + field.Set(reflect.ValueOf(int32(num.Int64()))) + + case reflect.Int64: + num := new(big.Int).SetBytes(topics[0][:]) + field.Set(reflect.ValueOf(num.Int64())) + + case reflect.Uint8: + num := new(big.Int).SetBytes(topics[0][:]) + field.Set(reflect.ValueOf(uint8(num.Uint64()))) + + case reflect.Uint16: + num := new(big.Int).SetBytes(topics[0][:]) + field.Set(reflect.ValueOf(uint16(num.Uint64()))) + + case reflect.Uint32: + num := new(big.Int).SetBytes(topics[0][:]) + field.Set(reflect.ValueOf(uint32(num.Uint64()))) + + case reflect.Uint64: + num := new(big.Int).SetBytes(topics[0][:]) + field.Set(reflect.ValueOf(num.Uint64())) + + default: + // Ran out of plain primitive types, try custom types + switch field.Type() { + case reflectHash: // Also covers all dynamic types + field.Set(reflect.ValueOf(topics[0])) + + case reflectAddress: + var addr common.Address + copy(addr[:], topics[0][common.HashLength-common.AddressLength:]) + field.Set(reflect.ValueOf(addr)) + + case reflectBigInt: + num := new(big.Int).SetBytes(topics[0][:]) + field.Set(reflect.ValueOf(num)) + + default: + // Ran out of custom types, try the crazies + switch { + + // static byte array + case arg.Type.T == abi.FixedBytesTy: + reflect.Copy(field, reflect.ValueOf(topics[0][:arg.Type.Size])) + + default: + return fmt.Errorf("unsupported indexed type: %v", arg.Type) + } + } + } + topics = topics[1:] + } + return nil +} + +// parseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs +func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics []common.Hash) error { + // Sanity check that the fields and topics match up + if len(fields) != len(topics) { + return errors.New("topic/field count mismatch") + } + // Iterate over all the fields and reconstruct them from topics + for _, arg := range fields { + if !arg.Indexed { + return errors.New("non-indexed field in topic reconstruction") + } + + switch arg.Type.T { + case abi.BoolTy: + out[arg.Name] = topics[0][common.HashLength-1] == 1 + case abi.IntTy, abi.UintTy: + num := new(big.Int).SetBytes(topics[0][:]) + out[arg.Name] = num + case abi.AddressTy: + var addr common.Address + copy(addr[:], topics[0][common.HashLength-common.AddressLength:]) + out[arg.Name] = addr + case abi.HashTy: + out[arg.Name] = topics[0] + case abi.FixedBytesTy: + out[arg.Name] = topics[0][:] + case abi.StringTy, abi.BytesTy, abi.SliceTy, abi.ArrayTy: + // Array types (including strings and bytes) have their keccak256 hashes stored in the topic- not a hash + // whose bytes can be decoded to the actual value- so the best we can do is retrieve that hash + out[arg.Name] = topics[0] + case abi.FunctionTy: + if garbage := binary.BigEndian.Uint64(topics[0][0:8]); garbage != 0 { + return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[0].Bytes()) + } + var tmp [24]byte + copy(tmp[:], topics[0][8:32]) + out[arg.Name] = tmp + default: // Not handling tuples + return fmt.Errorf("unsupported indexed type: %v", arg.Type) + } + + topics = topics[1:] + } + + return nil +} diff --git a/accounts/abi/bind/topics_test.go b/accounts/abi/bind/topics_test.go new file mode 100644 index 000000000..e6f745a15 --- /dev/null +++ b/accounts/abi/bind/topics_test.go @@ -0,0 +1,103 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bind + +import ( + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" +) + +func TestMakeTopics(t *testing.T) { + type args struct { + query [][]interface{} + } + tests := []struct { + name string + args args + want [][]common.Hash + wantErr bool + }{ + { + "support fixed byte types, right padded to 32 bytes", + args{[][]interface{}{{[5]byte{1, 2, 3, 4, 5}}}}, + [][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := makeTopics(tt.args.query...) + if (err != nil) != tt.wantErr { + t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("makeTopics() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParseTopics(t *testing.T) { + type bytesStruct struct { + StaticBytes [5]byte + } + bytesType, _ := abi.NewType("bytes5", nil) + type args struct { + createObj func() interface{} + resultObj func() interface{} + fields abi.Arguments + topics []common.Hash + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "support fixed byte types, right padded to 32 bytes", + args: args{ + createObj: func() interface{} { return &bytesStruct{} }, + resultObj: func() interface{} { return &bytesStruct{StaticBytes: [5]byte{1, 2, 3, 4, 5}} }, + fields: abi.Arguments{abi.Argument{ + Name: "staticBytes", + Type: bytesType, + Indexed: true, + }}, + topics: []common.Hash{ + {1, 2, 3, 4, 5}, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + createObj := tt.args.createObj() + if err := parseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr { + t.Errorf("parseTopics() error = %v, wantErr %v", err, tt.wantErr) + } + resultObj := tt.args.resultObj() + if !reflect.DeepEqual(createObj, resultObj) { + t.Errorf("parseTopics() = %v, want %v", createObj, resultObj) + } + }) + } +} diff --git a/accounts/abi/bind/util.go b/accounts/abi/bind/util.go new file mode 100644 index 000000000..d129993ca --- /dev/null +++ b/accounts/abi/bind/util.go @@ -0,0 +1,76 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bind + +import ( + "context" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +// WaitMined waits for tx to be mined on the blockchain. +// It stops waiting when the context is canceled. +func WaitMined(ctx context.Context, b DeployBackend, tx *types.Transaction) (*types.Receipt, error) { + queryTicker := time.NewTicker(time.Second) + defer queryTicker.Stop() + + logger := log.New("hash", tx.Hash()) + for { + receipt, err := b.TransactionReceipt(ctx, tx.Hash()) + if receipt != nil { + return receipt, nil + } + if err != nil { + logger.Trace("Receipt retrieval failed", "err", err) + } else { + logger.Trace("Transaction not yet mined") + } + // Wait for the next round. + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-queryTicker.C: + } + } +} + +// WaitDeployed waits for a contract deployment transaction and returns the on-chain +// contract address when it is mined. It stops waiting when ctx is canceled. +func WaitDeployed(ctx context.Context, b DeployBackend, tx *types.Transaction) (common.Address, error) { + if tx.To() != nil { + return common.Address{}, fmt.Errorf("tx is not contract creation") + } + receipt, err := WaitMined(ctx, b, tx) + if err != nil { + return common.Address{}, err + } + if receipt.ContractAddress == (common.Address{}) { + return common.Address{}, fmt.Errorf("zero address") + } + // Check that code has indeed been deployed at the address. + // This matters on pre-Homestead chains: OOG in the constructor + // could leave an empty account behind. + code, err := b.CodeAt(ctx, receipt.ContractAddress, nil) + if err == nil && len(code) == 0 { + err = ErrNoCodeAfterDeploy + } + return receipt.ContractAddress, err +} diff --git a/accounts/abi/bind/util_test.go b/accounts/abi/bind/util_test.go new file mode 100644 index 000000000..8f4092971 --- /dev/null +++ b/accounts/abi/bind/util_test.go @@ -0,0 +1,94 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bind_test + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + +var waitDeployedTests = map[string]struct { + code string + gas uint64 + wantAddress common.Address + wantErr error +}{ + "successful deploy": { + code: `6060604052600a8060106000396000f360606040526008565b00`, + gas: 3000000, + wantAddress: common.HexToAddress("0x3a220f351252089d385b29beca14e27f204c296a"), + }, + "empty code": { + code: ``, + gas: 300000, + wantErr: bind.ErrNoCodeAfterDeploy, + wantAddress: common.HexToAddress("0x3a220f351252089d385b29beca14e27f204c296a"), + }, +} + +func TestWaitDeployed(t *testing.T) { + for name, test := range waitDeployedTests { + backend := backends.NewSimulatedBackend( + core.GenesisAlloc{ + crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)}, + }, 10000000, + ) + + // Create the transaction. + tx := types.NewContractCreation(0, big.NewInt(0), test.gas, big.NewInt(1), common.FromHex(test.code)) + tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + + // Wait for it to get mined in the background. + var ( + err error + address common.Address + mined = make(chan struct{}) + ctx = context.Background() + ) + go func() { + address, err = bind.WaitDeployed(ctx, backend, tx) + close(mined) + }() + + // Send and mine the transaction. + backend.SendTransaction(ctx, tx) + backend.Commit() + + select { + case <-mined: + if err != test.wantErr { + t.Errorf("test %q: error mismatch: got %q, want %q", name, err, test.wantErr) + } + if address != test.wantAddress { + t.Errorf("test %q: unexpected contract address %s", name, address.Hex()) + } + case <-time.After(2 * time.Second): + t.Errorf("test %q: timeout", name) + } + } +} diff --git a/accounts/abi/doc.go b/accounts/abi/doc.go new file mode 100644 index 000000000..824206858 --- /dev/null +++ b/accounts/abi/doc.go @@ -0,0 +1,26 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package abi implements the Ethereum ABI (Application Binary +// Interface). +// +// The Ethereum ABI is strongly typed, known at compile time +// and static. This ABI will handle basic type casting; unsigned +// to signed and visa versa. It does not handle slice casting such +// as unsigned slice to signed slice. Bit size type casting is also +// handled. ints with a bit size of 32 will be properly cast to int256, +// etc. +package abi diff --git a/accounts/abi/error.go b/accounts/abi/error.go new file mode 100644 index 000000000..9d8674ad0 --- /dev/null +++ b/accounts/abi/error.go @@ -0,0 +1,84 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "errors" + "fmt" + "reflect" +) + +var ( + errBadBool = errors.New("abi: improperly encoded boolean value") +) + +// formatSliceString formats the reflection kind with the given slice size +// and returns a formatted string representation. +func formatSliceString(kind reflect.Kind, sliceSize int) string { + if sliceSize == -1 { + return fmt.Sprintf("[]%v", kind) + } + return fmt.Sprintf("[%d]%v", sliceSize, kind) +} + +// sliceTypeCheck checks that the given slice can by assigned to the reflection +// type in t. +func sliceTypeCheck(t Type, val reflect.Value) error { + if val.Kind() != reflect.Slice && val.Kind() != reflect.Array { + return typeErr(formatSliceString(t.Kind, t.Size), val.Type()) + } + + if t.T == ArrayTy && val.Len() != t.Size { + return typeErr(formatSliceString(t.Elem.Kind, t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len())) + } + + if t.Elem.T == SliceTy { + if val.Len() > 0 { + return sliceTypeCheck(*t.Elem, val.Index(0)) + } + } else if t.Elem.T == ArrayTy { + return sliceTypeCheck(*t.Elem, val.Index(0)) + } + + if elemKind := val.Type().Elem().Kind(); elemKind != t.Elem.Kind { + return typeErr(formatSliceString(t.Elem.Kind, t.Size), val.Type()) + } + return nil +} + +// typeCheck checks that the given reflection value can be assigned to the reflection +// type in t. +func typeCheck(t Type, value reflect.Value) error { + if t.T == SliceTy || t.T == ArrayTy { + return sliceTypeCheck(t, value) + } + + // Check base type validity. Element types will be checked later on. + if t.Kind != value.Kind() { + return typeErr(t.Kind, value.Kind()) + } else if t.T == FixedBytesTy && t.Size != value.Len() { + return typeErr(t.Type, value.Type()) + } else { + return nil + } + +} + +// typeErr returns a formatted type casting error. +func typeErr(expected, got interface{}) error { + return fmt.Errorf("abi: cannot use %v as type %v as argument", got, expected) +} diff --git a/accounts/abi/event.go b/accounts/abi/event.go new file mode 100644 index 000000000..9392c1990 --- /dev/null +++ b/accounts/abi/event.go @@ -0,0 +1,57 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// Event is an event potentially triggered by the EVM's LOG mechanism. The Event +// holds type information (inputs) about the yielded output. Anonymous events +// don't get the signature canonical representation as the first LOG topic. +type Event struct { + Name string + Anonymous bool + Inputs Arguments +} + +func (e Event) String() string { + inputs := make([]string, len(e.Inputs)) + for i, input := range e.Inputs { + inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name) + if input.Indexed { + inputs[i] = fmt.Sprintf("%v indexed %v", input.Type, input.Name) + } + } + return fmt.Sprintf("event %v(%v)", e.Name, strings.Join(inputs, ", ")) +} + +// Id returns the canonical representation of the event's signature used by the +// abi definition to identify event names and types. +func (e Event) Id() common.Hash { + types := make([]string, len(e.Inputs)) + i := 0 + for _, input := range e.Inputs { + types[i] = input.Type.String() + i++ + } + return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ","))))) +} diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go new file mode 100644 index 000000000..e735cceb8 --- /dev/null +++ b/accounts/abi/event_test.go @@ -0,0 +1,428 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "math/big" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var jsonEventTransfer = []byte(`{ + "anonymous": false, + "inputs": [ + { + "indexed": true, "name": "from", "type": "address" + }, { + "indexed": true, "name": "to", "type": "address" + }, { + "indexed": false, "name": "value", "type": "uint256" + }], + "name": "Transfer", + "type": "event" +}`) + +var jsonEventPledge = []byte(`{ + "anonymous": false, + "inputs": [{ + "indexed": false, "name": "who", "type": "address" + }, { + "indexed": false, "name": "wad", "type": "uint128" + }, { + "indexed": false, "name": "currency", "type": "bytes3" + }], + "name": "Pledge", + "type": "event" +}`) + +var jsonEventMixedCase = []byte(`{ + "anonymous": false, + "inputs": [{ + "indexed": false, "name": "value", "type": "uint256" + }, { + "indexed": false, "name": "_value", "type": "uint256" + }, { + "indexed": false, "name": "Value", "type": "uint256" + }], + "name": "MixedCase", + "type": "event" + }`) + +// 1000000 +var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240" + +// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd" +var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000" + +// 1000000,2218516807680,1000001 +var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241" + +func TestEventId(t *testing.T) { + var table = []struct { + definition string + expectations map[string]common.Hash + }{ + { + definition: `[ + { "type" : "event", "name" : "Balance", "inputs": [{ "name" : "in", "type": "uint256" }] }, + { "type" : "event", "name" : "Check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] } + ]`, + expectations: map[string]common.Hash{ + "Balance": crypto.Keccak256Hash([]byte("Balance(uint256)")), + "Check": crypto.Keccak256Hash([]byte("Check(address,uint256)")), + }, + }, + } + + for _, test := range table { + abi, err := JSON(strings.NewReader(test.definition)) + if err != nil { + t.Fatal(err) + } + + for name, event := range abi.Events { + if event.Id() != test.expectations[name] { + t.Errorf("expected id to be %x, got %x", test.expectations[name], event.Id()) + } + } + } +} + +func TestEventString(t *testing.T) { + var table = []struct { + definition string + expectations map[string]string + }{ + { + definition: `[ + { "type" : "event", "name" : "Balance", "inputs": [{ "name" : "in", "type": "uint256" }] }, + { "type" : "event", "name" : "Check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] }, + { "type" : "event", "name" : "Transfer", "inputs": [{ "name": "from", "type": "address", "indexed": true }, { "name": "to", "type": "address", "indexed": true }, { "name": "value", "type": "uint256" }] } + ]`, + expectations: map[string]string{ + "Balance": "event Balance(uint256 in)", + "Check": "event Check(address t, uint256 b)", + "Transfer": "event Transfer(address indexed from, address indexed to, uint256 value)", + }, + }, + } + + for _, test := range table { + abi, err := JSON(strings.NewReader(test.definition)) + if err != nil { + t.Fatal(err) + } + + for name, event := range abi.Events { + if event.String() != test.expectations[name] { + t.Errorf("expected string to be %s, got %s", test.expectations[name], event.String()) + } + } + } +} + +// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array. +func TestEventMultiValueWithArrayUnpack(t *testing.T) { + definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` + type testStruct struct { + Value1 [2]uint8 + Value2 uint8 + } + abi, err := JSON(strings.NewReader(definition)) + require.NoError(t, err) + var b bytes.Buffer + var i uint8 = 1 + for ; i <= 3; i++ { + b.Write(packNum(reflect.ValueOf(i))) + } + var rst testStruct + require.NoError(t, abi.Unpack(&rst, "test", b.Bytes())) + require.Equal(t, [2]uint8{1, 2}, rst.Value1) + require.Equal(t, uint8(3), rst.Value2) +} + +func TestEventTupleUnpack(t *testing.T) { + + type EventTransfer struct { + Value *big.Int + } + + type EventTransferWithTag struct { + // this is valid because `value` is not exportable, + // so value is only unmarshalled into `Value1`. + value *big.Int + Value1 *big.Int `abi:"value"` + } + + type BadEventTransferWithSameFieldAndTag struct { + Value *big.Int + Value1 *big.Int `abi:"value"` + } + + type BadEventTransferWithDuplicatedTag struct { + Value1 *big.Int `abi:"value"` + Value2 *big.Int `abi:"value"` + } + + type BadEventTransferWithEmptyTag struct { + Value *big.Int `abi:""` + } + + type EventPledge struct { + Who common.Address + Wad *big.Int + Currency [3]byte + } + + type BadEventPledge struct { + Who string + Wad int + Currency [3]byte + } + + type EventMixedCase struct { + Value1 *big.Int `abi:"value"` + Value2 *big.Int `abi:"_value"` + Value3 *big.Int `abi:"Value"` + } + + bigint := new(big.Int) + bigintExpected := big.NewInt(1000000) + bigintExpected2 := big.NewInt(2218516807680) + bigintExpected3 := big.NewInt(1000001) + addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268") + var testCases = []struct { + data string + dest interface{} + expected interface{} + jsonLog []byte + error string + name string + }{{ + transferData1, + &EventTransfer{}, + &EventTransfer{Value: bigintExpected}, + jsonEventTransfer, + "", + "Can unpack ERC20 Transfer event into structure", + }, { + transferData1, + &[]interface{}{&bigint}, + &[]interface{}{&bigintExpected}, + jsonEventTransfer, + "", + "Can unpack ERC20 Transfer event into slice", + }, { + transferData1, + &EventTransferWithTag{}, + &EventTransferWithTag{Value1: bigintExpected}, + jsonEventTransfer, + "", + "Can unpack ERC20 Transfer event into structure with abi: tag", + }, { + transferData1, + &BadEventTransferWithDuplicatedTag{}, + &BadEventTransferWithDuplicatedTag{}, + jsonEventTransfer, + "struct: abi tag in 'Value2' already mapped", + "Can not unpack ERC20 Transfer event with duplicated abi tag", + }, { + transferData1, + &BadEventTransferWithSameFieldAndTag{}, + &BadEventTransferWithSameFieldAndTag{}, + jsonEventTransfer, + "abi: multiple variables maps to the same abi field 'value'", + "Can not unpack ERC20 Transfer event with a field and a tag mapping to the same abi variable", + }, { + transferData1, + &BadEventTransferWithEmptyTag{}, + &BadEventTransferWithEmptyTag{}, + jsonEventTransfer, + "struct: abi tag in 'Value' is empty", + "Can not unpack ERC20 Transfer event with an empty tag", + }, { + pledgeData1, + &EventPledge{}, + &EventPledge{ + addr, + bigintExpected2, + [3]byte{'u', 's', 'd'}}, + jsonEventPledge, + "", + "Can unpack Pledge event into structure", + }, { + pledgeData1, + &[]interface{}{&common.Address{}, &bigint, &[3]byte{}}, + &[]interface{}{ + &addr, + &bigintExpected2, + &[3]byte{'u', 's', 'd'}}, + jsonEventPledge, + "", + "Can unpack Pledge event into slice", + }, { + pledgeData1, + &[3]interface{}{&common.Address{}, &bigint, &[3]byte{}}, + &[3]interface{}{ + &addr, + &bigintExpected2, + &[3]byte{'u', 's', 'd'}}, + jsonEventPledge, + "", + "Can unpack Pledge event into an array", + }, { + pledgeData1, + &[]interface{}{new(int), 0, 0}, + &[]interface{}{}, + jsonEventPledge, + "abi: cannot unmarshal common.Address in to int", + "Can not unpack Pledge event into slice with wrong types", + }, { + pledgeData1, + &BadEventPledge{}, + &BadEventPledge{}, + jsonEventPledge, + "abi: cannot unmarshal common.Address in to string", + "Can not unpack Pledge event into struct with wrong filed types", + }, { + pledgeData1, + &[]interface{}{common.Address{}, new(big.Int)}, + &[]interface{}{}, + jsonEventPledge, + "abi: insufficient number of elements in the list/array for unpack, want 3, got 2", + "Can not unpack Pledge event into too short slice", + }, { + pledgeData1, + new(map[string]interface{}), + &[]interface{}{}, + jsonEventPledge, + "abi: cannot unmarshal tuple into map[string]interface {}", + "Can not unpack Pledge event into map", + }, { + mixedCaseData1, + &EventMixedCase{}, + &EventMixedCase{Value1: bigintExpected, Value2: bigintExpected2, Value3: bigintExpected3}, + jsonEventMixedCase, + "", + "Can unpack abi variables with mixed case", + }} + + for _, tc := range testCases { + assert := assert.New(t) + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert) + if tc.error == "" { + assert.Nil(err, "Should be able to unpack event data.") + assert.Equal(tc.expected, tc.dest, tc.name) + } else { + assert.EqualError(err, tc.error, tc.name) + } + }) + } +} + +func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, assert *assert.Assertions) error { + data, err := hex.DecodeString(hexData) + assert.NoError(err, "Hex data should be a correct hex-string") + var e Event + assert.NoError(json.Unmarshal(jsonEvent, &e), "Should be able to unmarshal event ABI") + a := ABI{Events: map[string]Event{"e": e}} + return a.Unpack(dest, "e", data) +} + +/* +Taken from +https://github.com/ethereum/go-ethereum/pull/15568 +*/ + +type testResult struct { + Values [2]*big.Int + Value1 *big.Int + Value2 *big.Int +} + +type testCase struct { + definition string + want testResult +} + +func (tc testCase) encoded(intType, arrayType Type) []byte { + var b bytes.Buffer + if tc.want.Value1 != nil { + val, _ := intType.pack(reflect.ValueOf(tc.want.Value1)) + b.Write(val) + } + + if !reflect.DeepEqual(tc.want.Values, [2]*big.Int{nil, nil}) { + val, _ := arrayType.pack(reflect.ValueOf(tc.want.Values)) + b.Write(val) + } + if tc.want.Value2 != nil { + val, _ := intType.pack(reflect.ValueOf(tc.want.Value2)) + b.Write(val) + } + return b.Bytes() +} + +// TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder. +func TestEventUnpackIndexed(t *testing.T) { + definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` + type testStruct struct { + Value1 uint8 + Value2 uint8 + } + abi, err := JSON(strings.NewReader(definition)) + require.NoError(t, err) + var b bytes.Buffer + b.Write(packNum(reflect.ValueOf(uint8(8)))) + var rst testStruct + require.NoError(t, abi.Unpack(&rst, "test", b.Bytes())) + require.Equal(t, uint8(0), rst.Value1) + require.Equal(t, uint8(8), rst.Value2) +} + +// TestEventIndexedWithArrayUnpack verifies that decoder will not overlow when static array is indexed input. +func TestEventIndexedWithArrayUnpack(t *testing.T) { + definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]` + type testStruct struct { + Value1 [2]uint8 + Value2 string + } + abi, err := JSON(strings.NewReader(definition)) + require.NoError(t, err) + var b bytes.Buffer + stringOut := "abc" + // number of fields that will be encoded * 32 + b.Write(packNum(reflect.ValueOf(32))) + b.Write(packNum(reflect.ValueOf(len(stringOut)))) + b.Write(common.RightPadBytes([]byte(stringOut), 32)) + + var rst testStruct + require.NoError(t, abi.Unpack(&rst, "test", b.Bytes())) + require.Equal(t, [2]uint8{0, 0}, rst.Value1) + require.Equal(t, stringOut, rst.Value2) +} diff --git a/accounts/abi/method.go b/accounts/abi/method.go new file mode 100644 index 000000000..2d8d3d658 --- /dev/null +++ b/accounts/abi/method.go @@ -0,0 +1,77 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/crypto" +) + +// Method represents a callable given a `Name` and whether the method is a constant. +// If the method is `Const` no transaction needs to be created for this +// particular Method call. It can easily be simulated using a local VM. +// For example a `Balance()` method only needs to retrieve something +// from the storage and therefor requires no Tx to be send to the +// network. A method such as `Transact` does require a Tx and thus will +// be flagged `true`. +// Input specifies the required input parameters for this gives method. +type Method struct { + Name string + Const bool + Inputs Arguments + Outputs Arguments +} + +// Sig returns the methods string signature according to the ABI spec. +// +// Example +// +// function foo(uint32 a, int b) = "foo(uint32,int256)" +// +// Please note that "int" is substitute for its canonical representation "int256" +func (method Method) Sig() string { + types := make([]string, len(method.Inputs)) + for i, input := range method.Inputs { + types[i] = input.Type.String() + } + return fmt.Sprintf("%v(%v)", method.Name, strings.Join(types, ",")) +} + +func (method Method) String() string { + inputs := make([]string, len(method.Inputs)) + for i, input := range method.Inputs { + inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name) + } + outputs := make([]string, len(method.Outputs)) + for i, output := range method.Outputs { + outputs[i] = output.Type.String() + if len(output.Name) > 0 { + outputs[i] += fmt.Sprintf(" %v", output.Name) + } + } + constant := "" + if method.Const { + constant = "constant " + } + return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.Name, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", ")) +} + +func (method Method) Id() []byte { + return crypto.Keccak256([]byte(method.Sig()))[:4] +} diff --git a/accounts/abi/method_test.go b/accounts/abi/method_test.go new file mode 100644 index 000000000..a98f1cd31 --- /dev/null +++ b/accounts/abi/method_test.go @@ -0,0 +1,61 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "strings" + "testing" +) + +const methoddata = ` +[ + { "type" : "function", "name" : "balance", "constant" : true }, + { "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, + { "type" : "function", "name" : "transfer", "constant" : false, "inputs" : [ { "name" : "from", "type" : "address" }, { "name" : "to", "type" : "address" }, { "name" : "value", "type" : "uint256" } ], "outputs" : [ { "name" : "success", "type" : "bool" } ] } +]` + +func TestMethodString(t *testing.T) { + var table = []struct { + method string + expectation string + }{ + { + method: "balance", + expectation: "function balance() constant returns()", + }, + { + method: "send", + expectation: "function send(uint256 amount) returns()", + }, + { + method: "transfer", + expectation: "function transfer(address from, address to, uint256 value) returns(bool success)", + }, + } + + abi, err := JSON(strings.NewReader(methoddata)) + if err != nil { + t.Fatal(err) + } + + for _, test := range table { + got := abi.Methods[test.method].String() + if got != test.expectation { + t.Errorf("expected string to be %s, got %s", test.expectation, got) + } + } +} diff --git a/accounts/abi/numbers.go b/accounts/abi/numbers.go new file mode 100644 index 000000000..4d706846d --- /dev/null +++ b/accounts/abi/numbers.go @@ -0,0 +1,44 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" +) + +var ( + bigT = reflect.TypeOf(&big.Int{}) + derefbigT = reflect.TypeOf(big.Int{}) + uint8T = reflect.TypeOf(uint8(0)) + uint16T = reflect.TypeOf(uint16(0)) + uint32T = reflect.TypeOf(uint32(0)) + uint64T = reflect.TypeOf(uint64(0)) + int8T = reflect.TypeOf(int8(0)) + int16T = reflect.TypeOf(int16(0)) + int32T = reflect.TypeOf(int32(0)) + int64T = reflect.TypeOf(int64(0)) + addressT = reflect.TypeOf(common.Address{}) +) + +// U256 converts a big Int into a 256bit EVM number. +func U256(n *big.Int) []byte { + return math.PaddedBigBytes(math.U256(n), 32) +} diff --git a/accounts/abi/numbers_test.go b/accounts/abi/numbers_test.go new file mode 100644 index 000000000..d25a5abcb --- /dev/null +++ b/accounts/abi/numbers_test.go @@ -0,0 +1,33 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "bytes" + "math/big" + "testing" +) + +func TestNumberTypes(t *testing.T) { + ubytes := make([]byte, 32) + ubytes[31] = 1 + + unsigned := U256(big.NewInt(1)) + if !bytes.Equal(unsigned, ubytes) { + t.Errorf("expected %x got %x", ubytes, unsigned) + } +} diff --git a/accounts/abi/pack.go b/accounts/abi/pack.go new file mode 100644 index 000000000..36c58265b --- /dev/null +++ b/accounts/abi/pack.go @@ -0,0 +1,81 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" +) + +// packBytesSlice packs the given bytes as [L, V] as the canonical representation +// bytes slice +func packBytesSlice(bytes []byte, l int) []byte { + len := packNum(reflect.ValueOf(l)) + return append(len, common.RightPadBytes(bytes, (l+31)/32*32)...) +} + +// packElement packs the given reflect value according to the abi specification in +// t. +func packElement(t Type, reflectValue reflect.Value) []byte { + switch t.T { + case IntTy, UintTy: + return packNum(reflectValue) + case StringTy: + return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len()) + case AddressTy: + if reflectValue.Kind() == reflect.Array { + reflectValue = mustArrayToByteSlice(reflectValue) + } + + return common.LeftPadBytes(reflectValue.Bytes(), 32) + case BoolTy: + if reflectValue.Bool() { + return math.PaddedBigBytes(common.Big1, 32) + } + return math.PaddedBigBytes(common.Big0, 32) + case BytesTy: + if reflectValue.Kind() == reflect.Array { + reflectValue = mustArrayToByteSlice(reflectValue) + } + return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()) + case FixedBytesTy, FunctionTy: + if reflectValue.Kind() == reflect.Array { + reflectValue = mustArrayToByteSlice(reflectValue) + } + return common.RightPadBytes(reflectValue.Bytes(), 32) + default: + panic("abi: fatal error") + } +} + +// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation +func packNum(value reflect.Value) []byte { + switch kind := value.Kind(); kind { + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return U256(new(big.Int).SetUint64(value.Uint())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return U256(big.NewInt(value.Int())) + case reflect.Ptr: + return U256(value.Interface().(*big.Int)) + default: + panic("abi: fatal error") + } + +} diff --git a/accounts/abi/pack_test.go b/accounts/abi/pack_test.go new file mode 100644 index 000000000..10cd3a396 --- /dev/null +++ b/accounts/abi/pack_test.go @@ -0,0 +1,783 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "bytes" + "math" + "math/big" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestPack(t *testing.T) { + for i, test := range []struct { + typ string + components []ArgumentMarshaling + input interface{} + output []byte + }{ + { + "uint8", + nil, + uint8(2), + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "uint8[]", + nil, + []uint8{1, 2}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "uint16", + nil, + uint16(2), + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "uint16[]", + nil, + []uint16{1, 2}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "uint32", + nil, + uint32(2), + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "uint32[]", + nil, + []uint32{1, 2}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "uint64", + nil, + uint64(2), + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "uint64[]", + nil, + []uint64{1, 2}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "uint256", + nil, + big.NewInt(2), + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "uint256[]", + nil, + []*big.Int{big.NewInt(1), big.NewInt(2)}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "int8", + nil, + int8(2), + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "int8[]", + nil, + []int8{1, 2}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "int16", + nil, + int16(2), + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "int16[]", + nil, + []int16{1, 2}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "int32", + nil, + int32(2), + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "int32[]", + nil, + []int32{1, 2}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "int64", + nil, + int64(2), + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "int64[]", + nil, + []int64{1, 2}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "int256", + nil, + big.NewInt(2), + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "int256[]", + nil, + []*big.Int{big.NewInt(1), big.NewInt(2)}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), + }, + { + "bytes1", + nil, + [1]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes2", + nil, + [2]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes3", + nil, + [3]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes4", + nil, + [4]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes5", + nil, + [5]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes6", + nil, + [6]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes7", + nil, + [7]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes8", + nil, + [8]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes9", + nil, + [9]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes10", + nil, + [10]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes11", + nil, + [11]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes12", + nil, + [12]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes13", + nil, + [13]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes14", + nil, + [14]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes15", + nil, + [15]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes16", + nil, + [16]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes17", + nil, + [17]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes18", + nil, + [18]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes19", + nil, + [19]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes20", + nil, + [20]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes21", + nil, + [21]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes22", + nil, + [22]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes23", + nil, + [23]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes24", + nil, + [24]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes25", + nil, + [25]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes26", + nil, + [26]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes27", + nil, + [27]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes28", + nil, + [28]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes29", + nil, + [29]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes30", + nil, + [30]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes31", + nil, + [31]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "bytes32", + nil, + [32]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "uint32[2][3][4]", + nil, + [4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000018"), + }, + { + "address[]", + nil, + []common.Address{{1}, {2}}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000"), + }, + { + "bytes32[]", + nil, + []common.Hash{{1}, {2}}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000201000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000"), + }, + { + "function", + nil, + [24]byte{1}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + "string", + nil, + "foobar", + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000"), + }, + { + "string[]", + nil, + []string{"hello", "foobar"}, + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2 + "0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0 + "0000000000000000000000000000000000000000000000000000000000000080" + // offset 128 to i = 1 + "0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5 + "68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0] + "0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6 + "666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1] + }, + { + "string[2]", + nil, + []string{"hello", "foobar"}, + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset to i = 0 + "0000000000000000000000000000000000000000000000000000000000000080" + // offset to i = 1 + "0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5 + "68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0] + "0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6 + "666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1] + }, + { + "bytes32[][]", + nil, + [][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}}, + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2 + "0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0 + "00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1 + "0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2 + "0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0] + "0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1] + "0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3 + "0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0] + "0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1] + "0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2] + }, + + { + "bytes32[][2]", + nil, + [][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}}, + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0 + "00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1 + "0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2 + "0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0] + "0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1] + "0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3 + "0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0] + "0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1] + "0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2] + }, + + { + "bytes32[3][2]", + nil, + [][]common.Hash{{{1}, {2}, {3}}, {{3}, {4}, {5}}}, + common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0] + "0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1] + "0300000000000000000000000000000000000000000000000000000000000000" + // array[0][2] + "0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0] + "0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1] + "0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2] + }, + { + // static tuple + "tuple", + []ArgumentMarshaling{ + {Name: "a", Type: "int64"}, + {Name: "b", Type: "int256"}, + {Name: "c", Type: "int256"}, + {Name: "d", Type: "bool"}, + {Name: "e", Type: "bytes32[3][2]"}, + }, + struct { + A int64 + B *big.Int + C *big.Int + D bool + E [][]common.Hash + }{1, big.NewInt(1), big.NewInt(-1), true, [][]common.Hash{{{1}, {2}, {3}}, {{3}, {4}, {5}}}}, + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001" + // struct[a] + "0000000000000000000000000000000000000000000000000000000000000001" + // struct[b] + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // struct[c] + "0000000000000000000000000000000000000000000000000000000000000001" + // struct[d] + "0100000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][0] + "0200000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][1] + "0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][2] + "0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][0] + "0400000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][1] + "0500000000000000000000000000000000000000000000000000000000000000"), // struct[e] array[1][2] + }, + { + // dynamic tuple + "tuple", + []ArgumentMarshaling{ + {Name: "a", Type: "string"}, + {Name: "b", Type: "int64"}, + {Name: "c", Type: "bytes"}, + {Name: "d", Type: "string[]"}, + {Name: "e", Type: "int256[]"}, + {Name: "f", Type: "address[]"}, + }, + struct { + FieldA string `abi:"a"` // Test whether abi tag works + FieldB int64 `abi:"b"` + C []byte + D []string + E []*big.Int + F []common.Address + }{"foobar", 1, []byte{1}, []string{"foo", "bar"}, []*big.Int{big.NewInt(1), big.NewInt(-1)}, []common.Address{{1}, {2}}}, + common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset + "0000000000000000000000000000000000000000000000000000000000000001" + // struct[b] + "0000000000000000000000000000000000000000000000000000000000000100" + // struct[c] offset + "0000000000000000000000000000000000000000000000000000000000000140" + // struct[d] offset + "0000000000000000000000000000000000000000000000000000000000000220" + // struct[e] offset + "0000000000000000000000000000000000000000000000000000000000000280" + // struct[f] offset + "0000000000000000000000000000000000000000000000000000000000000006" + // struct[a] length + "666f6f6261720000000000000000000000000000000000000000000000000000" + // struct[a] "foobar" + "0000000000000000000000000000000000000000000000000000000000000001" + // struct[c] length + "0100000000000000000000000000000000000000000000000000000000000000" + // []byte{1} + "0000000000000000000000000000000000000000000000000000000000000002" + // struct[d] length + "0000000000000000000000000000000000000000000000000000000000000040" + // foo offset + "0000000000000000000000000000000000000000000000000000000000000080" + // bar offset + "0000000000000000000000000000000000000000000000000000000000000003" + // foo length + "666f6f0000000000000000000000000000000000000000000000000000000000" + // foo + "0000000000000000000000000000000000000000000000000000000000000003" + // bar offset + "6261720000000000000000000000000000000000000000000000000000000000" + // bar + "0000000000000000000000000000000000000000000000000000000000000002" + // struct[e] length + "0000000000000000000000000000000000000000000000000000000000000001" + // 1 + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // -1 + "0000000000000000000000000000000000000000000000000000000000000002" + // struct[f] length + "0000000000000000000000000100000000000000000000000000000000000000" + // common.Address{1} + "0000000000000000000000000200000000000000000000000000000000000000"), // common.Address{2} + }, + { + // nested tuple + "tuple", + []ArgumentMarshaling{ + {Name: "a", Type: "tuple", Components: []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256[]"}}}, + {Name: "b", Type: "int256[]"}, + }, + struct { + A struct { + FieldA *big.Int `abi:"a"` + B []*big.Int + } + B []*big.Int + }{ + A: struct { + FieldA *big.Int `abi:"a"` // Test whether abi tag works for nested tuple + B []*big.Int + }{big.NewInt(1), []*big.Int{big.NewInt(1), big.NewInt(0)}}, + B: []*big.Int{big.NewInt(1), big.NewInt(0)}}, + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // a offset + "00000000000000000000000000000000000000000000000000000000000000e0" + // b offset + "0000000000000000000000000000000000000000000000000000000000000001" + // a.a value + "0000000000000000000000000000000000000000000000000000000000000040" + // a.b offset + "0000000000000000000000000000000000000000000000000000000000000002" + // a.b length + "0000000000000000000000000000000000000000000000000000000000000001" + // a.b[0] value + "0000000000000000000000000000000000000000000000000000000000000000" + // a.b[1] value + "0000000000000000000000000000000000000000000000000000000000000002" + // b length + "0000000000000000000000000000000000000000000000000000000000000001" + // b[0] value + "0000000000000000000000000000000000000000000000000000000000000000"), // b[1] value + }, + { + // tuple slice + "tuple[]", + []ArgumentMarshaling{ + {Name: "a", Type: "int256"}, + {Name: "b", Type: "int256[]"}, + }, + []struct { + A *big.Int + B []*big.Int + }{ + {big.NewInt(-1), []*big.Int{big.NewInt(1), big.NewInt(0)}}, + {big.NewInt(1), []*big.Int{big.NewInt(2), big.NewInt(-1)}}, + }, + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // tuple length + "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset + "00000000000000000000000000000000000000000000000000000000000000e0" + // tuple[1] offset + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A + "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0].B offset + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].B length + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].B[0] value + "0000000000000000000000000000000000000000000000000000000000000000" + // tuple[0].B[1] value + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A + "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[1].B offset + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B length + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B[0] value + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].B[1] value + }, + { + // static tuple array + "tuple[2]", + []ArgumentMarshaling{ + {Name: "a", Type: "int256"}, + {Name: "b", Type: "int256"}, + }, + [2]struct { + A *big.Int + B *big.Int + }{ + {big.NewInt(-1), big.NewInt(1)}, + {big.NewInt(1), big.NewInt(-1)}, + }, + common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].a + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].b + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].a + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].b + }, + { + // dynamic tuple array + "tuple[2]", + []ArgumentMarshaling{ + {Name: "a", Type: "int256[]"}, + }, + [2]struct { + A []*big.Int + }{ + {[]*big.Int{big.NewInt(-1), big.NewInt(1)}}, + {[]*big.Int{big.NewInt(1), big.NewInt(-1)}}, + }, + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset + "00000000000000000000000000000000000000000000000000000000000000c0" + // tuple[1] offset + "0000000000000000000000000000000000000000000000000000000000000020" + // tuple[0].A offset + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].A length + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A[0] + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].A[1] + "0000000000000000000000000000000000000000000000000000000000000020" + // tuple[1].A offset + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].A length + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A[0] + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].A[1] + }, + } { + typ, err := NewType(test.typ, test.components) + if err != nil { + t.Fatalf("%v failed. Unexpected parse error: %v", i, err) + } + output, err := typ.pack(reflect.ValueOf(test.input)) + if err != nil { + t.Fatalf("%v failed. Unexpected pack error: %v", i, err) + } + + if !bytes.Equal(output, test.output) { + t.Errorf("input %d for typ: %v failed. Expected bytes: '%x' Got: '%x'", i, typ.String(), test.output, output) + } + } +} + +func TestMethodPack(t *testing.T) { + abi, err := JSON(strings.NewReader(jsondata2)) + if err != nil { + t.Fatal(err) + } + + sig := abi.Methods["slice"].Id() + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + + packed, err := abi.Pack("slice", []uint32{1, 2}) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + + var addrA, addrB = common.Address{1}, common.Address{2} + sig = abi.Methods["sliceAddress"].Id() + sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes(addrA[:], 32)...) + sig = append(sig, common.LeftPadBytes(addrB[:], 32)...) + + packed, err = abi.Pack("sliceAddress", []common.Address{addrA, addrB}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + + var addrC, addrD = common.Address{3}, common.Address{4} + sig = abi.Methods["sliceMultiAddress"].Id() + sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes(addrA[:], 32)...) + sig = append(sig, common.LeftPadBytes(addrB[:], 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes(addrC[:], 32)...) + sig = append(sig, common.LeftPadBytes(addrD[:], 32)...) + + packed, err = abi.Pack("sliceMultiAddress", []common.Address{addrA, addrB}, []common.Address{addrC, addrD}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + + sig = abi.Methods["slice256"].Id() + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + + packed, err = abi.Pack("slice256", []*big.Int{big.NewInt(1), big.NewInt(2)}) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + + a := [2][2]*big.Int{{big.NewInt(1), big.NewInt(1)}, {big.NewInt(2), big.NewInt(0)}} + sig = abi.Methods["nestedArray"].Id() + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0xa0}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes(addrC[:], 32)...) + sig = append(sig, common.LeftPadBytes(addrD[:], 32)...) + packed, err = abi.Pack("nestedArray", a, []common.Address{addrC, addrD}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + + sig = abi.Methods["nestedArray2"].Id() + sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0x80}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + packed, err = abi.Pack("nestedArray2", [2][]uint8{{1}, {1}}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + + sig = abi.Methods["nestedSlice"].Id() + sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0x02}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0xa0}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + packed, err = abi.Pack("nestedSlice", [][]uint8{{1, 2}, {1, 2}}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } +} + +func TestPackNumber(t *testing.T) { + tests := []struct { + value reflect.Value + packed []byte + }{ + // Protocol limits + {reflect.ValueOf(0), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")}, + {reflect.ValueOf(1), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")}, + {reflect.ValueOf(-1), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, + + // Type corner cases + {reflect.ValueOf(uint8(math.MaxUint8)), common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000ff")}, + {reflect.ValueOf(uint16(math.MaxUint16)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000ffff")}, + {reflect.ValueOf(uint32(math.MaxUint32)), common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000ffffffff")}, + {reflect.ValueOf(uint64(math.MaxUint64)), common.Hex2Bytes("000000000000000000000000000000000000000000000000ffffffffffffffff")}, + + {reflect.ValueOf(int8(math.MaxInt8)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000007f")}, + {reflect.ValueOf(int16(math.MaxInt16)), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000007fff")}, + {reflect.ValueOf(int32(math.MaxInt32)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000007fffffff")}, + {reflect.ValueOf(int64(math.MaxInt64)), common.Hex2Bytes("0000000000000000000000000000000000000000000000007fffffffffffffff")}, + + {reflect.ValueOf(int8(math.MinInt8)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80")}, + {reflect.ValueOf(int16(math.MinInt16)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000")}, + {reflect.ValueOf(int32(math.MinInt32)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000")}, + {reflect.ValueOf(int64(math.MinInt64)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffff8000000000000000")}, + } + for i, tt := range tests { + packed := packNum(tt.value) + if !bytes.Equal(packed, tt.packed) { + t.Errorf("test %d: pack mismatch: have %x, want %x", i, packed, tt.packed) + } + } +} diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go new file mode 100644 index 000000000..ccc6a6593 --- /dev/null +++ b/accounts/abi/reflect.go @@ -0,0 +1,218 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "fmt" + "reflect" + "strings" +) + +// indirect recursively dereferences the value until it either gets the value +// or finds a big.Int +func indirect(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbigT { + return indirect(v.Elem()) + } + return v +} + +// reflectIntKind returns the reflect using the given size and +// unsignedness. +func reflectIntKindAndType(unsigned bool, size int) (reflect.Kind, reflect.Type) { + switch size { + case 8: + if unsigned { + return reflect.Uint8, uint8T + } + return reflect.Int8, int8T + case 16: + if unsigned { + return reflect.Uint16, uint16T + } + return reflect.Int16, int16T + case 32: + if unsigned { + return reflect.Uint32, uint32T + } + return reflect.Int32, int32T + case 64: + if unsigned { + return reflect.Uint64, uint64T + } + return reflect.Int64, int64T + } + return reflect.Ptr, bigT +} + +// mustArrayToBytesSlice creates a new byte slice with the exact same size as value +// and copies the bytes in value to the new slice. +func mustArrayToByteSlice(value reflect.Value) reflect.Value { + slice := reflect.MakeSlice(reflect.TypeOf([]byte{}), value.Len(), value.Len()) + reflect.Copy(slice, value) + return slice +} + +// set attempts to assign src to dst by either setting, copying or otherwise. +// +// set is a bit more lenient when it comes to assignment and doesn't force an as +// strict ruleset as bare `reflect` does. +func set(dst, src reflect.Value) error { + dstType, srcType := dst.Type(), src.Type() + switch { + case dstType.Kind() == reflect.Interface && dst.Elem().IsValid(): + return set(dst.Elem(), src) + case dstType.Kind() == reflect.Ptr && dstType.Elem() != derefbigT: + return set(dst.Elem(), src) + case srcType.AssignableTo(dstType) && dst.CanSet(): + dst.Set(src) + case dstType.Kind() == reflect.Slice && srcType.Kind() == reflect.Slice: + return setSlice(dst, src) + default: + return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type()) + } + return nil +} + +// setSlice attempts to assign src to dst when slices are not assignable by default +// e.g. src: [][]byte -> dst: [][15]byte +func setSlice(dst, src reflect.Value) error { + slice := reflect.MakeSlice(dst.Type(), src.Len(), src.Len()) + for i := 0; i < src.Len(); i++ { + v := src.Index(i) + reflect.Copy(slice.Index(i), v) + } + + dst.Set(slice) + return nil +} + +// requireAssignable assures that `dest` is a pointer and it's not an interface. +func requireAssignable(dst, src reflect.Value) error { + if dst.Kind() != reflect.Ptr && dst.Kind() != reflect.Interface { + return fmt.Errorf("abi: cannot unmarshal %v into %v", src.Type(), dst.Type()) + } + return nil +} + +// requireUnpackKind verifies preconditions for unpacking `args` into `kind` +func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind, + args Arguments) error { + + switch k { + case reflect.Struct: + case reflect.Slice, reflect.Array: + if minLen := args.LengthNonIndexed(); v.Len() < minLen { + return fmt.Errorf("abi: insufficient number of elements in the list/array for unpack, want %d, got %d", + minLen, v.Len()) + } + default: + return fmt.Errorf("abi: cannot unmarshal tuple into %v", t) + } + return nil +} + +// mapArgNamesToStructFields maps a slice of argument names to struct fields. +// first round: for each Exportable field that contains a `abi:""` tag +// and this field name exists in the given argument name list, pair them together. +// second round: for each argument name that has not been already linked, +// find what variable is expected to be mapped into, if it exists and has not been +// used, pair them. +// Note this function assumes the given value is a struct value. +func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[string]string, error) { + typ := value.Type() + + abi2struct := make(map[string]string) + struct2abi := make(map[string]string) + + // first round ~~~ + for i := 0; i < typ.NumField(); i++ { + structFieldName := typ.Field(i).Name + + // skip private struct fields. + if structFieldName[:1] != strings.ToUpper(structFieldName[:1]) { + continue + } + // skip fields that have no abi:"" tag. + var ok bool + var tagName string + if tagName, ok = typ.Field(i).Tag.Lookup("abi"); !ok { + continue + } + // check if tag is empty. + if tagName == "" { + return nil, fmt.Errorf("struct: abi tag in '%s' is empty", structFieldName) + } + // check which argument field matches with the abi tag. + found := false + for _, arg := range argNames { + if arg == tagName { + if abi2struct[arg] != "" { + return nil, fmt.Errorf("struct: abi tag in '%s' already mapped", structFieldName) + } + // pair them + abi2struct[arg] = structFieldName + struct2abi[structFieldName] = arg + found = true + } + } + // check if this tag has been mapped. + if !found { + return nil, fmt.Errorf("struct: abi tag '%s' defined but not found in abi", tagName) + } + } + + // second round ~~~ + for _, argName := range argNames { + + structFieldName := ToCamelCase(argName) + + if structFieldName == "" { + return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct") + } + + // this abi has already been paired, skip it... unless there exists another, yet unassigned + // struct field with the same field name. If so, raise an error: + // abi: [ { "name": "value" } ] + // struct { Value *big.Int , Value1 *big.Int `abi:"value"`} + if abi2struct[argName] != "" { + if abi2struct[argName] != structFieldName && + struct2abi[structFieldName] == "" && + value.FieldByName(structFieldName).IsValid() { + return nil, fmt.Errorf("abi: multiple variables maps to the same abi field '%s'", argName) + } + continue + } + + // return an error if this struct field has already been paired. + if struct2abi[structFieldName] != "" { + return nil, fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", structFieldName) + } + + if value.FieldByName(structFieldName).IsValid() { + // pair them + abi2struct[argName] = structFieldName + struct2abi[structFieldName] = argName + } else { + // not paired, but annotate as used, to detect cases like + // abi : [ { "name": "value" }, { "name": "_value" } ] + // struct { Value *big.Int } + struct2abi[structFieldName] = argName + } + } + return abi2struct, nil +} diff --git a/accounts/abi/reflect_test.go b/accounts/abi/reflect_test.go new file mode 100644 index 000000000..c425e6e54 --- /dev/null +++ b/accounts/abi/reflect_test.go @@ -0,0 +1,191 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "reflect" + "testing" +) + +type reflectTest struct { + name string + args []string + struc interface{} + want map[string]string + err string +} + +var reflectTests = []reflectTest{ + { + name: "OneToOneCorrespondance", + args: []string{"fieldA"}, + struc: struct { + FieldA int `abi:"fieldA"` + }{}, + want: map[string]string{ + "fieldA": "FieldA", + }, + }, + { + name: "MissingFieldsInStruct", + args: []string{"fieldA", "fieldB"}, + struc: struct { + FieldA int `abi:"fieldA"` + }{}, + want: map[string]string{ + "fieldA": "FieldA", + }, + }, + { + name: "MoreFieldsInStructThanArgs", + args: []string{"fieldA"}, + struc: struct { + FieldA int `abi:"fieldA"` + FieldB int + }{}, + want: map[string]string{ + "fieldA": "FieldA", + }, + }, + { + name: "MissingFieldInArgs", + args: []string{"fieldA"}, + struc: struct { + FieldA int `abi:"fieldA"` + FieldB int `abi:"fieldB"` + }{}, + err: "struct: abi tag 'fieldB' defined but not found in abi", + }, + { + name: "NoAbiDescriptor", + args: []string{"fieldA"}, + struc: struct { + FieldA int + }{}, + want: map[string]string{ + "fieldA": "FieldA", + }, + }, + { + name: "NoArgs", + args: []string{}, + struc: struct { + FieldA int `abi:"fieldA"` + }{}, + err: "struct: abi tag 'fieldA' defined but not found in abi", + }, + { + name: "DifferentName", + args: []string{"fieldB"}, + struc: struct { + FieldA int `abi:"fieldB"` + }{}, + want: map[string]string{ + "fieldB": "FieldA", + }, + }, + { + name: "DifferentName", + args: []string{"fieldB"}, + struc: struct { + FieldA int `abi:"fieldB"` + }{}, + want: map[string]string{ + "fieldB": "FieldA", + }, + }, + { + name: "MultipleFields", + args: []string{"fieldA", "fieldB"}, + struc: struct { + FieldA int `abi:"fieldA"` + FieldB int `abi:"fieldB"` + }{}, + want: map[string]string{ + "fieldA": "FieldA", + "fieldB": "FieldB", + }, + }, + { + name: "MultipleFieldsABIMissing", + args: []string{"fieldA", "fieldB"}, + struc: struct { + FieldA int `abi:"fieldA"` + FieldB int + }{}, + want: map[string]string{ + "fieldA": "FieldA", + "fieldB": "FieldB", + }, + }, + { + name: "NameConflict", + args: []string{"fieldB"}, + struc: struct { + FieldA int `abi:"fieldB"` + FieldB int + }{}, + err: "abi: multiple variables maps to the same abi field 'fieldB'", + }, + { + name: "Underscored", + args: []string{"_"}, + struc: struct { + FieldA int + }{}, + err: "abi: purely underscored output cannot unpack to struct", + }, + { + name: "DoubleMapping", + args: []string{"fieldB", "fieldC", "fieldA"}, + struc: struct { + FieldA int `abi:"fieldC"` + FieldB int + }{}, + err: "abi: multiple outputs mapping to the same struct field 'FieldA'", + }, + { + name: "AlreadyMapped", + args: []string{"fieldB", "fieldB"}, + struc: struct { + FieldB int `abi:"fieldB"` + }{}, + err: "struct: abi tag in 'FieldB' already mapped", + }, +} + +func TestReflectNameToStruct(t *testing.T) { + for _, test := range reflectTests { + t.Run(test.name, func(t *testing.T) { + m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc)) + if len(test.err) > 0 { + if err == nil || err.Error() != test.err { + t.Fatalf("Invalid error: expected %v, got %v", test.err, err) + } + } else { + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + for fname := range test.want { + if m[fname] != test.want[fname] { + t.Fatalf("Incorrect value for field %s: expected %v, got %v", fname, test.want[fname], m[fname]) + } + } + } + }) + } +} diff --git a/accounts/abi/type.go b/accounts/abi/type.go new file mode 100644 index 000000000..1a3718235 --- /dev/null +++ b/accounts/abi/type.go @@ -0,0 +1,353 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "errors" + "fmt" + "reflect" + "regexp" + "strconv" + "strings" +) + +// Type enumerator +const ( + IntTy byte = iota + UintTy + BoolTy + StringTy + SliceTy + ArrayTy + TupleTy + AddressTy + FixedBytesTy + BytesTy + HashTy + FixedPointTy + FunctionTy +) + +// Type is the reflection of the supported argument type +type Type struct { + Elem *Type + Kind reflect.Kind + Type reflect.Type + Size int + T byte // Our own type checking + + stringKind string // holds the unparsed string for deriving signatures + + // Tuple relative fields + TupleElems []*Type // Type information of all tuple fields + TupleRawNames []string // Raw field name of all tuple fields +} + +var ( + // typeRegex parses the abi sub types + typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?") +) + +// NewType creates a new reflection type of abi type given in t. +func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) { + // check that array brackets are equal if they exist + if strings.Count(t, "[") != strings.Count(t, "]") { + return Type{}, fmt.Errorf("invalid arg type in abi") + } + + typ.stringKind = t + + // if there are brackets, get ready to go into slice/array mode and + // recursively create the type + if strings.Count(t, "[") != 0 { + i := strings.LastIndex(t, "[") + // recursively embed the type + embeddedType, err := NewType(t[:i], components) + if err != nil { + return Type{}, err + } + // grab the last cell and create a type from there + sliced := t[i:] + // grab the slice size with regexp + re := regexp.MustCompile("[0-9]+") + intz := re.FindAllString(sliced, -1) + + if len(intz) == 0 { + // is a slice + typ.T = SliceTy + typ.Kind = reflect.Slice + typ.Elem = &embeddedType + typ.Type = reflect.SliceOf(embeddedType.Type) + if embeddedType.T == TupleTy { + typ.stringKind = embeddedType.stringKind + sliced + } + } else if len(intz) == 1 { + // is a array + typ.T = ArrayTy + typ.Kind = reflect.Array + typ.Elem = &embeddedType + typ.Size, err = strconv.Atoi(intz[0]) + if err != nil { + return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) + } + typ.Type = reflect.ArrayOf(typ.Size, embeddedType.Type) + if embeddedType.T == TupleTy { + typ.stringKind = embeddedType.stringKind + sliced + } + } else { + return Type{}, fmt.Errorf("invalid formatting of array type") + } + return typ, err + } + // parse the type and size of the abi-type. + matches := typeRegex.FindAllStringSubmatch(t, -1) + if len(matches) == 0 { + return Type{}, fmt.Errorf("invalid type '%v'", t) + } + parsedType := matches[0] + + // varSize is the size of the variable + var varSize int + if len(parsedType[3]) > 0 { + var err error + varSize, err = strconv.Atoi(parsedType[2]) + if err != nil { + return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) + } + } else { + if parsedType[0] == "uint" || parsedType[0] == "int" { + // this should fail because it means that there's something wrong with + // the abi type (the compiler should always format it to the size...always) + return Type{}, fmt.Errorf("unsupported arg type: %s", t) + } + } + // varType is the parsed abi type + switch varType := parsedType[1]; varType { + case "int": + typ.Kind, typ.Type = reflectIntKindAndType(false, varSize) + typ.Size = varSize + typ.T = IntTy + case "uint": + typ.Kind, typ.Type = reflectIntKindAndType(true, varSize) + typ.Size = varSize + typ.T = UintTy + case "bool": + typ.Kind = reflect.Bool + typ.T = BoolTy + typ.Type = reflect.TypeOf(bool(false)) + case "address": + typ.Kind = reflect.Array + typ.Type = addressT + typ.Size = 20 + typ.T = AddressTy + case "string": + typ.Kind = reflect.String + typ.Type = reflect.TypeOf("") + typ.T = StringTy + case "bytes": + if varSize == 0 { + typ.T = BytesTy + typ.Kind = reflect.Slice + typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0))) + } else { + typ.T = FixedBytesTy + typ.Kind = reflect.Array + typ.Size = varSize + typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0))) + } + case "tuple": + var ( + fields []reflect.StructField + elems []*Type + names []string + expression string // canonical parameter expression + ) + expression += "(" + for idx, c := range components { + cType, err := NewType(c.Type, c.Components) + if err != nil { + return Type{}, err + } + if ToCamelCase(c.Name) == "" { + return Type{}, errors.New("abi: purely anonymous or underscored field is not supported") + } + fields = append(fields, reflect.StructField{ + Name: ToCamelCase(c.Name), // reflect.StructOf will panic for any exported field. + Type: cType.Type, + Tag: reflect.StructTag("json:\"" + c.Name + "\""), + }) + elems = append(elems, &cType) + names = append(names, c.Name) + expression += cType.stringKind + if idx != len(components)-1 { + expression += "," + } + } + expression += ")" + typ.Kind = reflect.Struct + typ.Type = reflect.StructOf(fields) + typ.TupleElems = elems + typ.TupleRawNames = names + typ.T = TupleTy + typ.stringKind = expression + case "function": + typ.Kind = reflect.Array + typ.T = FunctionTy + typ.Size = 24 + typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0))) + default: + return Type{}, fmt.Errorf("unsupported arg type: %s", t) + } + + return +} + +// String implements Stringer +func (t Type) String() (out string) { + return t.stringKind +} + +func (t Type) pack(v reflect.Value) ([]byte, error) { + // dereference pointer first if it's a pointer + v = indirect(v) + if err := typeCheck(t, v); err != nil { + return nil, err + } + + switch t.T { + case SliceTy, ArrayTy: + var ret []byte + + if t.requiresLengthPrefix() { + // append length + ret = append(ret, packNum(reflect.ValueOf(v.Len()))...) + } + + // calculate offset if any + offset := 0 + offsetReq := isDynamicType(*t.Elem) + if offsetReq { + offset = getTypeSize(*t.Elem) * v.Len() + } + var tail []byte + for i := 0; i < v.Len(); i++ { + val, err := t.Elem.pack(v.Index(i)) + if err != nil { + return nil, err + } + if !offsetReq { + ret = append(ret, val...) + continue + } + ret = append(ret, packNum(reflect.ValueOf(offset))...) + offset += len(val) + tail = append(tail, val...) + } + return append(ret, tail...), nil + case TupleTy: + // (T1,...,Tk) for k >= 0 and any types T1, …, Tk + // enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k)) + // where X = (X(1), ..., X(k)) and head and tail are defined for Ti being a static + // type as + // head(X(i)) = enc(X(i)) and tail(X(i)) = "" (the empty string) + // and as + // head(X(i)) = enc(len(head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1)))) + // tail(X(i)) = enc(X(i)) + // otherwise, i.e. if Ti is a dynamic type. + fieldmap, err := mapArgNamesToStructFields(t.TupleRawNames, v) + if err != nil { + return nil, err + } + // Calculate prefix occupied size. + offset := 0 + for _, elem := range t.TupleElems { + offset += getTypeSize(*elem) + } + var ret, tail []byte + for i, elem := range t.TupleElems { + field := v.FieldByName(fieldmap[t.TupleRawNames[i]]) + if !field.IsValid() { + return nil, fmt.Errorf("field %s for tuple not found in the given struct", t.TupleRawNames[i]) + } + val, err := elem.pack(field) + if err != nil { + return nil, err + } + if isDynamicType(*elem) { + ret = append(ret, packNum(reflect.ValueOf(offset))...) + tail = append(tail, val...) + offset += len(val) + } else { + ret = append(ret, val...) + } + } + return append(ret, tail...), nil + + default: + return packElement(t, v), nil + } +} + +// requireLengthPrefix returns whether the type requires any sort of length +// prefixing. +func (t Type) requiresLengthPrefix() bool { + return t.T == StringTy || t.T == BytesTy || t.T == SliceTy +} + +// isDynamicType returns true if the type is dynamic. +// The following types are called “dynamic”: +// * bytes +// * string +// * T[] for any T +// * T[k] for any dynamic T and any k >= 0 +// * (T1,...,Tk) if Ti is dynamic for some 1 <= i <= k +func isDynamicType(t Type) bool { + if t.T == TupleTy { + for _, elem := range t.TupleElems { + if isDynamicType(*elem) { + return true + } + } + return false + } + return t.T == StringTy || t.T == BytesTy || t.T == SliceTy || (t.T == ArrayTy && isDynamicType(*t.Elem)) +} + +// getTypeSize returns the size that this type needs to occupy. +// We distinguish static and dynamic types. Static types are encoded in-place +// and dynamic types are encoded at a separately allocated location after the +// current block. +// So for a static variable, the size returned represents the size that the +// variable actually occupies. +// For a dynamic variable, the returned size is fixed 32 bytes, which is used +// to store the location reference for actual value storage. +func getTypeSize(t Type) int { + if t.T == ArrayTy && !isDynamicType(*t.Elem) { + // Recursively calculate type size if it is a nested array + if t.Elem.T == ArrayTy { + return t.Size * getTypeSize(*t.Elem) + } + return t.Size * 32 + } else if t.T == TupleTy && !isDynamicType(t) { + total := 0 + for _, elem := range t.TupleElems { + total += getTypeSize(*elem) + } + return total + } + return 32 +} diff --git a/accounts/abi/type_test.go b/accounts/abi/type_test.go new file mode 100644 index 000000000..5023456ae --- /dev/null +++ b/accounts/abi/type_test.go @@ -0,0 +1,308 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "math/big" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common" +) + +// typeWithoutStringer is a alias for the Type type which simply doesn't implement +// the stringer interface to allow printing type details in the tests below. +type typeWithoutStringer Type + +// Tests that all allowed types get recognized by the type parser. +func TestTypeRegexp(t *testing.T) { + tests := []struct { + blob string + components []ArgumentMarshaling + kind Type + }{ + {"bool", nil, Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}}, + {"bool[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool(nil)), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}}, + {"bool[2]", nil, Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}}, + {"bool[2][]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}}, + {"bool[][]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}}, + {"bool[][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}}, + {"bool[2][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}}, + {"bool[2][][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][][2]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}, stringKind: "bool[2][][2]"}}, + {"bool[2][2][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}}, + {"bool[][][]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}}, + {"bool[][2][]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][2][]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}}, + {"int8", nil, Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}}, + {"int16", nil, Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}}, + {"int32", nil, Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}}, + {"int64", nil, Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}}, + {"int256", nil, Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}}, + {"int8[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}}, + {"int8[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}}, + {"int16[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}}, + {"int16[2]", nil, Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}}, + {"int32[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}}, + {"int32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}}, + {"int64[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}}, + {"int64[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}}, + {"int256[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}}, + {"int256[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}}, + {"uint8", nil, Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}}, + {"uint16", nil, Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}}, + {"uint32", nil, Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}}, + {"uint64", nil, Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}}, + {"uint256", nil, Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}}, + {"uint8[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}}, + {"uint8[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}}, + {"uint16[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}}, + {"uint16[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}}, + {"uint32[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}}, + {"uint32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}}, + {"uint64[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}}, + {"uint64[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}}, + {"uint256[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}}, + {"uint256[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]*big.Int{}), Size: 2, Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}}, + {"bytes32", nil, Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}}, + {"bytes[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]byte{}), Elem: &Type{Kind: reflect.Slice, Type: reflect.TypeOf([]byte{}), T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}}, + {"bytes[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]byte{}), Elem: &Type{T: BytesTy, Type: reflect.TypeOf([]byte{}), Kind: reflect.Slice, stringKind: "bytes"}, stringKind: "bytes[2]"}}, + {"bytes32[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][32]byte{}), Elem: &Type{Kind: reflect.Array, Type: reflect.TypeOf([32]byte{}), T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[]"}}, + {"bytes32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][32]byte{}), Elem: &Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}, stringKind: "bytes32[2]"}}, + {"string", nil, Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}}, + {"string[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]string{}), Elem: &Type{Kind: reflect.String, Type: reflect.TypeOf(""), T: StringTy, stringKind: "string"}, stringKind: "string[]"}}, + {"string[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]string{}), Elem: &Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}, stringKind: "string[2]"}}, + {"address", nil, Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}}, + {"address[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}}, + {"address[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}}, + // TODO when fixed types are implemented properly + // {"fixed", nil, Type{}}, + // {"fixed128x128", nil, Type{}}, + // {"fixed[]", nil, Type{}}, + // {"fixed[2]", nil, Type{}}, + // {"fixed128x128[]", nil, Type{}}, + // {"fixed128x128[2]", nil, Type{}}, + {"tuple", []ArgumentMarshaling{{Name: "a", Type: "int64"}}, Type{Kind: reflect.Struct, T: TupleTy, Type: reflect.TypeOf(struct { + A int64 `json:"a"` + }{}), stringKind: "(int64)", + TupleElems: []*Type{{Kind: reflect.Int64, T: IntTy, Type: reflect.TypeOf(int64(0)), Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"a"}}}, + {"tuple with long name", []ArgumentMarshaling{{Name: "aTypicalParamName", Type: "int64"}}, Type{Kind: reflect.Struct, T: TupleTy, Type: reflect.TypeOf(struct { + ATypicalParamName int64 `json:"aTypicalParamName"` + }{}), stringKind: "(int64)", + TupleElems: []*Type{{Kind: reflect.Int64, T: IntTy, Type: reflect.TypeOf(int64(0)), Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"aTypicalParamName"}}}, + } + + for _, tt := range tests { + typ, err := NewType(tt.blob, tt.components) + if err != nil { + t.Errorf("type %q: failed to parse type string: %v", tt.blob, err) + } + if !reflect.DeepEqual(typ, tt.kind) { + t.Errorf("type %q: parsed type mismatch:\nGOT %s\nWANT %s ", tt.blob, spew.Sdump(typeWithoutStringer(typ)), spew.Sdump(typeWithoutStringer(tt.kind))) + } + } +} + +func TestTypeCheck(t *testing.T) { + for i, test := range []struct { + typ string + components []ArgumentMarshaling + input interface{} + err string + }{ + {"uint", nil, big.NewInt(1), "unsupported arg type: uint"}, + {"int", nil, big.NewInt(1), "unsupported arg type: int"}, + {"uint256", nil, big.NewInt(1), ""}, + {"uint256[][3][]", nil, [][3][]*big.Int{{{}}}, ""}, + {"uint256[][][3]", nil, [3][][]*big.Int{{{}}}, ""}, + {"uint256[3][][]", nil, [][][3]*big.Int{{{}}}, ""}, + {"uint256[3][3][3]", nil, [3][3][3]*big.Int{{{}}}, ""}, + {"uint8[][]", nil, [][]uint8{}, ""}, + {"int256", nil, big.NewInt(1), ""}, + {"uint8", nil, uint8(1), ""}, + {"uint16", nil, uint16(1), ""}, + {"uint32", nil, uint32(1), ""}, + {"uint64", nil, uint64(1), ""}, + {"int8", nil, int8(1), ""}, + {"int16", nil, int16(1), ""}, + {"int32", nil, int32(1), ""}, + {"int64", nil, int64(1), ""}, + {"uint24", nil, big.NewInt(1), ""}, + {"uint40", nil, big.NewInt(1), ""}, + {"uint48", nil, big.NewInt(1), ""}, + {"uint56", nil, big.NewInt(1), ""}, + {"uint72", nil, big.NewInt(1), ""}, + {"uint80", nil, big.NewInt(1), ""}, + {"uint88", nil, big.NewInt(1), ""}, + {"uint96", nil, big.NewInt(1), ""}, + {"uint104", nil, big.NewInt(1), ""}, + {"uint112", nil, big.NewInt(1), ""}, + {"uint120", nil, big.NewInt(1), ""}, + {"uint128", nil, big.NewInt(1), ""}, + {"uint136", nil, big.NewInt(1), ""}, + {"uint144", nil, big.NewInt(1), ""}, + {"uint152", nil, big.NewInt(1), ""}, + {"uint160", nil, big.NewInt(1), ""}, + {"uint168", nil, big.NewInt(1), ""}, + {"uint176", nil, big.NewInt(1), ""}, + {"uint184", nil, big.NewInt(1), ""}, + {"uint192", nil, big.NewInt(1), ""}, + {"uint200", nil, big.NewInt(1), ""}, + {"uint208", nil, big.NewInt(1), ""}, + {"uint216", nil, big.NewInt(1), ""}, + {"uint224", nil, big.NewInt(1), ""}, + {"uint232", nil, big.NewInt(1), ""}, + {"uint240", nil, big.NewInt(1), ""}, + {"uint248", nil, big.NewInt(1), ""}, + {"int24", nil, big.NewInt(1), ""}, + {"int40", nil, big.NewInt(1), ""}, + {"int48", nil, big.NewInt(1), ""}, + {"int56", nil, big.NewInt(1), ""}, + {"int72", nil, big.NewInt(1), ""}, + {"int80", nil, big.NewInt(1), ""}, + {"int88", nil, big.NewInt(1), ""}, + {"int96", nil, big.NewInt(1), ""}, + {"int104", nil, big.NewInt(1), ""}, + {"int112", nil, big.NewInt(1), ""}, + {"int120", nil, big.NewInt(1), ""}, + {"int128", nil, big.NewInt(1), ""}, + {"int136", nil, big.NewInt(1), ""}, + {"int144", nil, big.NewInt(1), ""}, + {"int152", nil, big.NewInt(1), ""}, + {"int160", nil, big.NewInt(1), ""}, + {"int168", nil, big.NewInt(1), ""}, + {"int176", nil, big.NewInt(1), ""}, + {"int184", nil, big.NewInt(1), ""}, + {"int192", nil, big.NewInt(1), ""}, + {"int200", nil, big.NewInt(1), ""}, + {"int208", nil, big.NewInt(1), ""}, + {"int216", nil, big.NewInt(1), ""}, + {"int224", nil, big.NewInt(1), ""}, + {"int232", nil, big.NewInt(1), ""}, + {"int240", nil, big.NewInt(1), ""}, + {"int248", nil, big.NewInt(1), ""}, + {"uint30", nil, uint8(1), "abi: cannot use uint8 as type ptr as argument"}, + {"uint8", nil, uint16(1), "abi: cannot use uint16 as type uint8 as argument"}, + {"uint8", nil, uint32(1), "abi: cannot use uint32 as type uint8 as argument"}, + {"uint8", nil, uint64(1), "abi: cannot use uint64 as type uint8 as argument"}, + {"uint8", nil, int8(1), "abi: cannot use int8 as type uint8 as argument"}, + {"uint8", nil, int16(1), "abi: cannot use int16 as type uint8 as argument"}, + {"uint8", nil, int32(1), "abi: cannot use int32 as type uint8 as argument"}, + {"uint8", nil, int64(1), "abi: cannot use int64 as type uint8 as argument"}, + {"uint16", nil, uint16(1), ""}, + {"uint16", nil, uint8(1), "abi: cannot use uint8 as type uint16 as argument"}, + {"uint16[]", nil, []uint16{1, 2, 3}, ""}, + {"uint16[]", nil, [3]uint16{1, 2, 3}, ""}, + {"uint16[]", nil, []uint32{1, 2, 3}, "abi: cannot use []uint32 as type [0]uint16 as argument"}, + {"uint16[3]", nil, [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"}, + {"uint16[3]", nil, [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"}, + {"uint16[3]", nil, []uint16{1, 2, 3}, ""}, + {"uint16[3]", nil, []uint16{1, 2, 3, 4}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"}, + {"address[]", nil, []common.Address{{1}}, ""}, + {"address[1]", nil, []common.Address{{1}}, ""}, + {"address[1]", nil, [1]common.Address{{1}}, ""}, + {"address[2]", nil, [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"}, + {"bytes32", nil, [32]byte{}, ""}, + {"bytes31", nil, [31]byte{}, ""}, + {"bytes30", nil, [30]byte{}, ""}, + {"bytes29", nil, [29]byte{}, ""}, + {"bytes28", nil, [28]byte{}, ""}, + {"bytes27", nil, [27]byte{}, ""}, + {"bytes26", nil, [26]byte{}, ""}, + {"bytes25", nil, [25]byte{}, ""}, + {"bytes24", nil, [24]byte{}, ""}, + {"bytes23", nil, [23]byte{}, ""}, + {"bytes22", nil, [22]byte{}, ""}, + {"bytes21", nil, [21]byte{}, ""}, + {"bytes20", nil, [20]byte{}, ""}, + {"bytes19", nil, [19]byte{}, ""}, + {"bytes18", nil, [18]byte{}, ""}, + {"bytes17", nil, [17]byte{}, ""}, + {"bytes16", nil, [16]byte{}, ""}, + {"bytes15", nil, [15]byte{}, ""}, + {"bytes14", nil, [14]byte{}, ""}, + {"bytes13", nil, [13]byte{}, ""}, + {"bytes12", nil, [12]byte{}, ""}, + {"bytes11", nil, [11]byte{}, ""}, + {"bytes10", nil, [10]byte{}, ""}, + {"bytes9", nil, [9]byte{}, ""}, + {"bytes8", nil, [8]byte{}, ""}, + {"bytes7", nil, [7]byte{}, ""}, + {"bytes6", nil, [6]byte{}, ""}, + {"bytes5", nil, [5]byte{}, ""}, + {"bytes4", nil, [4]byte{}, ""}, + {"bytes3", nil, [3]byte{}, ""}, + {"bytes2", nil, [2]byte{}, ""}, + {"bytes1", nil, [1]byte{}, ""}, + {"bytes32", nil, [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"}, + {"bytes32", nil, common.Hash{1}, ""}, + {"bytes31", nil, common.Hash{1}, "abi: cannot use common.Hash as type [31]uint8 as argument"}, + {"bytes31", nil, [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"}, + {"bytes", nil, []byte{0, 1}, ""}, + {"bytes", nil, [2]byte{0, 1}, "abi: cannot use array as type slice as argument"}, + {"bytes", nil, common.Hash{1}, "abi: cannot use array as type slice as argument"}, + {"string", nil, "hello world", ""}, + {"string", nil, string(""), ""}, + {"string", nil, []byte{}, "abi: cannot use slice as type string as argument"}, + {"bytes32[]", nil, [][32]byte{{}}, ""}, + {"function", nil, [24]byte{}, ""}, + {"bytes20", nil, common.Address{}, ""}, + {"address", nil, [20]byte{}, ""}, + {"address", nil, common.Address{}, ""}, + {"bytes32[]]", nil, "", "invalid arg type in abi"}, + {"invalidType", nil, "", "unsupported arg type: invalidType"}, + {"invalidSlice[]", nil, "", "unsupported arg type: invalidSlice"}, + // simple tuple + {"tuple", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, struct { + A *big.Int + B *big.Int + }{}, ""}, + // tuple slice + {"tuple[]", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, []struct { + A *big.Int + B *big.Int + }{}, ""}, + // tuple array + {"tuple[2]", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, []struct { + A *big.Int + B *big.Int + }{{big.NewInt(0), big.NewInt(0)}, {big.NewInt(0), big.NewInt(0)}}, ""}, + } { + typ, err := NewType(test.typ, test.components) + if err != nil && len(test.err) == 0 { + t.Fatal("unexpected parse error:", err) + } else if err != nil && len(test.err) != 0 { + if err.Error() != test.err { + t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err) + } + continue + } + + err = typeCheck(typ, reflect.ValueOf(test.input)) + if err != nil && len(test.err) == 0 { + t.Errorf("%d failed. Expected no err but got: %v", i, err) + continue + } + if err == nil && len(test.err) != 0 { + t.Errorf("%d failed. Expected err: %v but got none", i, test.err) + continue + } + + if err != nil && len(test.err) != 0 && err.Error() != test.err { + t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err) + } + } +} diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go new file mode 100644 index 000000000..b2e61d06c --- /dev/null +++ b/accounts/abi/unpack.go @@ -0,0 +1,295 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "encoding/binary" + "fmt" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/common" +) + +var ( + maxUint256 = big.NewInt(0).Add( + big.NewInt(0).Exp(big.NewInt(2), big.NewInt(256), nil), + big.NewInt(-1)) + maxInt256 = big.NewInt(0).Add( + big.NewInt(0).Exp(big.NewInt(2), big.NewInt(255), nil), + big.NewInt(-1)) +) + +// reads the integer based on its kind +func readInteger(typ byte, kind reflect.Kind, b []byte) interface{} { + switch kind { + case reflect.Uint8: + return b[len(b)-1] + case reflect.Uint16: + return binary.BigEndian.Uint16(b[len(b)-2:]) + case reflect.Uint32: + return binary.BigEndian.Uint32(b[len(b)-4:]) + case reflect.Uint64: + return binary.BigEndian.Uint64(b[len(b)-8:]) + case reflect.Int8: + return int8(b[len(b)-1]) + case reflect.Int16: + return int16(binary.BigEndian.Uint16(b[len(b)-2:])) + case reflect.Int32: + return int32(binary.BigEndian.Uint32(b[len(b)-4:])) + case reflect.Int64: + return int64(binary.BigEndian.Uint64(b[len(b)-8:])) + default: + // the only case lefts for integer is int256/uint256. + // big.SetBytes can't tell if a number is negative, positive on itself. + // On EVM, if the returned number > max int256, it is negative. + ret := new(big.Int).SetBytes(b) + if typ == UintTy { + return ret + } + + if ret.Cmp(maxInt256) > 0 { + ret.Add(maxUint256, big.NewInt(0).Neg(ret)) + ret.Add(ret, big.NewInt(1)) + ret.Neg(ret) + } + return ret + } +} + +// reads a bool +func readBool(word []byte) (bool, error) { + for _, b := range word[:31] { + if b != 0 { + return false, errBadBool + } + } + switch word[31] { + case 0: + return false, nil + case 1: + return true, nil + default: + return false, errBadBool + } +} + +// A function type is simply the address with the function selection signature at the end. +// This enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes) +func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { + if t.T != FunctionTy { + return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array") + } + if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 { + err = fmt.Errorf("abi: got improperly encoded function type, got %v", word) + } else { + copy(funcTy[:], word[0:24]) + } + return +} + +// through reflection, creates a fixed array to be read from +func readFixedBytes(t Type, word []byte) (interface{}, error) { + if t.T != FixedBytesTy { + return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array") + } + // convert + array := reflect.New(t.Type).Elem() + + reflect.Copy(array, reflect.ValueOf(word[0:t.Size])) + return array.Interface(), nil + +} + +// iteratively unpack elements +func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) { + if size < 0 { + return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size) + } + if start+32*size > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size) + } + + // this value will become our slice or our array, depending on the type + var refSlice reflect.Value + + if t.T == SliceTy { + // declare our slice + refSlice = reflect.MakeSlice(t.Type, size, size) + } else if t.T == ArrayTy { + // declare our array + refSlice = reflect.New(t.Type).Elem() + } else { + return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage") + } + + // Arrays have packed elements, resulting in longer unpack steps. + // Slices have just 32 bytes per element (pointing to the contents). + elemSize := getTypeSize(*t.Elem) + + for i, j := start, 0; j < size; i, j = i+elemSize, j+1 { + inter, err := toGoType(i, *t.Elem, output) + if err != nil { + return nil, err + } + + // append the item to our reflect slice + refSlice.Index(j).Set(reflect.ValueOf(inter)) + } + + // return the interface + return refSlice.Interface(), nil +} + +func forTupleUnpack(t Type, output []byte) (interface{}, error) { + retval := reflect.New(t.Type).Elem() + virtualArgs := 0 + for index, elem := range t.TupleElems { + marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output) + if elem.T == ArrayTy && !isDynamicType(*elem) { + // If we have a static array, like [3]uint256, these are coded as + // just like uint256,uint256,uint256. + // This means that we need to add two 'virtual' arguments when + // we count the index from now on. + // + // Array values nested multiple levels deep are also encoded inline: + // [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256 + // + // Calculate the full array size to get the correct offset for the next argument. + // Decrement it by 1, as the normal index increment is still applied. + virtualArgs += getTypeSize(*elem)/32 - 1 + } else if elem.T == TupleTy && !isDynamicType(*elem) { + // If we have a static tuple, like (uint256, bool, uint256), these are + // coded as just like uint256,bool,uint256 + virtualArgs += getTypeSize(*elem)/32 - 1 + } + if err != nil { + return nil, err + } + retval.Field(index).Set(reflect.ValueOf(marshalledValue)) + } + return retval.Interface(), nil +} + +// toGoType parses the output bytes and recursively assigns the value of these bytes +// into a go type with accordance with the ABI spec. +func toGoType(index int, t Type, output []byte) (interface{}, error) { + if index+32 > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32) + } + + var ( + returnOutput []byte + begin, length int + err error + ) + + // if we require a length prefix, find the beginning word and size returned. + if t.requiresLengthPrefix() { + begin, length, err = lengthPrefixPointsTo(index, output) + if err != nil { + return nil, err + } + } else { + returnOutput = output[index : index+32] + } + + switch t.T { + case TupleTy: + if isDynamicType(t) { + begin, err := tuplePointsTo(index, output) + if err != nil { + return nil, err + } + return forTupleUnpack(t, output[begin:]) + } else { + return forTupleUnpack(t, output[index:]) + } + case SliceTy: + return forEachUnpack(t, output[begin:], 0, length) + case ArrayTy: + if isDynamicType(*t.Elem) { + offset := int64(binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:])) + return forEachUnpack(t, output[offset:], 0, t.Size) + } + return forEachUnpack(t, output[index:], 0, t.Size) + case StringTy: // variable arrays are written at the end of the return bytes + return string(output[begin : begin+length]), nil + case IntTy, UintTy: + return readInteger(t.T, t.Kind, returnOutput), nil + case BoolTy: + return readBool(returnOutput) + case AddressTy: + return common.BytesToAddress(returnOutput), nil + case HashTy: + return common.BytesToHash(returnOutput), nil + case BytesTy: + return output[begin : begin+length], nil + case FixedBytesTy: + return readFixedBytes(t, returnOutput) + case FunctionTy: + return readFunctionType(t, returnOutput) + default: + return nil, fmt.Errorf("abi: unknown type %v", t.T) + } +} + +// interprets a 32 byte slice as an offset and then determines which indice to look to decode the type. +func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) { + bigOffsetEnd := big.NewInt(0).SetBytes(output[index : index+32]) + bigOffsetEnd.Add(bigOffsetEnd, common.Big32) + outputLength := big.NewInt(int64(len(output))) + + if bigOffsetEnd.Cmp(outputLength) > 0 { + return 0, 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", bigOffsetEnd, outputLength) + } + + if bigOffsetEnd.BitLen() > 63 { + return 0, 0, fmt.Errorf("abi offset larger than int64: %v", bigOffsetEnd) + } + + offsetEnd := int(bigOffsetEnd.Uint64()) + lengthBig := big.NewInt(0).SetBytes(output[offsetEnd-32 : offsetEnd]) + + totalSize := big.NewInt(0) + totalSize.Add(totalSize, bigOffsetEnd) + totalSize.Add(totalSize, lengthBig) + if totalSize.BitLen() > 63 { + return 0, 0, fmt.Errorf("abi: length larger than int64: %v", totalSize) + } + + if totalSize.Cmp(outputLength) > 0 { + return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %v require %v", outputLength, totalSize) + } + start = int(bigOffsetEnd.Uint64()) + length = int(lengthBig.Uint64()) + return +} + +// tuplePointsTo resolves the location reference for dynamic tuple. +func tuplePointsTo(index int, output []byte) (start int, err error) { + offset := big.NewInt(0).SetBytes(output[index : index+32]) + outputLen := big.NewInt(int64(len(output))) + + if offset.Cmp(big.NewInt(int64(len(output)))) > 0 { + return 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", offset, outputLen) + } + if offset.BitLen() > 63 { + return 0, fmt.Errorf("abi offset larger than int64: %v", offset) + } + return int(offset.Uint64()), nil +} diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go new file mode 100644 index 000000000..fa8a69d05 --- /dev/null +++ b/accounts/abi/unpack_test.go @@ -0,0 +1,1127 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "bytes" + "encoding/hex" + "fmt" + "math/big" + "reflect" + "strconv" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +type unpackTest struct { + def string // ABI definition JSON + enc string // evm return data + want interface{} // the expected output + err string // empty or error if expected +} + +func (test unpackTest) checkError(err error) error { + if err != nil { + if len(test.err) == 0 { + return fmt.Errorf("expected no err but got: %v", err) + } else if err.Error() != test.err { + return fmt.Errorf("expected err: '%v' got err: %q", test.err, err) + } + } else if len(test.err) > 0 { + return fmt.Errorf("expected err: %v but got none", test.err) + } + return nil +} + +var unpackTests = []unpackTest{ + { + def: `[{ "type": "bool" }]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: true, + }, + { + def: `[{ "type": "bool" }]`, + enc: "0000000000000000000000000000000000000000000000000000000000000000", + want: false, + }, + { + def: `[{ "type": "bool" }]`, + enc: "0000000000000000000000000000000000000000000000000001000000000001", + want: false, + err: "abi: improperly encoded boolean value", + }, + { + def: `[{ "type": "bool" }]`, + enc: "0000000000000000000000000000000000000000000000000000000000000003", + want: false, + err: "abi: improperly encoded boolean value", + }, + { + def: `[{"type": "uint32"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: uint32(1), + }, + { + def: `[{"type": "uint32"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: uint16(0), + err: "abi: cannot unmarshal uint32 in to uint16", + }, + { + def: `[{"type": "uint17"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: uint16(0), + err: "abi: cannot unmarshal *big.Int in to uint16", + }, + { + def: `[{"type": "uint17"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: big.NewInt(1), + }, + { + def: `[{"type": "int32"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: int32(1), + }, + { + def: `[{"type": "int32"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: int16(0), + err: "abi: cannot unmarshal int32 in to int16", + }, + { + def: `[{"type": "int17"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: int16(0), + err: "abi: cannot unmarshal *big.Int in to int16", + }, + { + def: `[{"type": "int17"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: big.NewInt(1), + }, + { + def: `[{"type": "int256"}]`, + enc: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + want: big.NewInt(-1), + }, + { + def: `[{"type": "address"}]`, + enc: "0000000000000000000000000100000000000000000000000000000000000000", + want: common.Address{1}, + }, + { + def: `[{"type": "bytes32"}]`, + enc: "0100000000000000000000000000000000000000000000000000000000000000", + want: [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + { + def: `[{"type": "bytes"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000", + want: common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + def: `[{"type": "bytes"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000", + want: [32]byte{}, + err: "abi: cannot unmarshal []uint8 in to [32]uint8", + }, + { + def: `[{"type": "bytes32"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000", + want: []byte(nil), + err: "abi: cannot unmarshal [32]uint8 in to []uint8", + }, + { + def: `[{"type": "bytes32"}]`, + enc: "0100000000000000000000000000000000000000000000000000000000000000", + want: [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + { + def: `[{"type": "function"}]`, + enc: "0100000000000000000000000000000000000000000000000000000000000000", + want: [24]byte{1}, + }, + // slices + { + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []uint8{1, 2}, + }, + { + def: `[{"type": "uint8[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]uint8{1, 2}, + }, + // multi dimensional, if these pass, all types that don't require length prefix should pass + { + def: `[{"type": "uint8[][]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [][]uint8{{1, 2}, {1, 2}}, + }, + { + def: `[{"type": "uint8[][]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", + want: [][]uint8{{1, 2}, {1, 2, 3}}, + }, + { + def: `[{"type": "uint8[2][2]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2][2]uint8{{1, 2}, {1, 2}}, + }, + { + def: `[{"type": "uint8[][2]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001", + want: [2][]uint8{{1}, {1}}, + }, + { + def: `[{"type": "uint8[2][]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [][2]uint8{{1, 2}}, + }, + { + def: `[{"type": "uint8[2][]"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [][2]uint8{{1, 2}, {1, 2}}, + }, + { + def: `[{"type": "uint16[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []uint16{1, 2}, + }, + { + def: `[{"type": "uint16[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]uint16{1, 2}, + }, + { + def: `[{"type": "uint32[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []uint32{1, 2}, + }, + { + def: `[{"type": "uint32[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]uint32{1, 2}, + }, + { + def: `[{"type": "uint32[2][3][4]"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000018", + want: [4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}}, + }, + { + def: `[{"type": "uint64[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []uint64{1, 2}, + }, + { + def: `[{"type": "uint64[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]uint64{1, 2}, + }, + { + def: `[{"type": "uint256[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []*big.Int{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"type": "uint256[3]"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", + want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}, + }, + { + def: `[{"type": "string[4]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b476f2d657468657265756d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000", + want: [4]string{"Hello", "World", "Go-ethereum", "Ethereum"}, + }, + { + def: `[{"type": "string[]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b676f2d657468657265756d000000000000000000000000000000000000000000", + want: []string{"Ethereum", "go-ethereum"}, + }, + { + def: `[{"type": "bytes[]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003f0f0f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003f0f0f00000000000000000000000000000000000000000000000000000000000", + want: [][]byte{{0xf0, 0xf0, 0xf0}, {0xf0, 0xf0, 0xf0}}, + }, + { + def: `[{"type": "uint256[2][][]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e8", + want: [][][2]*big.Int{{{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}, {{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}}, + }, + { + def: `[{"type": "int8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []int8{1, 2}, + }, + { + def: `[{"type": "int8[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]int8{1, 2}, + }, + { + def: `[{"type": "int16[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []int16{1, 2}, + }, + { + def: `[{"type": "int16[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]int16{1, 2}, + }, + { + def: `[{"type": "int32[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []int32{1, 2}, + }, + { + def: `[{"type": "int32[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]int32{1, 2}, + }, + { + def: `[{"type": "int64[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []int64{1, 2}, + }, + { + def: `[{"type": "int64[2]"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: [2]int64{1, 2}, + }, + { + def: `[{"type": "int256[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: []*big.Int{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"type": "int256[3]"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", + want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}, + }, + // struct outputs + { + def: `[{"name":"int1","type":"int256"},{"name":"int2","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + Int1 *big.Int + Int2 *big.Int + }{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"name":"int_one","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"name":"int__one","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"name":"int_one_","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"name":"int_one","type":"int256"}, {"name":"intone","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + IntOne *big.Int + Intone *big.Int + }{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"name":"___","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + IntOne *big.Int + Intone *big.Int + }{}, + err: "abi: purely underscored output cannot unpack to struct", + }, + { + def: `[{"name":"int_one","type":"int256"},{"name":"IntOne","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + Int1 *big.Int + Int2 *big.Int + }{}, + err: "abi: multiple outputs mapping to the same struct field 'IntOne'", + }, + { + def: `[{"name":"int","type":"int256"},{"name":"Int","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + Int1 *big.Int + Int2 *big.Int + }{}, + err: "abi: multiple outputs mapping to the same struct field 'Int'", + }, + { + def: `[{"name":"int","type":"int256"},{"name":"_int","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + Int1 *big.Int + Int2 *big.Int + }{}, + err: "abi: multiple outputs mapping to the same struct field 'Int'", + }, + { + def: `[{"name":"Int","type":"int256"},{"name":"_int","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + Int1 *big.Int + Int2 *big.Int + }{}, + err: "abi: multiple outputs mapping to the same struct field 'Int'", + }, + { + def: `[{"name":"Int","type":"int256"},{"name":"_","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + Int1 *big.Int + Int2 *big.Int + }{}, + err: "abi: purely underscored output cannot unpack to struct", + }, +} + +func TestUnpack(t *testing.T) { + for i, test := range unpackTests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def) + abi, err := JSON(strings.NewReader(def)) + if err != nil { + t.Fatalf("invalid ABI definition %s: %v", def, err) + } + encb, err := hex.DecodeString(test.enc) + if err != nil { + t.Fatalf("invalid hex: %s" + test.enc) + } + outptr := reflect.New(reflect.TypeOf(test.want)) + err = abi.Unpack(outptr.Interface(), "method", encb) + if err := test.checkError(err); err != nil { + t.Errorf("test %d (%v) failed: %v", i, test.def, err) + return + } + out := outptr.Elem().Interface() + if !reflect.DeepEqual(test.want, out) { + t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out) + } + }) + } +} + +func TestUnpackSetDynamicArrayOutput(t *testing.T) { + abi, err := JSON(strings.NewReader(`[{"constant":true,"inputs":[],"name":"testDynamicFixedBytes15","outputs":[{"name":"","type":"bytes15[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"testDynamicFixedBytes32","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"}]`)) + if err != nil { + t.Fatal(err) + } + + var ( + marshalledReturn32 = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000230783132333435363738393000000000000000000000000000000000000000003078303938373635343332310000000000000000000000000000000000000000") + marshalledReturn15 = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000230783031323334350000000000000000000000000000000000000000000000003078393837363534000000000000000000000000000000000000000000000000") + + out32 [][32]byte + out15 [][15]byte + ) + + // test 32 + err = abi.Unpack(&out32, "testDynamicFixedBytes32", marshalledReturn32) + if err != nil { + t.Fatal(err) + } + if len(out32) != 2 { + t.Fatalf("expected array with 2 values, got %d", len(out32)) + } + expected := common.Hex2Bytes("3078313233343536373839300000000000000000000000000000000000000000") + if !bytes.Equal(out32[0][:], expected) { + t.Errorf("expected %x, got %x\n", expected, out32[0]) + } + expected = common.Hex2Bytes("3078303938373635343332310000000000000000000000000000000000000000") + if !bytes.Equal(out32[1][:], expected) { + t.Errorf("expected %x, got %x\n", expected, out32[1]) + } + + // test 15 + err = abi.Unpack(&out15, "testDynamicFixedBytes32", marshalledReturn15) + if err != nil { + t.Fatal(err) + } + if len(out15) != 2 { + t.Fatalf("expected array with 2 values, got %d", len(out15)) + } + expected = common.Hex2Bytes("307830313233343500000000000000") + if !bytes.Equal(out15[0][:], expected) { + t.Errorf("expected %x, got %x\n", expected, out15[0]) + } + expected = common.Hex2Bytes("307839383736353400000000000000") + if !bytes.Equal(out15[1][:], expected) { + t.Errorf("expected %x, got %x\n", expected, out15[1]) + } +} + +type methodMultiOutput struct { + Int *big.Int + String string +} + +func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOutput) { + const definition = `[ + { "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]` + var expected = methodMultiOutput{big.NewInt(1), "hello"} + + abi, err := JSON(strings.NewReader(definition)) + require.NoError(err) + // using buff to make the code readable + buff := new(bytes.Buffer) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005")) + buff.Write(common.RightPadBytes([]byte(expected.String), 32)) + return abi, buff.Bytes(), expected +} + +func TestMethodMultiReturn(t *testing.T) { + type reversed struct { + String string + Int *big.Int + } + + newInterfaceSlice := func(len int) interface{} { + slice := make([]interface{}, len) + return &slice + } + + abi, data, expected := methodMultiReturn(require.New(t)) + bigint := new(big.Int) + var testCases = []struct { + dest interface{} + expected interface{} + error string + name string + }{{ + &methodMultiOutput{}, + &expected, + "", + "Can unpack into structure", + }, { + &reversed{}, + &reversed{expected.String, expected.Int}, + "", + "Can unpack into reversed structure", + }, { + &[]interface{}{&bigint, new(string)}, + &[]interface{}{&expected.Int, &expected.String}, + "", + "Can unpack into a slice", + }, { + &[2]interface{}{&bigint, new(string)}, + &[2]interface{}{&expected.Int, &expected.String}, + "", + "Can unpack into an array", + }, { + &[2]interface{}{}, + &[2]interface{}{expected.Int, expected.String}, + "", + "Can unpack into interface array", + }, { + newInterfaceSlice(2), + &[]interface{}{expected.Int, expected.String}, + "", + "Can unpack into interface slice", + }, { + &[]interface{}{new(int), new(int)}, + &[]interface{}{&expected.Int, &expected.String}, + "abi: cannot unmarshal *big.Int in to int", + "Can not unpack into a slice with wrong types", + }, { + &[]interface{}{new(int)}, + &[]interface{}{}, + "abi: insufficient number of elements in the list/array for unpack, want 2, got 1", + "Can not unpack into a slice with wrong types", + }} + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + require := require.New(t) + err := abi.Unpack(tc.dest, "multi", data) + if tc.error == "" { + require.Nil(err, "Should be able to unpack method outputs.") + require.Equal(tc.expected, tc.dest) + } else { + require.EqualError(err, tc.error) + } + }) + } +} + +func TestMultiReturnWithArray(t *testing.T) { + const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]` + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + buff := new(bytes.Buffer) + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000009")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000008")) + + ret1, ret1Exp := new([3]uint64), [3]uint64{9, 9, 9} + ret2, ret2Exp := new(uint64), uint64(8) + if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(*ret1, ret1Exp) { + t.Error("array result", *ret1, "!= Expected", ret1Exp) + } + if *ret2 != ret2Exp { + t.Error("int result", *ret2, "!= Expected", ret2Exp) + } +} + +func TestMultiReturnWithStringArray(t *testing.T) { + const definition = `[{"name" : "multi", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]` + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + buff := new(bytes.Buffer) + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000005c1b78ea0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000001a055690d9db80000000000000000000000000000ab1257528b3782fb40d7ed5f72e624b744dffb2f00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001048656c6c6f2c20457468657265756d2100000000000000000000000000000000")) + temp, _ := big.NewInt(0).SetString("30000000000000000000", 10) + ret1, ret1Exp := new([3]*big.Int), [3]*big.Int{big.NewInt(1545304298), big.NewInt(6), temp} + ret2, ret2Exp := new(common.Address), common.HexToAddress("ab1257528b3782fb40d7ed5f72e624b744dffb2f") + ret3, ret3Exp := new([2]string), [2]string{"Ethereum", "Hello, Ethereum!"} + ret4, ret4Exp := new(bool), false + if err := abi.Unpack(&[]interface{}{ret1, ret2, ret3, ret4}, "multi", buff.Bytes()); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(*ret1, ret1Exp) { + t.Error("big.Int array result", *ret1, "!= Expected", ret1Exp) + } + if !reflect.DeepEqual(*ret2, ret2Exp) { + t.Error("address result", *ret2, "!= Expected", ret2Exp) + } + if !reflect.DeepEqual(*ret3, ret3Exp) { + t.Error("string array result", *ret3, "!= Expected", ret3Exp) + } + if !reflect.DeepEqual(*ret4, ret4Exp) { + t.Error("bool result", *ret4, "!= Expected", ret4Exp) + } +} + +func TestMultiReturnWithStringSlice(t *testing.T) { + const definition = `[{"name" : "multi", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]` + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + buff := new(bytes.Buffer) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // output[0] offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000120")) // output[1] offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // output[0] length + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // output[0][0] offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // output[0][1] offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000008")) // output[0][0] length + buff.Write(common.Hex2Bytes("657468657265756d000000000000000000000000000000000000000000000000")) // output[0][0] value + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000b")) // output[0][1] length + buff.Write(common.Hex2Bytes("676f2d657468657265756d000000000000000000000000000000000000000000")) // output[0][1] value + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // output[1] length + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000064")) // output[1][0] value + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000065")) // output[1][1] value + ret1, ret1Exp := new([]string), []string{"ethereum", "go-ethereum"} + ret2, ret2Exp := new([]*big.Int), []*big.Int{big.NewInt(100), big.NewInt(101)} + if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(*ret1, ret1Exp) { + t.Error("string slice result", *ret1, "!= Expected", ret1Exp) + } + if !reflect.DeepEqual(*ret2, ret2Exp) { + t.Error("uint256 slice result", *ret2, "!= Expected", ret2Exp) + } +} + +func TestMultiReturnWithDeeplyNestedArray(t *testing.T) { + // Similar to TestMultiReturnWithArray, but with a special case in mind: + // values of nested static arrays count towards the size as well, and any element following + // after such nested array argument should be read with the correct offset, + // so that it does not read content from the previous array argument. + const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3][2][4]"}, {"type": "uint64"}]}]` + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + buff := new(bytes.Buffer) + // construct the test array, each 3 char element is joined with 61 '0' chars, + // to from the ((3 + 61) * 0.5) = 32 byte elements in the array. + buff.Write(common.Hex2Bytes(strings.Join([]string{ + "", //empty, to apply the 61-char separator to the first element as well. + "111", "112", "113", "121", "122", "123", + "211", "212", "213", "221", "222", "223", + "311", "312", "313", "321", "322", "323", + "411", "412", "413", "421", "422", "423", + }, "0000000000000000000000000000000000000000000000000000000000000"))) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000009876")) + + ret1, ret1Exp := new([4][2][3]uint64), [4][2][3]uint64{ + {{0x111, 0x112, 0x113}, {0x121, 0x122, 0x123}}, + {{0x211, 0x212, 0x213}, {0x221, 0x222, 0x223}}, + {{0x311, 0x312, 0x313}, {0x321, 0x322, 0x323}}, + {{0x411, 0x412, 0x413}, {0x421, 0x422, 0x423}}, + } + ret2, ret2Exp := new(uint64), uint64(0x9876) + if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(*ret1, ret1Exp) { + t.Error("array result", *ret1, "!= Expected", ret1Exp) + } + if *ret2 != ret2Exp { + t.Error("int result", *ret2, "!= Expected", ret2Exp) + } +} + +func TestUnmarshal(t *testing.T) { + const definition = `[ + { "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] }, + { "name" : "bool", "constant" : false, "outputs": [ { "type": "bool" } ] }, + { "name" : "bytes", "constant" : false, "outputs": [ { "type": "bytes" } ] }, + { "name" : "fixed", "constant" : false, "outputs": [ { "type": "bytes32" } ] }, + { "name" : "multi", "constant" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] }, + { "name" : "intArraySingle", "constant" : false, "outputs": [ { "type": "uint256[3]" } ] }, + { "name" : "addressSliceSingle", "constant" : false, "outputs": [ { "type": "address[]" } ] }, + { "name" : "addressSliceDouble", "constant" : false, "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] }, + { "name" : "mixedBytes", "constant" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + buff := new(bytes.Buffer) + + // marshall mixed bytes (mixedBytes) + p0, p0Exp := []byte{}, common.Hex2Bytes("01020000000000000000") + p1, p1Exp := [32]byte{}, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff") + mixedBytes := []interface{}{&p0, &p1} + + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff")) + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000a")) + buff.Write(common.Hex2Bytes("0102000000000000000000000000000000000000000000000000000000000000")) + + err = abi.Unpack(&mixedBytes, "mixedBytes", buff.Bytes()) + if err != nil { + t.Error(err) + } else { + if !bytes.Equal(p0, p0Exp) { + t.Errorf("unexpected value unpacked: want %x, got %x", p0Exp, p0) + } + + if !bytes.Equal(p1[:], p1Exp) { + t.Errorf("unexpected value unpacked: want %x, got %x", p1Exp, p1) + } + } + + // marshal int + var Int *big.Int + err = abi.Unpack(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + if err != nil { + t.Error(err) + } + + if Int == nil || Int.Cmp(big.NewInt(1)) != 0 { + t.Error("expected Int to be 1 got", Int) + } + + // marshal bool + var Bool bool + err = abi.Unpack(&Bool, "bool", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + if err != nil { + t.Error(err) + } + + if !Bool { + t.Error("expected Bool to be true") + } + + // marshal dynamic bytes max length 32 + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + bytesOut := common.RightPadBytes([]byte("hello"), 32) + buff.Write(bytesOut) + + var Bytes []byte + err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(Bytes, bytesOut) { + t.Errorf("expected %x got %x", bytesOut, Bytes) + } + + // marshall dynamic bytes max length 64 + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) + bytesOut = common.RightPadBytes([]byte("hello"), 64) + buff.Write(bytesOut) + + err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(Bytes, bytesOut) { + t.Errorf("expected %x got %x", bytesOut, Bytes) + } + + // marshall dynamic bytes max length 64 + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000003f")) + bytesOut = common.RightPadBytes([]byte("hello"), 64) + buff.Write(bytesOut) + + err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(Bytes, bytesOut[:len(bytesOut)-1]) { + t.Errorf("expected %x got %x", bytesOut[:len(bytesOut)-1], Bytes) + } + + // marshal dynamic bytes output empty + err = abi.Unpack(&Bytes, "bytes", nil) + if err == nil { + t.Error("expected error") + } + + // marshal dynamic bytes length 5 + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005")) + buff.Write(common.RightPadBytes([]byte("hello"), 32)) + + err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(Bytes, []byte("hello")) { + t.Errorf("expected %x got %x", bytesOut, Bytes) + } + + // marshal dynamic bytes length 5 + buff.Reset() + buff.Write(common.RightPadBytes([]byte("hello"), 32)) + + var hash common.Hash + err = abi.Unpack(&hash, "fixed", buff.Bytes()) + if err != nil { + t.Error(err) + } + + helloHash := common.BytesToHash(common.RightPadBytes([]byte("hello"), 32)) + if hash != helloHash { + t.Errorf("Expected %x to equal %x", hash, helloHash) + } + + // marshal error + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + if err == nil { + t.Error("expected error") + } + + err = abi.Unpack(&Bytes, "multi", make([]byte, 64)) + if err == nil { + t.Error("expected error") + } + + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000003")) + // marshal int array + var intArray [3]*big.Int + err = abi.Unpack(&intArray, "intArraySingle", buff.Bytes()) + if err != nil { + t.Error(err) + } + var testAgainstIntArray [3]*big.Int + testAgainstIntArray[0] = big.NewInt(1) + testAgainstIntArray[1] = big.NewInt(2) + testAgainstIntArray[2] = big.NewInt(3) + + for i, Int := range intArray { + if Int.Cmp(testAgainstIntArray[i]) != 0 { + t.Errorf("expected %v, got %v", testAgainstIntArray[i], Int) + } + } + // marshal address slice + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) // offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size + buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000")) + + var outAddr []common.Address + err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes()) + if err != nil { + t.Fatal("didn't expect error:", err) + } + + if len(outAddr) != 1 { + t.Fatal("expected 1 item, got", len(outAddr)) + } + + if outAddr[0] != (common.Address{1}) { + t.Errorf("expected %x, got %x", common.Address{1}, outAddr[0]) + } + + // marshal multiple address slice + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size + buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // size + buff.Write(common.Hex2Bytes("0000000000000000000000000200000000000000000000000000000000000000")) + buff.Write(common.Hex2Bytes("0000000000000000000000000300000000000000000000000000000000000000")) + + var outAddrStruct struct { + A []common.Address + B []common.Address + } + err = abi.Unpack(&outAddrStruct, "addressSliceDouble", buff.Bytes()) + if err != nil { + t.Fatal("didn't expect error:", err) + } + + if len(outAddrStruct.A) != 1 { + t.Fatal("expected 1 item, got", len(outAddrStruct.A)) + } + + if outAddrStruct.A[0] != (common.Address{1}) { + t.Errorf("expected %x, got %x", common.Address{1}, outAddrStruct.A[0]) + } + + if len(outAddrStruct.B) != 2 { + t.Fatal("expected 1 item, got", len(outAddrStruct.B)) + } + + if outAddrStruct.B[0] != (common.Address{2}) { + t.Errorf("expected %x, got %x", common.Address{2}, outAddrStruct.B[0]) + } + if outAddrStruct.B[1] != (common.Address{3}) { + t.Errorf("expected %x, got %x", common.Address{3}, outAddrStruct.B[1]) + } + + // marshal invalid address slice + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000100")) + + err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes()) + if err == nil { + t.Fatal("expected error:", err) + } +} + +func TestUnpackTuple(t *testing.T) { + const simpleTuple = `[{"name":"tuple","constant":false,"outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]` + abi, err := JSON(strings.NewReader(simpleTuple)) + if err != nil { + t.Fatal(err) + } + buff := new(bytes.Buffer) + + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // ret[a] = 1 + buff.Write(common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) // ret[b] = -1 + + v := struct { + Ret struct { + A *big.Int + B *big.Int + } + }{Ret: struct { + A *big.Int + B *big.Int + }{new(big.Int), new(big.Int)}} + + err = abi.Unpack(&v, "tuple", buff.Bytes()) + if err != nil { + t.Error(err) + } else { + if v.Ret.A.Cmp(big.NewInt(1)) != 0 { + t.Errorf("unexpected value unpacked: want %x, got %x", 1, v.Ret.A) + } + if v.Ret.B.Cmp(big.NewInt(-1)) != 0 { + t.Errorf("unexpected value unpacked: want %x, got %x", v.Ret.B, -1) + } + } + + // Test nested tuple + const nestedTuple = `[{"name":"tuple","constant":false,"outputs":[ + {"type":"tuple","name":"s","components":[{"type":"uint256","name":"a"},{"type":"uint256[]","name":"b"},{"type":"tuple[]","name":"c","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]}]}, + {"type":"tuple","name":"t","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]}, + {"type":"uint256","name":"a"} + ]}]` + + abi, err = JSON(strings.NewReader(nestedTuple)) + if err != nil { + t.Fatal(err) + } + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // s offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")) // t.X = 0 + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // t.Y = 1 + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // a = 1 + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.A = 1 + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000060")) // s.B offset + buff.Write(common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000c0")) // s.C offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.B length + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.B[0] = 1 + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.B[0] = 2 + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.C length + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.C[0].X + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.C[0].Y + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.C[1].X + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.C[1].Y + + type T struct { + X *big.Int `abi:"x"` + Z *big.Int `abi:"y"` // Test whether the abi tag works. + } + + type S struct { + A *big.Int + B []*big.Int + C []T + } + + type Ret struct { + FieldS S `abi:"s"` + FieldT T `abi:"t"` + A *big.Int + } + var ret Ret + var expected = Ret{ + FieldS: S{ + A: big.NewInt(1), + B: []*big.Int{big.NewInt(1), big.NewInt(2)}, + C: []T{ + {big.NewInt(1), big.NewInt(2)}, + {big.NewInt(2), big.NewInt(1)}, + }, + }, + FieldT: T{ + big.NewInt(0), big.NewInt(1), + }, + A: big.NewInt(1), + } + + err = abi.Unpack(&ret, "tuple", buff.Bytes()) + if err != nil { + t.Error(err) + } + if reflect.DeepEqual(ret, expected) { + t.Error("unexpected unpack value") + } +} + +func TestOOMMaliciousInput(t *testing.T) { + oomTests := []unpackTest{ + { + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset + "0000000000000000000000000000000000000000000000000000000000000003" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Length larger than 64 bits + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset + "00ffffffffffffffffffffffffffffffffffffffffffffff0000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Offset very large (over 64 bits) + def: `[{"type": "uint8[]"}]`, + enc: "00ffffffffffffffffffffffffffffffffffffffffffffff0000000000000020" + // offset + "0000000000000000000000000000000000000000000000000000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Offset very large (below 64 bits) + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000007ffffffffff00020" + // offset + "0000000000000000000000000000000000000000000000000000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Offset negative (as 64 bit) + def: `[{"type": "uint8[]"}]`, + enc: "000000000000000000000000000000000000000000000000f000000000000020" + // offset + "0000000000000000000000000000000000000000000000000000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + + { // Negative length + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset + "000000000000000000000000000000000000000000000000f000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Very large length + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset + "0000000000000000000000000000000000000000000000007fffffffff000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + } + for i, test := range oomTests { + def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def) + abi, err := JSON(strings.NewReader(def)) + if err != nil { + t.Fatalf("invalid ABI definition %s: %v", def, err) + } + encb, err := hex.DecodeString(test.enc) + if err != nil { + t.Fatalf("invalid hex: %s" + test.enc) + } + _, err = abi.Methods["method"].Outputs.UnpackValues(encb) + if err == nil { + t.Fatalf("Expected error on malicious input, test %d", i) + } + } +} diff --git a/accounts/accounts.go b/accounts/accounts.go new file mode 100644 index 000000000..afeb412fe --- /dev/null +++ b/accounts/accounts.go @@ -0,0 +1,222 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package accounts implements high level Ethereum account management. +package accounts + +import ( + "fmt" + "math/big" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "golang.org/x/crypto/sha3" +) + +// Account represents an Ethereum account located at a specific location defined +// by the optional URL field. +type Account struct { + Address common.Address `json:"address"` // Ethereum account address derived from the key + URL URL `json:"url"` // Optional resource locator within a backend +} + +const ( + MimetypeTextWithValidator = "text/validator" + MimetypeTypedData = "data/typed" + MimetypeClique = "application/x-clique-header" + MimetypeTextPlain = "text/plain" +) + +// Wallet represents a software or hardware wallet that might contain one or more +// accounts (derived from the same seed). +type Wallet interface { + // URL retrieves the canonical path under which this wallet is reachable. It is + // user by upper layers to define a sorting order over all wallets from multiple + // backends. + URL() URL + + // Status returns a textual status to aid the user in the current state of the + // wallet. It also returns an error indicating any failure the wallet might have + // encountered. + Status() (string, error) + + // Open initializes access to a wallet instance. It is not meant to unlock or + // decrypt account keys, rather simply to establish a connection to hardware + // wallets and/or to access derivation seeds. + // + // The passphrase parameter may or may not be used by the implementation of a + // particular wallet instance. The reason there is no passwordless open method + // is to strive towards a uniform wallet handling, oblivious to the different + // backend providers. + // + // Please note, if you open a wallet, you must close it to release any allocated + // resources (especially important when working with hardware wallets). + Open(passphrase string) error + + // Close releases any resources held by an open wallet instance. + Close() error + + // Accounts retrieves the list of signing accounts the wallet is currently aware + // of. For hierarchical deterministic wallets, the list will not be exhaustive, + // rather only contain the accounts explicitly pinned during account derivation. + Accounts() []Account + + // Contains returns whether an account is part of this particular wallet or not. + Contains(account Account) bool + + // Derive attempts to explicitly derive a hierarchical deterministic account at + // the specified derivation path. If requested, the derived account will be added + // to the wallet's tracked account list. + Derive(path DerivationPath, pin bool) (Account, error) + + // SelfDerive sets a base account derivation path from which the wallet attempts + // to discover non zero accounts and automatically add them to list of tracked + // accounts. + // + // Note, self derivaton will increment the last component of the specified path + // opposed to decending into a child path to allow discovering accounts starting + // from non zero components. + // + // Some hardware wallets switched derivation paths through their evolution, so + // this method supports providing multiple bases to discover old user accounts + // too. Only the last base will be used to derive the next empty account. + // + // You can disable automatic account discovery by calling SelfDerive with a nil + // chain state reader. + SelfDerive(bases []DerivationPath, chain ethereum.ChainStateReader) + + // SignData requests the wallet to sign the hash of the given data + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + // + // If the wallet requires additional authentication to sign the request (e.g. + // a password to decrypt the account, or a PIN code o verify the transaction), + // an AuthNeededError instance will be returned, containing infos for the user + // about which fields or actions are needed. The user may retry by providing + // the needed details via SignDataWithPassphrase, or by other means (e.g. unlock + // the account in a keystore). + SignData(account Account, mimeType string, data []byte) ([]byte, error) + + // SignDataWithPassphrase is identical to SignData, but also takes a password + // NOTE: there's an chance that an erroneous call might mistake the two strings, and + // supply password in the mimetype field, or vice versa. Thus, an implementation + // should never echo the mimetype or return the mimetype in the error-response + SignDataWithPassphrase(account Account, passphrase, mimeType string, data []byte) ([]byte, error) + + // SignText requests the wallet to sign the hash of a given piece of data, prefixed + // by the Ethereum prefix scheme + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + // + // If the wallet requires additional authentication to sign the request (e.g. + // a password to decrypt the account, or a PIN code o verify the transaction), + // an AuthNeededError instance will be returned, containing infos for the user + // about which fields or actions are needed. The user may retry by providing + // the needed details via SignHashWithPassphrase, or by other means (e.g. unlock + // the account in a keystore). + SignText(account Account, text []byte) ([]byte, error) + + // SignTextWithPassphrase is identical to Signtext, but also takes a password + SignTextWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error) + + // SignTx requests the wallet to sign the given transaction. + // + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + // + // If the wallet requires additional authentication to sign the request (e.g. + // a password to decrypt the account, or a PIN code to verify the transaction), + // an AuthNeededError instance will be returned, containing infos for the user + // about which fields or actions are needed. The user may retry by providing + // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock + // the account in a keystore). + SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) + + // SignTxWithPassphrase is identical to SignTx, but also takes a password + SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) +} + +// Backend is a "wallet provider" that may contain a batch of accounts they can +// sign transactions with and upon request, do so. +type Backend interface { + // Wallets retrieves the list of wallets the backend is currently aware of. + // + // The returned wallets are not opened by default. For software HD wallets this + // means that no base seeds are decrypted, and for hardware wallets that no actual + // connection is established. + // + // The resulting wallet list will be sorted alphabetically based on its internal + // URL assigned by the backend. Since wallets (especially hardware) may come and + // go, the same wallet might appear at a different positions in the list during + // subsequent retrievals. + Wallets() []Wallet + + // Subscribe creates an async subscription to receive notifications when the + // backend detects the arrival or departure of a wallet. + Subscribe(sink chan<- WalletEvent) event.Subscription +} + +// TextHash is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calulcated as +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func TextHash(data []byte) []byte { + hash, _ := TextAndHash(data) + return hash +} + +// TextAndHash is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calulcated as +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func TextAndHash(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data)) + hasher := sha3.NewLegacyKeccak256() + hasher.Write([]byte(msg)) + return hasher.Sum(nil), msg +} + +// WalletEventType represents the different event types that can be fired by +// the wallet subscription subsystem. +type WalletEventType int + +const ( + // WalletArrived is fired when a new wallet is detected either via USB or via + // a filesystem event in the keystore. + WalletArrived WalletEventType = iota + + // WalletOpened is fired when a wallet is successfully opened with the purpose + // of starting any background processes such as automatic key derivation. + WalletOpened + + // WalletDropped + WalletDropped +) + +// WalletEvent is an event fired by an account backend when a wallet arrival or +// departure is detected. +type WalletEvent struct { + Wallet Wallet // Wallet instance arrived or departed + Kind WalletEventType // Event type that happened in the system +} diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go new file mode 100644 index 000000000..a49e3954e --- /dev/null +++ b/accounts/accounts_test.go @@ -0,0 +1,32 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package accounts + +import ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +func TestTextHash(t *testing.T) { + hash := TextHash([]byte("Hello Joe")) + want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b") + if !bytes.Equal(hash, want) { + t.Fatalf("wrong hash: %x", hash) + } +} diff --git a/accounts/errors.go b/accounts/errors.go new file mode 100644 index 000000000..40b21ed17 --- /dev/null +++ b/accounts/errors.go @@ -0,0 +1,68 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package accounts + +import ( + "errors" + "fmt" +) + +// ErrUnknownAccount is returned for any requested operation for which no backend +// provides the specified account. +var ErrUnknownAccount = errors.New("unknown account") + +// ErrUnknownWallet is returned for any requested operation for which no backend +// provides the specified wallet. +var ErrUnknownWallet = errors.New("unknown wallet") + +// ErrNotSupported is returned when an operation is requested from an account +// backend that it does not support. +var ErrNotSupported = errors.New("not supported") + +// ErrInvalidPassphrase is returned when a decryption operation receives a bad +// passphrase. +var ErrInvalidPassphrase = errors.New("invalid passphrase") + +// ErrWalletAlreadyOpen is returned if a wallet is attempted to be opened the +// second time. +var ErrWalletAlreadyOpen = errors.New("wallet already open") + +// ErrWalletClosed is returned if a wallet is attempted to be opened the +// secodn time. +var ErrWalletClosed = errors.New("wallet closed") + +// AuthNeededError is returned by backends for signing requests where the user +// is required to provide further authentication before signing can succeed. +// +// This usually means either that a password needs to be supplied, or perhaps a +// one time PIN code displayed by some hardware device. +type AuthNeededError struct { + Needed string // Extra authentication the user needs to provide +} + +// NewAuthNeededError creates a new authentication error with the extra details +// about the needed fields set. +func NewAuthNeededError(needed string) error { + return &AuthNeededError{ + Needed: needed, + } +} + +// Error implements the standard error interface. +func (err *AuthNeededError) Error() string { + return fmt.Sprintf("authentication needed: %s", err.Needed) +} diff --git a/accounts/external/backend.go b/accounts/external/backend.go new file mode 100644 index 000000000..23037f52d --- /dev/null +++ b/accounts/external/backend.go @@ -0,0 +1,228 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package external + +import ( + "fmt" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/signer/core" +) + +type ExternalBackend struct { + signers []accounts.Wallet +} + +func (eb *ExternalBackend) Wallets() []accounts.Wallet { + return eb.signers +} + +func NewExternalBackend(endpoint string) (*ExternalBackend, error) { + signer, err := NewExternalSigner(endpoint) + if err != nil { + return nil, err + } + return &ExternalBackend{ + signers: []accounts.Wallet{signer}, + }, nil +} + +func (eb *ExternalBackend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { + return event.NewSubscription(func(quit <-chan struct{}) error { + <-quit + return nil + }) +} + +// ExternalSigner provides an API to interact with an external signer (clef) +// It proxies request to the external signer while forwarding relevant +// request headers +type ExternalSigner struct { + client *rpc.Client + endpoint string + status string + cacheMu sync.RWMutex + cache []accounts.Account +} + +func NewExternalSigner(endpoint string) (*ExternalSigner, error) { + client, err := rpc.Dial(endpoint) + if err != nil { + return nil, err + } + extsigner := &ExternalSigner{ + client: client, + endpoint: endpoint, + } + // Check if reachable + version, err := extsigner.pingVersion() + if err != nil { + return nil, err + } + extsigner.status = fmt.Sprintf("ok [version=%v]", version) + return extsigner, nil +} + +func (api *ExternalSigner) URL() accounts.URL { + return accounts.URL{ + Scheme: "extapi", + Path: api.endpoint, + } +} + +func (api *ExternalSigner) Status() (string, error) { + return api.status, nil +} + +func (api *ExternalSigner) Open(passphrase string) error { + return fmt.Errorf("operation not supported on external signers") +} + +func (api *ExternalSigner) Close() error { + return fmt.Errorf("operation not supported on external signers") +} + +func (api *ExternalSigner) Accounts() []accounts.Account { + var accnts []accounts.Account + res, err := api.listAccounts() + if err != nil { + log.Error("account listing failed", "error", err) + return accnts + } + for _, addr := range res { + accnts = append(accnts, accounts.Account{ + URL: accounts.URL{ + Scheme: "extapi", + Path: api.endpoint, + }, + Address: addr, + }) + } + api.cacheMu.Lock() + api.cache = accnts + api.cacheMu.Unlock() + return accnts +} + +func (api *ExternalSigner) Contains(account accounts.Account) bool { + api.cacheMu.RLock() + defer api.cacheMu.RUnlock() + for _, a := range api.cache { + if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) { + return true + } + } + return false +} + +func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { + return accounts.Account{}, fmt.Errorf("operation not supported on external signers") +} + +func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { + log.Error("operation SelfDerive not supported on external signers") +} + +func (api *ExternalSigner) signHash(account accounts.Account, hash []byte) ([]byte, error) { + return []byte{}, fmt.Errorf("operation not supported on external signers") +} + +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed +func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { + var res hexutil.Bytes + var signAddress = common.NewMixedcaseAddress(account.Address) + if err := api.client.Call(&res, "account_signData", + mimeType, + &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined + hexutil.Encode(data)); err != nil { + return nil, err + } + // If V is on 27/28-form, convert to to 0/1 for Clique + if mimeType == accounts.MimetypeClique && (res[64] == 27 || res[64] == 28) { + res[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use + } + return res, nil +} + +func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) { + var res hexutil.Bytes + var signAddress = common.NewMixedcaseAddress(account.Address) + if err := api.client.Call(&res, "account_signData", + accounts.MimetypeTextPlain, + &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined + hexutil.Encode(text)); err != nil { + return nil, err + } + return res, nil +} + +func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + res := ethapi.SignTransactionResult{} + to := common.NewMixedcaseAddress(*tx.To()) + data := hexutil.Bytes(tx.Data()) + args := &core.SendTxArgs{ + Data: &data, + Nonce: hexutil.Uint64(tx.Nonce()), + Value: hexutil.Big(*tx.Value()), + Gas: hexutil.Uint64(tx.Gas()), + GasPrice: hexutil.Big(*tx.GasPrice()), + To: &to, + From: common.NewMixedcaseAddress(account.Address), + } + + if err := api.client.Call(&res, "account_signTransaction", args); err != nil { + return nil, err + } + return res.Tx, nil +} + +func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { + return []byte{}, fmt.Errorf("passphrase-operations not supported on external signers") +} + +func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + return nil, fmt.Errorf("passphrase-operations not supported on external signers") +} +func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { + return nil, fmt.Errorf("passphrase-operations not supported on external signers") +} + +func (api *ExternalSigner) listAccounts() ([]common.Address, error) { + var res []common.Address + if err := api.client.Call(&res, "account_list"); err != nil { + return nil, err + } + return res, nil +} + +func (api *ExternalSigner) pingVersion() (string, error) { + var v string + if err := api.client.Call(&v, "account_version"); err != nil { + return "", err + } + return v, nil +} diff --git a/accounts/hd.go b/accounts/hd.go new file mode 100644 index 000000000..75c476110 --- /dev/null +++ b/accounts/hd.go @@ -0,0 +1,152 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package accounts + +import ( + "encoding/json" + "errors" + "fmt" + "math" + "math/big" + "strings" +) + +// DefaultRootDerivationPath is the root path to which custom derivation endpoints +// are appended. As such, the first account will be at m/44'/60'/0'/0, the second +// at m/44'/60'/0'/1, etc. +var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0} + +// DefaultBaseDerivationPath is the base path from which custom derivation endpoints +// are incremented. As such, the first account will be at m/44'/60'/0'/0/0, the second +// at m/44'/60'/0'/0/1, etc. +var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0} + +// LegacyLedgerBaseDerivationPath is the legacy base path from which custom derivation +// endpoints are incremented. As such, the first account will be at m/44'/60'/0'/0, the +// second at m/44'/60'/0'/1, etc. +var LegacyLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0} + +// DerivationPath represents the computer friendly version of a hierarchical +// deterministic wallet account derivaion path. +// +// The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki +// defines derivation paths to be of the form: +// +// m / purpose' / coin_type' / account' / change / address_index +// +// The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +// defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and +// SLIP-44 https://github.com/satoshilabs/slips/blob/master/slip-0044.md assigns +// the `coin_type` 60' (or 0x8000003C) to Ethereum. +// +// The root path for Ethereum is m/44'/60'/0'/0 according to the specification +// from https://github.com/ethereum/EIPs/issues/84, albeit it's not set in stone +// yet whether accounts should increment the last component or the children of +// that. We will go with the simpler approach of incrementing the last component. +type DerivationPath []uint32 + +// ParseDerivationPath converts a user specified derivation path string to the +// internal binary representation. +// +// Full derivation paths need to start with the `m/` prefix, relative derivation +// paths (which will get appended to the default root path) must not have prefixes +// in front of the first element. Whitespace is ignored. +func ParseDerivationPath(path string) (DerivationPath, error) { + var result DerivationPath + + // Handle absolute or relative paths + components := strings.Split(path, "/") + switch { + case len(components) == 0: + return nil, errors.New("empty derivation path") + + case strings.TrimSpace(components[0]) == "": + return nil, errors.New("ambiguous path: use 'm/' prefix for absolute paths, or no leading '/' for relative ones") + + case strings.TrimSpace(components[0]) == "m": + components = components[1:] + + default: + result = append(result, DefaultRootDerivationPath...) + } + // All remaining components are relative, append one by one + if len(components) == 0 { + return nil, errors.New("empty derivation path") // Empty relative paths + } + for _, component := range components { + // Ignore any user added whitespace + component = strings.TrimSpace(component) + var value uint32 + + // Handle hardened paths + if strings.HasSuffix(component, "'") { + value = 0x80000000 + component = strings.TrimSpace(strings.TrimSuffix(component, "'")) + } + // Handle the non hardened component + bigval, ok := new(big.Int).SetString(component, 0) + if !ok { + return nil, fmt.Errorf("invalid component: %s", component) + } + max := math.MaxUint32 - value + if bigval.Sign() < 0 || bigval.Cmp(big.NewInt(int64(max))) > 0 { + if value == 0 { + return nil, fmt.Errorf("component %v out of allowed range [0, %d]", bigval, max) + } + return nil, fmt.Errorf("component %v out of allowed hardened range [0, %d]", bigval, max) + } + value += uint32(bigval.Uint64()) + + // Append and repeat + result = append(result, value) + } + return result, nil +} + +// String implements the stringer interface, converting a binary derivation path +// to its canonical representation. +func (path DerivationPath) String() string { + result := "m" + for _, component := range path { + var hardened bool + if component >= 0x80000000 { + component -= 0x80000000 + hardened = true + } + result = fmt.Sprintf("%s/%d", result, component) + if hardened { + result += "'" + } + } + return result +} + +// MarshalJSON turns a derivation path into its json-serialized string +func (path DerivationPath) MarshalJSON() ([]byte, error) { + return json.Marshal(path.String()) +} + +// UnmarshalJSON a json-serialized string back into a derivation path +func (path *DerivationPath) UnmarshalJSON(b []byte) error { + var dp string + var err error + if err = json.Unmarshal(b, &dp); err != nil { + return err + } + *path, err = ParseDerivationPath(dp) + return err +} diff --git a/accounts/hd_test.go b/accounts/hd_test.go new file mode 100644 index 000000000..b6b23230d --- /dev/null +++ b/accounts/hd_test.go @@ -0,0 +1,79 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package accounts + +import ( + "reflect" + "testing" +) + +// Tests that HD derivation paths can be correctly parsed into our internal binary +// representation. +func TestHDPathParsing(t *testing.T) { + tests := []struct { + input string + output DerivationPath + }{ + // Plain absolute derivation paths + {"m/44'/60'/0'/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + {"m/44'/60'/0'/128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, + {"m/44'/60'/0'/0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, + {"m/44'/60'/0'/128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, + {"m/2147483692/2147483708/2147483648/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + {"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, + + // Plain relative derivation paths + {"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}}, + {"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}}, + {"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, + {"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}}, + {"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, + + // Hexadecimal absolute derivation paths + {"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + {"m/0x2C'/0x3c'/0x00'/0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, + {"m/0x2C'/0x3c'/0x00'/0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, + {"m/0x2C'/0x3c'/0x00'/0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, + {"m/0x8000002C/0x8000003c/0x80000000/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + {"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, + + // Hexadecimal relative derivation paths + {"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}}, + {"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}}, + {"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, + {"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}}, + {"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, + + // Weird inputs just to ensure they work + {" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + + // Invaid derivation paths + {"", nil}, // Empty relative derivation path + {"m", nil}, // Empty absolute derivation path + {"m/", nil}, // Missing last derivation component + {"/44'/60'/0'/0", nil}, // Absolute path without m prefix, might be user error + {"m/2147483648'", nil}, // Overflows 32 bit integer + {"m/-1'", nil}, // Cannot contain negative number + } + for i, tt := range tests { + if path, err := ParseDerivationPath(tt.input); !reflect.DeepEqual(path, tt.output) { + t.Errorf("test %d: parse mismatch: have %v (%v), want %v", i, path, err, tt.output) + } else if path == nil && err == nil { + t.Errorf("test %d: nil path and error: %v", i, err) + } + } +} diff --git a/accounts/keystore/account_cache.go b/accounts/keystore/account_cache.go new file mode 100644 index 000000000..8f660e282 --- /dev/null +++ b/accounts/keystore/account_cache.go @@ -0,0 +1,301 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package keystore + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" + + mapset "github.com/deckarep/golang-set" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +// Minimum amount of time between cache reloads. This limit applies if the platform does +// not support change notifications. It also applies if the keystore directory does not +// exist yet, the code will attempt to create a watcher at most this often. +const minReloadInterval = 2 * time.Second + +type accountsByURL []accounts.Account + +func (s accountsByURL) Len() int { return len(s) } +func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 } +func (s accountsByURL) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// AmbiguousAddrError is returned when attempting to unlock +// an address for which more than one file exists. +type AmbiguousAddrError struct { + Addr common.Address + Matches []accounts.Account +} + +func (err *AmbiguousAddrError) Error() string { + files := "" + for i, a := range err.Matches { + files += a.URL.Path + if i < len(err.Matches)-1 { + files += ", " + } + } + return fmt.Sprintf("multiple keys match address (%s)", files) +} + +// accountCache is a live index of all accounts in the keystore. +type accountCache struct { + keydir string + watcher *watcher + mu sync.Mutex + all accountsByURL + byAddr map[common.Address][]accounts.Account + throttle *time.Timer + notify chan struct{} + fileC fileCache +} + +func newAccountCache(keydir string) (*accountCache, chan struct{}) { + ac := &accountCache{ + keydir: keydir, + byAddr: make(map[common.Address][]accounts.Account), + notify: make(chan struct{}, 1), + fileC: fileCache{all: mapset.NewThreadUnsafeSet()}, + } + ac.watcher = newWatcher(ac) + return ac, ac.notify +} + +func (ac *accountCache) accounts() []accounts.Account { + ac.maybeReload() + ac.mu.Lock() + defer ac.mu.Unlock() + cpy := make([]accounts.Account, len(ac.all)) + copy(cpy, ac.all) + return cpy +} + +func (ac *accountCache) hasAddress(addr common.Address) bool { + ac.maybeReload() + ac.mu.Lock() + defer ac.mu.Unlock() + return len(ac.byAddr[addr]) > 0 +} + +func (ac *accountCache) add(newAccount accounts.Account) { + ac.mu.Lock() + defer ac.mu.Unlock() + + i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 }) + if i < len(ac.all) && ac.all[i] == newAccount { + return + } + // newAccount is not in the cache. + ac.all = append(ac.all, accounts.Account{}) + copy(ac.all[i+1:], ac.all[i:]) + ac.all[i] = newAccount + ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount) +} + +// note: removed needs to be unique here (i.e. both File and Address must be set). +func (ac *accountCache) delete(removed accounts.Account) { + ac.mu.Lock() + defer ac.mu.Unlock() + + ac.all = removeAccount(ac.all, removed) + if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { + delete(ac.byAddr, removed.Address) + } else { + ac.byAddr[removed.Address] = ba + } +} + +// deleteByFile removes an account referenced by the given path. +func (ac *accountCache) deleteByFile(path string) { + ac.mu.Lock() + defer ac.mu.Unlock() + i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Path >= path }) + + if i < len(ac.all) && ac.all[i].URL.Path == path { + removed := ac.all[i] + ac.all = append(ac.all[:i], ac.all[i+1:]...) + if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { + delete(ac.byAddr, removed.Address) + } else { + ac.byAddr[removed.Address] = ba + } + } +} + +func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account { + for i := range slice { + if slice[i] == elem { + return append(slice[:i], slice[i+1:]...) + } + } + return slice +} + +// find returns the cached account for address if there is a unique match. +// The exact matching rules are explained by the documentation of accounts.Account. +// Callers must hold ac.mu. +func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) { + // Limit search to address candidates if possible. + matches := ac.all + if (a.Address != common.Address{}) { + matches = ac.byAddr[a.Address] + } + if a.URL.Path != "" { + // If only the basename is specified, complete the path. + if !strings.ContainsRune(a.URL.Path, filepath.Separator) { + a.URL.Path = filepath.Join(ac.keydir, a.URL.Path) + } + for i := range matches { + if matches[i].URL == a.URL { + return matches[i], nil + } + } + if (a.Address == common.Address{}) { + return accounts.Account{}, ErrNoMatch + } + } + switch len(matches) { + case 1: + return matches[0], nil + case 0: + return accounts.Account{}, ErrNoMatch + default: + err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))} + copy(err.Matches, matches) + sort.Sort(accountsByURL(err.Matches)) + return accounts.Account{}, err + } +} + +func (ac *accountCache) maybeReload() { + ac.mu.Lock() + + if ac.watcher.running { + ac.mu.Unlock() + return // A watcher is running and will keep the cache up-to-date. + } + if ac.throttle == nil { + ac.throttle = time.NewTimer(0) + } else { + select { + case <-ac.throttle.C: + default: + ac.mu.Unlock() + return // The cache was reloaded recently. + } + } + // No watcher running, start it. + ac.watcher.start() + ac.throttle.Reset(minReloadInterval) + ac.mu.Unlock() + ac.scanAccounts() +} + +func (ac *accountCache) close() { + ac.mu.Lock() + ac.watcher.close() + if ac.throttle != nil { + ac.throttle.Stop() + } + if ac.notify != nil { + close(ac.notify) + ac.notify = nil + } + ac.mu.Unlock() +} + +// scanAccounts checks if any changes have occurred on the filesystem, and +// updates the account cache accordingly +func (ac *accountCache) scanAccounts() error { + // Scan the entire folder metadata for file changes + creates, deletes, updates, err := ac.fileC.scan(ac.keydir) + if err != nil { + log.Debug("Failed to reload keystore contents", "err", err) + return err + } + if creates.Cardinality() == 0 && deletes.Cardinality() == 0 && updates.Cardinality() == 0 { + return nil + } + // Create a helper method to scan the contents of the key files + var ( + buf = new(bufio.Reader) + key struct { + Address string `json:"address"` + } + ) + readAccount := func(path string) *accounts.Account { + fd, err := os.Open(path) + if err != nil { + log.Trace("Failed to open keystore file", "path", path, "err", err) + return nil + } + defer fd.Close() + buf.Reset(fd) + // Parse the address. + key.Address = "" + err = json.NewDecoder(buf).Decode(&key) + addr := common.HexToAddress(key.Address) + switch { + case err != nil: + log.Debug("Failed to decode keystore key", "path", path, "err", err) + case (addr == common.Address{}): + log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address") + default: + return &accounts.Account{ + Address: addr, + URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}, + } + } + return nil + } + // Process all the file diffs + start := time.Now() + + for _, p := range creates.ToSlice() { + if a := readAccount(p.(string)); a != nil { + ac.add(*a) + } + } + for _, p := range deletes.ToSlice() { + ac.deleteByFile(p.(string)) + } + for _, p := range updates.ToSlice() { + path := p.(string) + ac.deleteByFile(path) + if a := readAccount(path); a != nil { + ac.add(*a) + } + } + end := time.Now() + + select { + case ac.notify <- struct{}{}: + default: + } + log.Trace("Handled keystore changes", "time", end.Sub(start)) + return nil +} diff --git a/accounts/keystore/account_cache_test.go b/accounts/keystore/account_cache_test.go new file mode 100644 index 000000000..fe9233c04 --- /dev/null +++ b/accounts/keystore/account_cache_test.go @@ -0,0 +1,406 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package keystore + +import ( + "fmt" + "io/ioutil" + "math/rand" + "os" + "path/filepath" + "reflect" + "sort" + "testing" + "time" + + "github.com/cespare/cp" + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" +) + +var ( + cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore")) + cachetestAccounts = []accounts.Account{ + { + Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")}, + }, + { + Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")}, + }, + { + Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")}, + }, + } +) + +func TestWatchNewFile(t *testing.T) { + t.Parallel() + + dir, ks := tmpKeyStore(t, false) + defer os.RemoveAll(dir) + + // Ensure the watcher is started before adding any files. + ks.Accounts() + time.Sleep(1000 * time.Millisecond) + + // Move in the files. + wantAccounts := make([]accounts.Account, len(cachetestAccounts)) + for i := range cachetestAccounts { + wantAccounts[i] = accounts.Account{ + Address: cachetestAccounts[i].Address, + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))}, + } + if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil { + t.Fatal(err) + } + } + + // ks should see the accounts. + var list []accounts.Account + for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 { + list = ks.Accounts() + if reflect.DeepEqual(list, wantAccounts) { + // ks should have also received change notifications + select { + case <-ks.changes: + default: + t.Fatalf("wasn't notified of new accounts") + } + return + } + time.Sleep(d) + } + t.Errorf("got %s, want %s", spew.Sdump(list), spew.Sdump(wantAccounts)) +} + +func TestWatchNoDir(t *testing.T) { + t.Parallel() + + // Create ks but not the directory that it watches. + rand.Seed(time.Now().UnixNano()) + dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int())) + ks := NewKeyStore(dir, LightScryptN, LightScryptP) + + list := ks.Accounts() + if len(list) > 0 { + t.Error("initial account list not empty:", list) + } + time.Sleep(100 * time.Millisecond) + + // Create the directory and copy a key file into it. + os.MkdirAll(dir, 0700) + defer os.RemoveAll(dir) + file := filepath.Join(dir, "aaa") + if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { + t.Fatal(err) + } + + // ks should see the account. + wantAccounts := []accounts.Account{cachetestAccounts[0]} + wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { + list = ks.Accounts() + if reflect.DeepEqual(list, wantAccounts) { + // ks should have also received change notifications + select { + case <-ks.changes: + default: + t.Fatalf("wasn't notified of new accounts") + } + return + } + time.Sleep(d) + } + t.Errorf("\ngot %v\nwant %v", list, wantAccounts) +} + +func TestCacheInitialReload(t *testing.T) { + cache, _ := newAccountCache(cachetestDir) + accounts := cache.accounts() + if !reflect.DeepEqual(accounts, cachetestAccounts) { + t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts)) + } +} + +func TestCacheAddDeleteOrder(t *testing.T) { + cache, _ := newAccountCache("testdata/no-such-dir") + cache.watcher.running = true // prevent unexpected reloads + + accs := []accounts.Account{ + { + Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"}, + }, + { + Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"}, + }, + { + Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"}, + }, + { + Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"}, + }, + { + Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"}, + }, + { + Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"}, + }, + { + Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"}, + }, + } + for _, a := range accs { + cache.add(a) + } + // Add some of them twice to check that they don't get reinserted. + cache.add(accs[0]) + cache.add(accs[2]) + + // Check that the account list is sorted by filename. + wantAccounts := make([]accounts.Account, len(accs)) + copy(wantAccounts, accs) + sort.Sort(accountsByURL(wantAccounts)) + list := cache.accounts() + if !reflect.DeepEqual(list, wantAccounts) { + t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts)) + } + for _, a := range accs { + if !cache.hasAddress(a.Address) { + t.Errorf("expected hasAccount(%x) to return true", a.Address) + } + } + if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) { + t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) + } + + // Delete a few keys from the cache. + for i := 0; i < len(accs); i += 2 { + cache.delete(wantAccounts[i]) + } + cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}}) + + // Check content again after deletion. + wantAccountsAfterDelete := []accounts.Account{ + wantAccounts[1], + wantAccounts[3], + wantAccounts[5], + } + list = cache.accounts() + if !reflect.DeepEqual(list, wantAccountsAfterDelete) { + t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete)) + } + for _, a := range wantAccountsAfterDelete { + if !cache.hasAddress(a.Address) { + t.Errorf("expected hasAccount(%x) to return true", a.Address) + } + } + if cache.hasAddress(wantAccounts[0].Address) { + t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address) + } +} + +func TestCacheFind(t *testing.T) { + dir := filepath.Join("testdata", "dir") + cache, _ := newAccountCache(dir) + cache.watcher.running = true // prevent unexpected reloads + + accs := []accounts.Account{ + { + Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")}, + }, + { + Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")}, + }, + { + Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")}, + }, + { + Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")}, + }, + } + for _, a := range accs { + cache.add(a) + } + + nomatchAccount := accounts.Account{ + Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")}, + } + tests := []struct { + Query accounts.Account + WantResult accounts.Account + WantError error + }{ + // by address + {Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]}, + // by file + {Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]}, + // by basename + {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]}, + // by file and address + {Query: accs[0], WantResult: accs[0]}, + // ambiguous address, tie resolved by file + {Query: accs[2], WantResult: accs[2]}, + // ambiguous address error + { + Query: accounts.Account{Address: accs[2].Address}, + WantError: &AmbiguousAddrError{ + Addr: accs[2].Address, + Matches: []accounts.Account{accs[2], accs[3]}, + }, + }, + // no match error + {Query: nomatchAccount, WantError: ErrNoMatch}, + {Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch}, + {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch}, + {Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch}, + } + for i, test := range tests { + a, err := cache.find(test.Query) + if !reflect.DeepEqual(err, test.WantError) { + t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError) + continue + } + if a != test.WantResult { + t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult) + continue + } + } +} + +func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error { + var list []accounts.Account + for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { + list = ks.Accounts() + if reflect.DeepEqual(list, wantAccounts) { + // ks should have also received change notifications + select { + case <-ks.changes: + default: + return fmt.Errorf("wasn't notified of new accounts") + } + return nil + } + time.Sleep(d) + } + return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts) +} + +// TestUpdatedKeyfileContents tests that updating the contents of a keystore file +// is noticed by the watcher, and the account cache is updated accordingly +func TestUpdatedKeyfileContents(t *testing.T) { + t.Parallel() + + // Create a temporary kesytore to test with + rand.Seed(time.Now().UnixNano()) + dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int())) + ks := NewKeyStore(dir, LightScryptN, LightScryptP) + + list := ks.Accounts() + if len(list) > 0 { + t.Error("initial account list not empty:", list) + } + time.Sleep(100 * time.Millisecond) + + // Create the directory and copy a key file into it. + os.MkdirAll(dir, 0700) + defer os.RemoveAll(dir) + file := filepath.Join(dir, "aaa") + + // Place one of our testfiles in there + if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { + t.Fatal(err) + } + + // ks should see the account. + wantAccounts := []accounts.Account{cachetestAccounts[0]} + wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + if err := waitForAccounts(wantAccounts, ks); err != nil { + t.Error(err) + return + } + + // needed so that modTime of `file` is different to its current value after forceCopyFile + time.Sleep(1000 * time.Millisecond) + + // Now replace file contents + if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil { + t.Fatal(err) + return + } + wantAccounts = []accounts.Account{cachetestAccounts[1]} + wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + if err := waitForAccounts(wantAccounts, ks); err != nil { + t.Errorf("First replacement failed") + t.Error(err) + return + } + + // needed so that modTime of `file` is different to its current value after forceCopyFile + time.Sleep(1000 * time.Millisecond) + + // Now replace file contents again + if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil { + t.Fatal(err) + return + } + wantAccounts = []accounts.Account{cachetestAccounts[2]} + wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + if err := waitForAccounts(wantAccounts, ks); err != nil { + t.Errorf("Second replacement failed") + t.Error(err) + return + } + + // needed so that modTime of `file` is different to its current value after ioutil.WriteFile + time.Sleep(1000 * time.Millisecond) + + // Now replace file contents with crap + if err := ioutil.WriteFile(file, []byte("foo"), 0644); err != nil { + t.Fatal(err) + return + } + if err := waitForAccounts([]accounts.Account{}, ks); err != nil { + t.Errorf("Emptying account file failed") + t.Error(err) + return + } +} + +// forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists. +func forceCopyFile(dst, src string) error { + data, err := ioutil.ReadFile(src) + if err != nil { + return err + } + return ioutil.WriteFile(dst, data, 0644) +} diff --git a/accounts/keystore/file_cache.go b/accounts/keystore/file_cache.go new file mode 100644 index 000000000..73ff6ae9e --- /dev/null +++ b/accounts/keystore/file_cache.go @@ -0,0 +1,102 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package keystore + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + "sync" + "time" + + mapset "github.com/deckarep/golang-set" + "github.com/ethereum/go-ethereum/log" +) + +// fileCache is a cache of files seen during scan of keystore. +type fileCache struct { + all mapset.Set // Set of all files from the keystore folder + lastMod time.Time // Last time instance when a file was modified + mu sync.RWMutex +} + +// scan performs a new scan on the given directory, compares against the already +// cached filenames, and returns file sets: creates, deletes, updates. +func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, error) { + t0 := time.Now() + + // List all the failes from the keystore folder + files, err := ioutil.ReadDir(keyDir) + if err != nil { + return nil, nil, nil, err + } + t1 := time.Now() + + fc.mu.Lock() + defer fc.mu.Unlock() + + // Iterate all the files and gather their metadata + all := mapset.NewThreadUnsafeSet() + mods := mapset.NewThreadUnsafeSet() + + var newLastMod time.Time + for _, fi := range files { + path := filepath.Join(keyDir, fi.Name()) + // Skip any non-key files from the folder + if nonKeyFile(fi) { + log.Trace("Ignoring file on account scan", "path", path) + continue + } + // Gather the set of all and fresly modified files + all.Add(path) + + modified := fi.ModTime() + if modified.After(fc.lastMod) { + mods.Add(path) + } + if modified.After(newLastMod) { + newLastMod = modified + } + } + t2 := time.Now() + + // Update the tracked files and return the three sets + deletes := fc.all.Difference(all) // Deletes = previous - current + creates := all.Difference(fc.all) // Creates = current - previous + updates := mods.Difference(creates) // Updates = modified - creates + + fc.all, fc.lastMod = all, newLastMod + t3 := time.Now() + + // Report on the scanning stats and return + log.Debug("FS scan times", "list", t1.Sub(t0), "set", t2.Sub(t1), "diff", t3.Sub(t2)) + return creates, deletes, updates, nil +} + +// nonKeyFile ignores editor backups, hidden files and folders/symlinks. +func nonKeyFile(fi os.FileInfo) bool { + // Skip editor backups and UNIX-style hidden files. + if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") { + return true + } + // Skip misc special files, directories (yes, symlinks too). + if fi.IsDir() || fi.Mode()&os.ModeType != 0 { + return true + } + return false +} diff --git a/accounts/keystore/key.go b/accounts/keystore/key.go new file mode 100644 index 000000000..84d8df0c5 --- /dev/null +++ b/accounts/keystore/key.go @@ -0,0 +1,232 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package keystore + +import ( + "bytes" + "crypto/ecdsa" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/pborman/uuid" +) + +const ( + version = 3 +) + +type Key struct { + Id uuid.UUID // Version 4 "random" for unique id not derived from key data + // to simplify lookups we also store the address + Address common.Address + // we only store privkey as pubkey/address can be derived from it + // privkey in this struct is always in plaintext + PrivateKey *ecdsa.PrivateKey +} + +type keyStore interface { + // Loads and decrypts the key from disk. + GetKey(addr common.Address, filename string, auth string) (*Key, error) + // Writes and encrypts the key. + StoreKey(filename string, k *Key, auth string) error + // Joins filename with the key directory unless it is already absolute. + JoinPath(filename string) string +} + +type plainKeyJSON struct { + Address string `json:"address"` + PrivateKey string `json:"privatekey"` + Id string `json:"id"` + Version int `json:"version"` +} + +type encryptedKeyJSONV3 struct { + Address string `json:"address"` + Crypto CryptoJSON `json:"crypto"` + Id string `json:"id"` + Version int `json:"version"` +} + +type encryptedKeyJSONV1 struct { + Address string `json:"address"` + Crypto CryptoJSON `json:"crypto"` + Id string `json:"id"` + Version string `json:"version"` +} + +type CryptoJSON struct { + Cipher string `json:"cipher"` + CipherText string `json:"ciphertext"` + CipherParams cipherparamsJSON `json:"cipherparams"` + KDF string `json:"kdf"` + KDFParams map[string]interface{} `json:"kdfparams"` + MAC string `json:"mac"` +} + +type cipherparamsJSON struct { + IV string `json:"iv"` +} + +func (k *Key) MarshalJSON() (j []byte, err error) { + jStruct := plainKeyJSON{ + hex.EncodeToString(k.Address[:]), + hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)), + k.Id.String(), + version, + } + j, err = json.Marshal(jStruct) + return j, err +} + +func (k *Key) UnmarshalJSON(j []byte) (err error) { + keyJSON := new(plainKeyJSON) + err = json.Unmarshal(j, &keyJSON) + if err != nil { + return err + } + + u := new(uuid.UUID) + *u = uuid.Parse(keyJSON.Id) + k.Id = *u + addr, err := hex.DecodeString(keyJSON.Address) + if err != nil { + return err + } + privkey, err := crypto.HexToECDSA(keyJSON.PrivateKey) + if err != nil { + return err + } + + k.Address = common.BytesToAddress(addr) + k.PrivateKey = privkey + + return nil +} + +func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { + id := uuid.NewRandom() + key := &Key{ + Id: id, + Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), + PrivateKey: privateKeyECDSA, + } + return key +} + +// NewKeyForDirectICAP generates a key whose address fits into < 155 bits so it can fit +// into the Direct ICAP spec. for simplicity and easier compatibility with other libs, we +// retry until the first byte is 0. +func NewKeyForDirectICAP(rand io.Reader) *Key { + randBytes := make([]byte, 64) + _, err := rand.Read(randBytes) + if err != nil { + panic("key generation: could not read from random source: " + err.Error()) + } + reader := bytes.NewReader(randBytes) + privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), reader) + if err != nil { + panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) + } + key := newKeyFromECDSA(privateKeyECDSA) + if !strings.HasPrefix(key.Address.Hex(), "0x00") { + return NewKeyForDirectICAP(rand) + } + return key +} + +func newKey(rand io.Reader) (*Key, error) { + privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand) + if err != nil { + return nil, err + } + return newKeyFromECDSA(privateKeyECDSA), nil +} + +func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) { + key, err := newKey(rand) + if err != nil { + return nil, accounts.Account{}, err + } + a := accounts.Account{ + Address: key.Address, + URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}, + } + if err := ks.StoreKey(a.URL.Path, key, auth); err != nil { + zeroKey(key.PrivateKey) + return nil, a, err + } + return key, a, err +} + +func writeTemporaryKeyFile(file string, content []byte) (string, error) { + // Create the keystore directory with appropriate permissions + // in case it is not present yet. + const dirPerm = 0700 + if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil { + return "", err + } + // Atomic write: create a temporary hidden file first + // then move it into place. TempFile assigns mode 0600. + f, err := ioutil.TempFile(filepath.Dir(file), "."+filepath.Base(file)+".tmp") + if err != nil { + return "", err + } + if _, err := f.Write(content); err != nil { + f.Close() + os.Remove(f.Name()) + return "", err + } + f.Close() + return f.Name(), nil +} + +func writeKeyFile(file string, content []byte) error { + name, err := writeTemporaryKeyFile(file, content) + if err != nil { + return err + } + return os.Rename(name, file) +} + +// keyFileName implements the naming convention for keyfiles: +// UTC---
+func keyFileName(keyAddr common.Address) string { + ts := time.Now().UTC() + return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:])) +} + +func toISO8601(t time.Time) string { + var tz string + name, offset := t.Zone() + if name == "UTC" { + tz = "Z" + } else { + tz = fmt.Sprintf("%03d00", offset/3600) + } + return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s", + t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz) +} diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go new file mode 100644 index 000000000..434e03504 --- /dev/null +++ b/accounts/keystore/keystore.go @@ -0,0 +1,495 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package keystore implements encrypted storage of secp256k1 private keys. +// +// Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification. +// See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information. +package keystore + +import ( + "crypto/ecdsa" + crand "crypto/rand" + "errors" + "fmt" + "math/big" + "os" + "path/filepath" + "reflect" + "runtime" + "sync" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" +) + +var ( + ErrLocked = accounts.NewAuthNeededError("password or unlock") + ErrNoMatch = errors.New("no key for given address or file") + ErrDecrypt = errors.New("could not decrypt key with given passphrase") +) + +// KeyStoreType is the reflect type of a keystore backend. +var KeyStoreType = reflect.TypeOf(&KeyStore{}) + +// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs. +const KeyStoreScheme = "keystore" + +// Maximum time between wallet refreshes (if filesystem notifications don't work). +const walletRefreshCycle = 3 * time.Second + +// KeyStore manages a key storage directory on disk. +type KeyStore struct { + storage keyStore // Storage backend, might be cleartext or encrypted + cache *accountCache // In-memory account cache over the filesystem storage + changes chan struct{} // Channel receiving change notifications from the cache + unlocked map[common.Address]*unlocked // Currently unlocked account (decrypted private keys) + + wallets []accounts.Wallet // Wallet wrappers around the individual key files + updateFeed event.Feed // Event feed to notify wallet additions/removals + updateScope event.SubscriptionScope // Subscription scope tracking current live listeners + updating bool // Whether the event notification loop is running + + mu sync.RWMutex +} + +type unlocked struct { + *Key + abort chan struct{} +} + +// NewKeyStore creates a keystore for the given directory. +func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore { + keydir, _ = filepath.Abs(keydir) + ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}} + ks.init(keydir) + return ks +} + +// NewPlaintextKeyStore creates a keystore for the given directory. +// Deprecated: Use NewKeyStore. +func NewPlaintextKeyStore(keydir string) *KeyStore { + keydir, _ = filepath.Abs(keydir) + ks := &KeyStore{storage: &keyStorePlain{keydir}} + ks.init(keydir) + return ks +} + +func (ks *KeyStore) init(keydir string) { + // Lock the mutex since the account cache might call back with events + ks.mu.Lock() + defer ks.mu.Unlock() + + // Initialize the set of unlocked keys and the account cache + ks.unlocked = make(map[common.Address]*unlocked) + ks.cache, ks.changes = newAccountCache(keydir) + + // TODO: In order for this finalizer to work, there must be no references + // to ks. addressCache doesn't keep a reference but unlocked keys do, + // so the finalizer will not trigger until all timed unlocks have expired. + runtime.SetFinalizer(ks, func(m *KeyStore) { + m.cache.close() + }) + // Create the initial list of wallets from the cache + accs := ks.cache.accounts() + ks.wallets = make([]accounts.Wallet, len(accs)) + for i := 0; i < len(accs); i++ { + ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks} + } +} + +// Wallets implements accounts.Backend, returning all single-key wallets from the +// keystore directory. +func (ks *KeyStore) Wallets() []accounts.Wallet { + // Make sure the list of wallets is in sync with the account cache + ks.refreshWallets() + + ks.mu.RLock() + defer ks.mu.RUnlock() + + cpy := make([]accounts.Wallet, len(ks.wallets)) + copy(cpy, ks.wallets) + return cpy +} + +// refreshWallets retrieves the current account list and based on that does any +// necessary wallet refreshes. +func (ks *KeyStore) refreshWallets() { + // Retrieve the current list of accounts + ks.mu.Lock() + accs := ks.cache.accounts() + + // Transform the current list of wallets into the new one + var ( + wallets = make([]accounts.Wallet, 0, len(accs)) + events []accounts.WalletEvent + ) + + for _, account := range accs { + // Drop wallets while they were in front of the next account + for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 { + events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Kind: accounts.WalletDropped}) + ks.wallets = ks.wallets[1:] + } + // If there are no more wallets or the account is before the next, wrap new wallet + if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 { + wallet := &keystoreWallet{account: account, keystore: ks} + + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) + wallets = append(wallets, wallet) + continue + } + // If the account is the same as the first wallet, keep it + if ks.wallets[0].Accounts()[0] == account { + wallets = append(wallets, ks.wallets[0]) + ks.wallets = ks.wallets[1:] + continue + } + } + // Drop any leftover wallets and set the new batch + for _, wallet := range ks.wallets { + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) + } + ks.wallets = wallets + ks.mu.Unlock() + + // Fire all wallet events and return + for _, event := range events { + ks.updateFeed.Send(event) + } +} + +// Subscribe implements accounts.Backend, creating an async subscription to +// receive notifications on the addition or removal of keystore wallets. +func (ks *KeyStore) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { + // We need the mutex to reliably start/stop the update loop + ks.mu.Lock() + defer ks.mu.Unlock() + + // Subscribe the caller and track the subscriber count + sub := ks.updateScope.Track(ks.updateFeed.Subscribe(sink)) + + // Subscribers require an active notification loop, start it + if !ks.updating { + ks.updating = true + go ks.updater() + } + return sub +} + +// updater is responsible for maintaining an up-to-date list of wallets stored in +// the keystore, and for firing wallet addition/removal events. It listens for +// account change events from the underlying account cache, and also periodically +// forces a manual refresh (only triggers for systems where the filesystem notifier +// is not running). +func (ks *KeyStore) updater() { + for { + // Wait for an account update or a refresh timeout + select { + case <-ks.changes: + case <-time.After(walletRefreshCycle): + } + // Run the wallet refresher + ks.refreshWallets() + + // If all our subscribers left, stop the updater + ks.mu.Lock() + if ks.updateScope.Count() == 0 { + ks.updating = false + ks.mu.Unlock() + return + } + ks.mu.Unlock() + } +} + +// HasAddress reports whether a key with the given address is present. +func (ks *KeyStore) HasAddress(addr common.Address) bool { + return ks.cache.hasAddress(addr) +} + +// Accounts returns all key files present in the directory. +func (ks *KeyStore) Accounts() []accounts.Account { + return ks.cache.accounts() +} + +// Delete deletes the key matched by account if the passphrase is correct. +// If the account contains no filename, the address must match a unique key. +func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error { + // Decrypting the key isn't really necessary, but we do + // it anyway to check the password and zero out the key + // immediately afterwards. + a, key, err := ks.getDecryptedKey(a, passphrase) + if key != nil { + zeroKey(key.PrivateKey) + } + if err != nil { + return err + } + // The order is crucial here. The key is dropped from the + // cache after the file is gone so that a reload happening in + // between won't insert it into the cache again. + err = os.Remove(a.URL.Path) + if err == nil { + ks.cache.delete(a) + ks.refreshWallets() + } + return err +} + +// SignHash calculates a ECDSA signature for the given hash. The produced +// signature is in the [R || S || V] format where V is 0 or 1. +func (ks *KeyStore) SignHash(a accounts.Account, hash []byte) ([]byte, error) { + // Look up the key to sign with and abort if it cannot be found + ks.mu.RLock() + defer ks.mu.RUnlock() + + unlockedKey, found := ks.unlocked[a.Address] + if !found { + return nil, ErrLocked + } + // Sign the hash using plain ECDSA operations + return crypto.Sign(hash, unlockedKey.PrivateKey) +} + +// SignTx signs the given transaction with the requested account. +func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + // Look up the key to sign with and abort if it cannot be found + ks.mu.RLock() + defer ks.mu.RUnlock() + + unlockedKey, found := ks.unlocked[a.Address] + if !found { + return nil, ErrLocked + } + // Depending on the presence of the chain ID, sign with EIP155 or homestead + if chainID != nil { + return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey) + } + return types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey) +} + +// SignHashWithPassphrase signs hash if the private key matching the given address +// can be decrypted with the given passphrase. The produced signature is in the +// [R || S || V] format where V is 0 or 1. +func (ks *KeyStore) SignHashWithPassphrase(a accounts.Account, passphrase string, hash []byte) (signature []byte, err error) { + _, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return nil, err + } + defer zeroKey(key.PrivateKey) + return crypto.Sign(hash, key.PrivateKey) +} + +// SignTxWithPassphrase signs the transaction if the private key matching the +// given address can be decrypted with the given passphrase. +func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + _, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return nil, err + } + defer zeroKey(key.PrivateKey) + + // Depending on the presence of the chain ID, sign with EIP155 or homestead + if chainID != nil { + return types.SignTx(tx, types.NewEIP155Signer(chainID), key.PrivateKey) + } + return types.SignTx(tx, types.HomesteadSigner{}, key.PrivateKey) +} + +// Unlock unlocks the given account indefinitely. +func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error { + return ks.TimedUnlock(a, passphrase, 0) +} + +// Lock removes the private key with the given address from memory. +func (ks *KeyStore) Lock(addr common.Address) error { + ks.mu.Lock() + if unl, found := ks.unlocked[addr]; found { + ks.mu.Unlock() + ks.expire(addr, unl, time.Duration(0)*time.Nanosecond) + } else { + ks.mu.Unlock() + } + return nil +} + +// TimedUnlock unlocks the given account with the passphrase. The account +// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account +// until the program exits. The account must match a unique key file. +// +// If the account address is already unlocked for a duration, TimedUnlock extends or +// shortens the active unlock timeout. If the address was previously unlocked +// indefinitely the timeout is not altered. +func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error { + a, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return err + } + + ks.mu.Lock() + defer ks.mu.Unlock() + u, found := ks.unlocked[a.Address] + if found { + if u.abort == nil { + // The address was unlocked indefinitely, so unlocking + // it with a timeout would be confusing. + zeroKey(key.PrivateKey) + return nil + } + // Terminate the expire goroutine and replace it below. + close(u.abort) + } + if timeout > 0 { + u = &unlocked{Key: key, abort: make(chan struct{})} + go ks.expire(a.Address, u, timeout) + } else { + u = &unlocked{Key: key} + } + ks.unlocked[a.Address] = u + return nil +} + +// Find resolves the given account into a unique entry in the keystore. +func (ks *KeyStore) Find(a accounts.Account) (accounts.Account, error) { + ks.cache.maybeReload() + ks.cache.mu.Lock() + a, err := ks.cache.find(a) + ks.cache.mu.Unlock() + return a, err +} + +func (ks *KeyStore) getDecryptedKey(a accounts.Account, auth string) (accounts.Account, *Key, error) { + a, err := ks.Find(a) + if err != nil { + return a, nil, err + } + key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth) + return a, key, err +} + +func (ks *KeyStore) expire(addr common.Address, u *unlocked, timeout time.Duration) { + t := time.NewTimer(timeout) + defer t.Stop() + select { + case <-u.abort: + // just quit + case <-t.C: + ks.mu.Lock() + // only drop if it's still the same key instance that dropLater + // was launched with. we can check that using pointer equality + // because the map stores a new pointer every time the key is + // unlocked. + if ks.unlocked[addr] == u { + zeroKey(u.PrivateKey) + delete(ks.unlocked, addr) + } + ks.mu.Unlock() + } +} + +// NewAccount generates a new key and stores it into the key directory, +// encrypting it with the passphrase. +func (ks *KeyStore) NewAccount(passphrase string) (accounts.Account, error) { + _, account, err := storeNewKey(ks.storage, crand.Reader, passphrase) + if err != nil { + return accounts.Account{}, err + } + // Add the account to the cache immediately rather + // than waiting for file system notifications to pick it up. + ks.cache.add(account) + ks.refreshWallets() + return account, nil +} + +// Export exports as a JSON key, encrypted with newPassphrase. +func (ks *KeyStore) Export(a accounts.Account, passphrase, newPassphrase string) (keyJSON []byte, err error) { + _, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return nil, err + } + var N, P int + if store, ok := ks.storage.(*keyStorePassphrase); ok { + N, P = store.scryptN, store.scryptP + } else { + N, P = StandardScryptN, StandardScryptP + } + return EncryptKey(key, newPassphrase, N, P) +} + +// Import stores the given encrypted JSON key into the key directory. +func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error) { + key, err := DecryptKey(keyJSON, passphrase) + if key != nil && key.PrivateKey != nil { + defer zeroKey(key.PrivateKey) + } + if err != nil { + return accounts.Account{}, err + } + return ks.importKey(key, newPassphrase) +} + +// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase. +func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) { + key := newKeyFromECDSA(priv) + if ks.cache.hasAddress(key.Address) { + return accounts.Account{}, fmt.Errorf("account already exists") + } + return ks.importKey(key, passphrase) +} + +func (ks *KeyStore) importKey(key *Key, passphrase string) (accounts.Account, error) { + a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.storage.JoinPath(keyFileName(key.Address))}} + if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil { + return accounts.Account{}, err + } + ks.cache.add(a) + ks.refreshWallets() + return a, nil +} + +// Update changes the passphrase of an existing account. +func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string) error { + a, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return err + } + return ks.storage.StoreKey(a.URL.Path, key, newPassphrase) +} + +// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores +// a key file in the key directory. The key file is encrypted with the same passphrase. +func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (accounts.Account, error) { + a, _, err := importPreSaleKey(ks.storage, keyJSON, passphrase) + if err != nil { + return a, err + } + ks.cache.add(a) + ks.refreshWallets() + return a, nil +} + +// zeroKey zeroes a private key in memory. +func zeroKey(k *ecdsa.PrivateKey) { + b := k.D.Bits() + for i := range b { + b[i] = 0 + } +} diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/keystore_test.go new file mode 100644 index 000000000..6fb0a7808 --- /dev/null +++ b/accounts/keystore/keystore_test.go @@ -0,0 +1,387 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package keystore + +import ( + "io/ioutil" + "math/rand" + "os" + "runtime" + "sort" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/event" +) + +var testSigData = make([]byte, 32) + +func TestKeyStore(t *testing.T) { + dir, ks := tmpKeyStore(t, true) + defer os.RemoveAll(dir) + + a, err := ks.NewAccount("foo") + if err != nil { + t.Fatal(err) + } + if !strings.HasPrefix(a.URL.Path, dir) { + t.Errorf("account file %s doesn't have dir prefix", a.URL) + } + stat, err := os.Stat(a.URL.Path) + if err != nil { + t.Fatalf("account file %s doesn't exist (%v)", a.URL, err) + } + if runtime.GOOS != "windows" && stat.Mode() != 0600 { + t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600) + } + if !ks.HasAddress(a.Address) { + t.Errorf("HasAccount(%x) should've returned true", a.Address) + } + if err := ks.Update(a, "foo", "bar"); err != nil { + t.Errorf("Update error: %v", err) + } + if err := ks.Delete(a, "bar"); err != nil { + t.Errorf("Delete error: %v", err) + } + if common.FileExist(a.URL.Path) { + t.Errorf("account file %s should be gone after Delete", a.URL) + } + if ks.HasAddress(a.Address) { + t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address) + } +} + +func TestSign(t *testing.T) { + dir, ks := tmpKeyStore(t, true) + defer os.RemoveAll(dir) + + pass := "" // not used but required by API + a1, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + if err := ks.Unlock(a1, ""); err != nil { + t.Fatal(err) + } + if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil { + t.Fatal(err) + } +} + +func TestSignWithPassphrase(t *testing.T) { + dir, ks := tmpKeyStore(t, true) + defer os.RemoveAll(dir) + + pass := "passwd" + acc, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + if _, unlocked := ks.unlocked[acc.Address]; unlocked { + t.Fatal("expected account to be locked") + } + + _, err = ks.SignHashWithPassphrase(acc, pass, testSigData) + if err != nil { + t.Fatal(err) + } + + if _, unlocked := ks.unlocked[acc.Address]; unlocked { + t.Fatal("expected account to be locked") + } + + if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil { + t.Fatal("expected SignHashWithPassphrase to fail with invalid password") + } +} + +func TestTimedUnlock(t *testing.T) { + dir, ks := tmpKeyStore(t, true) + defer os.RemoveAll(dir) + + pass := "foo" + a1, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + // Signing without passphrase fails because account is locked + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != ErrLocked { + t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err) + } + + // Signing with passphrase works + if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { + t.Fatal(err) + } + + // Signing without passphrase works because account is temp unlocked + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != nil { + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) + } + + // Signing fails again after automatic locking + time.Sleep(250 * time.Millisecond) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != ErrLocked { + t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) + } +} + +func TestOverrideUnlock(t *testing.T) { + dir, ks := tmpKeyStore(t, false) + defer os.RemoveAll(dir) + + pass := "foo" + a1, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + // Unlock indefinitely. + if err = ks.TimedUnlock(a1, pass, 5*time.Minute); err != nil { + t.Fatal(err) + } + + // Signing without passphrase works because account is temp unlocked + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != nil { + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) + } + + // reset unlock to a shorter period, invalidates the previous unlock + if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { + t.Fatal(err) + } + + // Signing without passphrase still works because account is temp unlocked + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != nil { + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) + } + + // Signing fails again after automatic locking + time.Sleep(250 * time.Millisecond) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != ErrLocked { + t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) + } +} + +// This test should fail under -race if signing races the expiration goroutine. +func TestSignRace(t *testing.T) { + dir, ks := tmpKeyStore(t, false) + defer os.RemoveAll(dir) + + // Create a test account. + a1, err := ks.NewAccount("") + if err != nil { + t.Fatal("could not create the test account", err) + } + + if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil { + t.Fatal("could not unlock the test account", err) + } + end := time.Now().Add(500 * time.Millisecond) + for time.Now().Before(end) { + if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrLocked { + return + } else if err != nil { + t.Errorf("Sign error: %v", err) + return + } + time.Sleep(1 * time.Millisecond) + } + t.Errorf("Account did not lock within the timeout") +} + +// Tests that the wallet notifier loop starts and stops correctly based on the +// addition and removal of wallet event subscriptions. +func TestWalletNotifierLifecycle(t *testing.T) { + // Create a temporary kesytore to test with + dir, ks := tmpKeyStore(t, false) + defer os.RemoveAll(dir) + + // Ensure that the notification updater is not running yet + time.Sleep(250 * time.Millisecond) + ks.mu.RLock() + updating := ks.updating + ks.mu.RUnlock() + + if updating { + t.Errorf("wallet notifier running without subscribers") + } + // Subscribe to the wallet feed and ensure the updater boots up + updates := make(chan accounts.WalletEvent) + + subs := make([]event.Subscription, 2) + for i := 0; i < len(subs); i++ { + // Create a new subscription + subs[i] = ks.Subscribe(updates) + + // Ensure the notifier comes online + time.Sleep(250 * time.Millisecond) + ks.mu.RLock() + updating = ks.updating + ks.mu.RUnlock() + + if !updating { + t.Errorf("sub %d: wallet notifier not running after subscription", i) + } + } + // Unsubscribe and ensure the updater terminates eventually + for i := 0; i < len(subs); i++ { + // Close an existing subscription + subs[i].Unsubscribe() + + // Ensure the notifier shuts down at and only at the last close + for k := 0; k < int(walletRefreshCycle/(250*time.Millisecond))+2; k++ { + ks.mu.RLock() + updating = ks.updating + ks.mu.RUnlock() + + if i < len(subs)-1 && !updating { + t.Fatalf("sub %d: event notifier stopped prematurely", i) + } + if i == len(subs)-1 && !updating { + return + } + time.Sleep(250 * time.Millisecond) + } + } + t.Errorf("wallet notifier didn't terminate after unsubscribe") +} + +type walletEvent struct { + accounts.WalletEvent + a accounts.Account +} + +// Tests that wallet notifications and correctly fired when accounts are added +// or deleted from the keystore. +func TestWalletNotifications(t *testing.T) { + dir, ks := tmpKeyStore(t, false) + defer os.RemoveAll(dir) + + // Subscribe to the wallet feed and collect events. + var ( + events []walletEvent + updates = make(chan accounts.WalletEvent) + sub = ks.Subscribe(updates) + ) + defer sub.Unsubscribe() + go func() { + for { + select { + case ev := <-updates: + events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]}) + case <-sub.Err(): + close(updates) + return + } + } + }() + + // Randomly add and remove accounts. + var ( + live = make(map[common.Address]accounts.Account) + wantEvents []walletEvent + ) + for i := 0; i < 1024; i++ { + if create := len(live) == 0 || rand.Int()%4 > 0; create { + // Add a new account and ensure wallet notifications arrives + account, err := ks.NewAccount("") + if err != nil { + t.Fatalf("failed to create test account: %v", err) + } + live[account.Address] = account + wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletArrived}, account}) + } else { + // Delete a random account. + var account accounts.Account + for _, a := range live { + account = a + break + } + if err := ks.Delete(account, ""); err != nil { + t.Fatalf("failed to delete test account: %v", err) + } + delete(live, account.Address) + wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletDropped}, account}) + } + } + + // Shut down the event collector and check events. + sub.Unsubscribe() + <-updates + checkAccounts(t, live, ks.Wallets()) + checkEvents(t, wantEvents, events) +} + +// checkAccounts checks that all known live accounts are present in the wallet list. +func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, wallets []accounts.Wallet) { + if len(live) != len(wallets) { + t.Errorf("wallet list doesn't match required accounts: have %d, want %d", len(wallets), len(live)) + return + } + liveList := make([]accounts.Account, 0, len(live)) + for _, account := range live { + liveList = append(liveList, account) + } + sort.Sort(accountsByURL(liveList)) + for j, wallet := range wallets { + if accs := wallet.Accounts(); len(accs) != 1 { + t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs)) + } else if accs[0] != liveList[j] { + t.Errorf("wallet %d: account mismatch: have %v, want %v", j, accs[0], liveList[j]) + } + } +} + +// checkEvents checks that all events in 'want' are present in 'have'. Events may be present multiple times. +func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) { + for _, wantEv := range want { + nmatch := 0 + for ; len(have) > 0; nmatch++ { + if have[0].Kind != wantEv.Kind || have[0].a != wantEv.a { + break + } + have = have[1:] + } + if nmatch == 0 { + t.Fatalf("can't find event with Kind=%v for %x", wantEv.Kind, wantEv.a.Address) + } + } +} + +func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) { + d, err := ioutil.TempDir("", "eth-keystore-test") + if err != nil { + t.Fatal(err) + } + new := NewPlaintextKeyStore + if encrypted { + new = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) } + } + return d, new(d) +} diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go new file mode 100644 index 000000000..1ced41e99 --- /dev/null +++ b/accounts/keystore/passphrase.go @@ -0,0 +1,356 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +/* + +This key store behaves as KeyStorePlain with the difference that +the private key is encrypted and on disk uses another JSON encoding. + +The crypto is documented at https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition + +*/ + +package keystore + +import ( + "bytes" + "crypto/aes" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" + "github.com/pborman/uuid" + "golang.org/x/crypto/pbkdf2" + "golang.org/x/crypto/scrypt" +) + +const ( + keyHeaderKDF = "scrypt" + + // StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB + // memory and taking approximately 1s CPU time on a modern processor. + StandardScryptN = 1 << 18 + + // StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB + // memory and taking approximately 1s CPU time on a modern processor. + StandardScryptP = 1 + + // LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB + // memory and taking approximately 100ms CPU time on a modern processor. + LightScryptN = 1 << 12 + + // LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB + // memory and taking approximately 100ms CPU time on a modern processor. + LightScryptP = 6 + + scryptR = 8 + scryptDKLen = 32 +) + +type keyStorePassphrase struct { + keysDirPath string + scryptN int + scryptP int + // skipKeyFileVerification disables the security-feature which does + // reads and decrypts any newly created keyfiles. This should be 'false' in all + // cases except tests -- setting this to 'true' is not recommended. + skipKeyFileVerification bool +} + +func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) { + // Load the key from the keystore and decrypt its contents + keyjson, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + key, err := DecryptKey(keyjson, auth) + if err != nil { + return nil, err + } + // Make sure we're really operating on the requested key (no swap attacks) + if key.Address != addr { + return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, addr) + } + return key, nil +} + +// StoreKey generates a key, encrypts with 'auth' and stores in the given directory +func StoreKey(dir, auth string, scryptN, scryptP int) (accounts.Account, error) { + _, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP, false}, rand.Reader, auth) + return a, err +} + +func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error { + keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP) + if err != nil { + return err + } + // Write into temporary file + tmpName, err := writeTemporaryKeyFile(filename, keyjson) + if err != nil { + return err + } + if !ks.skipKeyFileVerification { + // Verify that we can decrypt the file with the given password. + _, err = ks.GetKey(key.Address, tmpName, auth) + if err != nil { + msg := "An error was encountered when saving and verifying the keystore file. \n" + + "This indicates that the keystore is corrupted. \n" + + "The corrupted file is stored at \n%v\n" + + "Please file a ticket at:\n\n" + + "https://github.com/ethereum/go-ethereum/issues." + + "The error was : %s" + return fmt.Errorf(msg, tmpName, err) + } + } + return os.Rename(tmpName, filename) +} + +func (ks keyStorePassphrase) JoinPath(filename string) string { + if filepath.IsAbs(filename) { + return filename + } + return filepath.Join(ks.keysDirPath, filename) +} + +// Encryptdata encrypts the data given as 'data' with the password 'auth'. +func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) { + + salt := make([]byte, 32) + if _, err := io.ReadFull(rand.Reader, salt); err != nil { + panic("reading from crypto/rand failed: " + err.Error()) + } + derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen) + if err != nil { + return CryptoJSON{}, err + } + encryptKey := derivedKey[:16] + + iv := make([]byte, aes.BlockSize) // 16 + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + panic("reading from crypto/rand failed: " + err.Error()) + } + cipherText, err := aesCTRXOR(encryptKey, data, iv) + if err != nil { + return CryptoJSON{}, err + } + mac := crypto.Keccak256(derivedKey[16:32], cipherText) + + scryptParamsJSON := make(map[string]interface{}, 5) + scryptParamsJSON["n"] = scryptN + scryptParamsJSON["r"] = scryptR + scryptParamsJSON["p"] = scryptP + scryptParamsJSON["dklen"] = scryptDKLen + scryptParamsJSON["salt"] = hex.EncodeToString(salt) + cipherParamsJSON := cipherparamsJSON{ + IV: hex.EncodeToString(iv), + } + + cryptoStruct := CryptoJSON{ + Cipher: "aes-128-ctr", + CipherText: hex.EncodeToString(cipherText), + CipherParams: cipherParamsJSON, + KDF: keyHeaderKDF, + KDFParams: scryptParamsJSON, + MAC: hex.EncodeToString(mac), + } + return cryptoStruct, nil +} + +// EncryptKey encrypts a key using the specified scrypt parameters into a json +// blob that can be decrypted later on. +func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) { + keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32) + cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP) + if err != nil { + return nil, err + } + encryptedKeyJSONV3 := encryptedKeyJSONV3{ + hex.EncodeToString(key.Address[:]), + cryptoStruct, + key.Id.String(), + version, + } + return json.Marshal(encryptedKeyJSONV3) +} + +// DecryptKey decrypts a key from a json blob, returning the private key itself. +func DecryptKey(keyjson []byte, auth string) (*Key, error) { + // Parse the json into a simple map to fetch the key version + m := make(map[string]interface{}) + if err := json.Unmarshal(keyjson, &m); err != nil { + return nil, err + } + // Depending on the version try to parse one way or another + var ( + keyBytes, keyId []byte + err error + ) + if version, ok := m["version"].(string); ok && version == "1" { + k := new(encryptedKeyJSONV1) + if err := json.Unmarshal(keyjson, k); err != nil { + return nil, err + } + keyBytes, keyId, err = decryptKeyV1(k, auth) + } else { + k := new(encryptedKeyJSONV3) + if err := json.Unmarshal(keyjson, k); err != nil { + return nil, err + } + keyBytes, keyId, err = decryptKeyV3(k, auth) + } + // Handle any decryption errors and return the key + if err != nil { + return nil, err + } + key := crypto.ToECDSAUnsafe(keyBytes) + + return &Key{ + Id: uuid.UUID(keyId), + Address: crypto.PubkeyToAddress(key.PublicKey), + PrivateKey: key, + }, nil +} + +func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) { + if cryptoJson.Cipher != "aes-128-ctr" { + return nil, fmt.Errorf("Cipher not supported: %v", cryptoJson.Cipher) + } + mac, err := hex.DecodeString(cryptoJson.MAC) + if err != nil { + return nil, err + } + + iv, err := hex.DecodeString(cryptoJson.CipherParams.IV) + if err != nil { + return nil, err + } + + cipherText, err := hex.DecodeString(cryptoJson.CipherText) + if err != nil { + return nil, err + } + + derivedKey, err := getKDFKey(cryptoJson, auth) + if err != nil { + return nil, err + } + + calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText) + if !bytes.Equal(calculatedMAC, mac) { + return nil, ErrDecrypt + } + + plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv) + if err != nil { + return nil, err + } + return plainText, err +} + +func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) { + if keyProtected.Version != version { + return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version) + } + keyId = uuid.Parse(keyProtected.Id) + plainText, err := DecryptDataV3(keyProtected.Crypto, auth) + if err != nil { + return nil, nil, err + } + return plainText, keyId, err +} + +func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) { + keyId = uuid.Parse(keyProtected.Id) + mac, err := hex.DecodeString(keyProtected.Crypto.MAC) + if err != nil { + return nil, nil, err + } + + iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV) + if err != nil { + return nil, nil, err + } + + cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText) + if err != nil { + return nil, nil, err + } + + derivedKey, err := getKDFKey(keyProtected.Crypto, auth) + if err != nil { + return nil, nil, err + } + + calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText) + if !bytes.Equal(calculatedMAC, mac) { + return nil, nil, ErrDecrypt + } + + plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv) + if err != nil { + return nil, nil, err + } + return plainText, keyId, err +} + +func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) { + authArray := []byte(auth) + salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string)) + if err != nil { + return nil, err + } + dkLen := ensureInt(cryptoJSON.KDFParams["dklen"]) + + if cryptoJSON.KDF == keyHeaderKDF { + n := ensureInt(cryptoJSON.KDFParams["n"]) + r := ensureInt(cryptoJSON.KDFParams["r"]) + p := ensureInt(cryptoJSON.KDFParams["p"]) + return scrypt.Key(authArray, salt, n, r, p, dkLen) + + } else if cryptoJSON.KDF == "pbkdf2" { + c := ensureInt(cryptoJSON.KDFParams["c"]) + prf := cryptoJSON.KDFParams["prf"].(string) + if prf != "hmac-sha256" { + return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf) + } + key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New) + return key, nil + } + + return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF) +} + +// TODO: can we do without this when unmarshalling dynamic JSON? +// why do integers in KDF params end up as float64 and not int after +// unmarshal? +func ensureInt(x interface{}) int { + res, ok := x.(int) + if !ok { + res = int(x.(float64)) + } + return res +} diff --git a/accounts/keystore/passphrase_test.go b/accounts/keystore/passphrase_test.go new file mode 100644 index 000000000..630682ceb --- /dev/null +++ b/accounts/keystore/passphrase_test.go @@ -0,0 +1,60 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package keystore + +import ( + "io/ioutil" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +const ( + veryLightScryptN = 2 + veryLightScryptP = 1 +) + +// Tests that a json key file can be decrypted and encrypted in multiple rounds. +func TestKeyEncryptDecrypt(t *testing.T) { + keyjson, err := ioutil.ReadFile("testdata/very-light-scrypt.json") + if err != nil { + t.Fatal(err) + } + password := "" + address := common.HexToAddress("45dea0fb0bba44f4fcf290bba71fd57d7117cbb8") + + // Do a few rounds of decryption and encryption + for i := 0; i < 3; i++ { + // Try a bad password first + if _, err := DecryptKey(keyjson, password+"bad"); err == nil { + t.Errorf("test %d: json key decrypted with bad password", i) + } + // Decrypt with the correct password + key, err := DecryptKey(keyjson, password) + if err != nil { + t.Fatalf("test %d: json key failed to decrypt: %v", i, err) + } + if key.Address != address { + t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address) + } + // Recrypt with a new password and start over + password += "new data appended" + if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil { + t.Errorf("test %d: failed to recrypt key %v", i, err) + } + } +} diff --git a/accounts/keystore/plain.go b/accounts/keystore/plain.go new file mode 100644 index 000000000..f62a133ce --- /dev/null +++ b/accounts/keystore/plain.go @@ -0,0 +1,61 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package keystore + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" +) + +type keyStorePlain struct { + keysDirPath string +} + +func (ks keyStorePlain) GetKey(addr common.Address, filename, auth string) (*Key, error) { + fd, err := os.Open(filename) + if err != nil { + return nil, err + } + defer fd.Close() + key := new(Key) + if err := json.NewDecoder(fd).Decode(key); err != nil { + return nil, err + } + if key.Address != addr { + return nil, fmt.Errorf("key content mismatch: have address %x, want %x", key.Address, addr) + } + return key, nil +} + +func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error { + content, err := json.Marshal(key) + if err != nil { + return err + } + return writeKeyFile(filename, content) +} + +func (ks keyStorePlain) JoinPath(filename string) string { + if filepath.IsAbs(filename) { + return filename + } + return filepath.Join(ks.keysDirPath, filename) +} diff --git a/accounts/keystore/plain_test.go b/accounts/keystore/plain_test.go new file mode 100644 index 000000000..32852a0ad --- /dev/null +++ b/accounts/keystore/plain_test.go @@ -0,0 +1,266 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package keystore + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) { + d, err := ioutil.TempDir("", "geth-keystore-test") + if err != nil { + t.Fatal(err) + } + if encrypted { + ks = &keyStorePassphrase{d, veryLightScryptN, veryLightScryptP, true} + } else { + ks = &keyStorePlain{d} + } + return d, ks +} + +func TestKeyStorePlain(t *testing.T) { + dir, ks := tmpKeyStoreIface(t, false) + defer os.RemoveAll(dir) + + pass := "" // not used but required by API + k1, account, err := storeNewKey(ks, rand.Reader, pass) + if err != nil { + t.Fatal(err) + } + k2, err := ks.GetKey(k1.Address, account.URL.Path, pass) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(k1.Address, k2.Address) { + t.Fatal(err) + } + if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) { + t.Fatal(err) + } +} + +func TestKeyStorePassphrase(t *testing.T) { + dir, ks := tmpKeyStoreIface(t, true) + defer os.RemoveAll(dir) + + pass := "foo" + k1, account, err := storeNewKey(ks, rand.Reader, pass) + if err != nil { + t.Fatal(err) + } + k2, err := ks.GetKey(k1.Address, account.URL.Path, pass) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(k1.Address, k2.Address) { + t.Fatal(err) + } + if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) { + t.Fatal(err) + } +} + +func TestKeyStorePassphraseDecryptionFail(t *testing.T) { + dir, ks := tmpKeyStoreIface(t, true) + defer os.RemoveAll(dir) + + pass := "foo" + k1, account, err := storeNewKey(ks, rand.Reader, pass) + if err != nil { + t.Fatal(err) + } + if _, err = ks.GetKey(k1.Address, account.URL.Path, "bar"); err != ErrDecrypt { + t.Fatalf("wrong error for invalid passphrase\ngot %q\nwant %q", err, ErrDecrypt) + } +} + +func TestImportPreSaleKey(t *testing.T) { + dir, ks := tmpKeyStoreIface(t, true) + defer os.RemoveAll(dir) + + // file content of a presale key file generated with: + // python pyethsaletool.py genwallet + // with password "foo" + fileContent := "{\"encseed\": \"26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba\", \"ethaddr\": \"d4584b5f6229b7be90727b0fc8c6b91bb427821f\", \"email\": \"gustav.simonsson@gmail.com\", \"btcaddr\": \"1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx\"}" + pass := "foo" + account, _, err := importPreSaleKey(ks, []byte(fileContent), pass) + if err != nil { + t.Fatal(err) + } + if account.Address != common.HexToAddress("d4584b5f6229b7be90727b0fc8c6b91bb427821f") { + t.Errorf("imported account has wrong address %x", account.Address) + } + if !strings.HasPrefix(account.URL.Path, dir) { + t.Errorf("imported account file not in keystore directory: %q", account.URL) + } +} + +// Test and utils for the key store tests in the Ethereum JSON tests; +// testdataKeyStoreTests/basic_tests.json +type KeyStoreTestV3 struct { + Json encryptedKeyJSONV3 + Password string + Priv string +} + +type KeyStoreTestV1 struct { + Json encryptedKeyJSONV1 + Password string + Priv string +} + +func TestV3_PBKDF2_1(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) + testDecryptV3(tests["wikipage_test_vector_pbkdf2"], t) +} + +var testsSubmodule = filepath.Join("..", "..", "tests", "testdata", "KeyStoreTests") + +func skipIfSubmoduleMissing(t *testing.T) { + if !common.FileExist(testsSubmodule) { + t.Skipf("can't find JSON tests from submodule at %s", testsSubmodule) + } +} + +func TestV3_PBKDF2_2(t *testing.T) { + skipIfSubmoduleMissing(t) + t.Parallel() + tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t) + testDecryptV3(tests["test1"], t) +} + +func TestV3_PBKDF2_3(t *testing.T) { + skipIfSubmoduleMissing(t) + t.Parallel() + tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t) + testDecryptV3(tests["python_generated_test_with_odd_iv"], t) +} + +func TestV3_PBKDF2_4(t *testing.T) { + skipIfSubmoduleMissing(t) + t.Parallel() + tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t) + testDecryptV3(tests["evilnonce"], t) +} + +func TestV3_Scrypt_1(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) + testDecryptV3(tests["wikipage_test_vector_scrypt"], t) +} + +func TestV3_Scrypt_2(t *testing.T) { + skipIfSubmoduleMissing(t) + t.Parallel() + tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t) + testDecryptV3(tests["test2"], t) +} + +func TestV1_1(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV1("testdata/v1_test_vector.json", t) + testDecryptV1(tests["test1"], t) +} + +func TestV1_2(t *testing.T) { + t.Parallel() + ks := &keyStorePassphrase{"testdata/v1", LightScryptN, LightScryptP, true} + addr := common.HexToAddress("cb61d5a9c4896fb9658090b597ef0e7be6f7b67e") + file := "testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e" + k, err := ks.GetKey(addr, file, "g") + if err != nil { + t.Fatal(err) + } + privHex := hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)) + expectedHex := "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d" + if privHex != expectedHex { + t.Fatal(fmt.Errorf("Unexpected privkey: %v, expected %v", privHex, expectedHex)) + } +} + +func testDecryptV3(test KeyStoreTestV3, t *testing.T) { + privBytes, _, err := decryptKeyV3(&test.Json, test.Password) + if err != nil { + t.Fatal(err) + } + privHex := hex.EncodeToString(privBytes) + if test.Priv != privHex { + t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex)) + } +} + +func testDecryptV1(test KeyStoreTestV1, t *testing.T) { + privBytes, _, err := decryptKeyV1(&test.Json, test.Password) + if err != nil { + t.Fatal(err) + } + privHex := hex.EncodeToString(privBytes) + if test.Priv != privHex { + t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex)) + } +} + +func loadKeyStoreTestV3(file string, t *testing.T) map[string]KeyStoreTestV3 { + tests := make(map[string]KeyStoreTestV3) + err := common.LoadJSON(file, &tests) + if err != nil { + t.Fatal(err) + } + return tests +} + +func loadKeyStoreTestV1(file string, t *testing.T) map[string]KeyStoreTestV1 { + tests := make(map[string]KeyStoreTestV1) + err := common.LoadJSON(file, &tests) + if err != nil { + t.Fatal(err) + } + return tests +} + +func TestKeyForDirectICAP(t *testing.T) { + t.Parallel() + key := NewKeyForDirectICAP(rand.Reader) + if !strings.HasPrefix(key.Address.Hex(), "0x00") { + t.Errorf("Expected first address byte to be zero, have: %s", key.Address.Hex()) + } +} + +func TestV3_31_Byte_Key(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) + testDecryptV3(tests["31_byte_key"], t) +} + +func TestV3_30_Byte_Key(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) + testDecryptV3(tests["30_byte_key"], t) +} diff --git a/accounts/keystore/presale.go b/accounts/keystore/presale.go new file mode 100644 index 000000000..03055245f --- /dev/null +++ b/accounts/keystore/presale.go @@ -0,0 +1,147 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package keystore + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/crypto" + "github.com/pborman/uuid" + "golang.org/x/crypto/pbkdf2" +) + +// creates a Key and stores that in the given KeyStore by decrypting a presale key JSON +func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accounts.Account, *Key, error) { + key, err := decryptPreSaleKey(keyJSON, password) + if err != nil { + return accounts.Account{}, nil, err + } + key.Id = uuid.NewRandom() + a := accounts.Account{ + Address: key.Address, + URL: accounts.URL{ + Scheme: KeyStoreScheme, + Path: keyStore.JoinPath(keyFileName(key.Address)), + }, + } + err = keyStore.StoreKey(a.URL.Path, key, password) + return a, key, err +} + +func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error) { + preSaleKeyStruct := struct { + EncSeed string + EthAddr string + Email string + BtcAddr string + }{} + err = json.Unmarshal(fileContent, &preSaleKeyStruct) + if err != nil { + return nil, err + } + encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed) + if err != nil { + return nil, errors.New("invalid hex in encSeed") + } + if len(encSeedBytes) < 16 { + return nil, errors.New("invalid encSeed, too short") + } + iv := encSeedBytes[:16] + cipherText := encSeedBytes[16:] + /* + See https://github.com/ethereum/pyethsaletool + + pyethsaletool generates the encryption key from password by + 2000 rounds of PBKDF2 with HMAC-SHA-256 using password as salt (:(). + 16 byte key length within PBKDF2 and resulting key is used as AES key + */ + passBytes := []byte(password) + derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New) + plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv) + if err != nil { + return nil, err + } + ethPriv := crypto.Keccak256(plainText) + ecKey := crypto.ToECDSAUnsafe(ethPriv) + + key = &Key{ + Id: nil, + Address: crypto.PubkeyToAddress(ecKey.PublicKey), + PrivateKey: ecKey, + } + derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x" + expectedAddr := preSaleKeyStruct.EthAddr + if derivedAddr != expectedAddr { + err = fmt.Errorf("decrypted addr '%s' not equal to expected addr '%s'", derivedAddr, expectedAddr) + } + return key, err +} + +func aesCTRXOR(key, inText, iv []byte) ([]byte, error) { + // AES-128 is selected due to size of encryptKey. + aesBlock, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + stream := cipher.NewCTR(aesBlock, iv) + outText := make([]byte, len(inText)) + stream.XORKeyStream(outText, inText) + return outText, err +} + +func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) { + aesBlock, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + decrypter := cipher.NewCBCDecrypter(aesBlock, iv) + paddedPlaintext := make([]byte, len(cipherText)) + decrypter.CryptBlocks(paddedPlaintext, cipherText) + plaintext := pkcs7Unpad(paddedPlaintext) + if plaintext == nil { + return nil, ErrDecrypt + } + return plaintext, err +} + +// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes +func pkcs7Unpad(in []byte) []byte { + if len(in) == 0 { + return nil + } + + padding := in[len(in)-1] + if int(padding) > len(in) || padding > aes.BlockSize { + return nil + } else if padding == 0 { + return nil + } + + for i := len(in) - 1; i > len(in)-int(padding)-1; i-- { + if in[i] != padding { + return nil + } + } + return in[:len(in)-int(padding)] +} diff --git a/accounts/keystore/testdata/dupes/1 b/accounts/keystore/testdata/dupes/1 new file mode 100644 index 000000000..a3868ec6d --- /dev/null +++ b/accounts/keystore/testdata/dupes/1 @@ -0,0 +1 @@ +{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/dupes/2 b/accounts/keystore/testdata/dupes/2 new file mode 100644 index 000000000..a3868ec6d --- /dev/null +++ b/accounts/keystore/testdata/dupes/2 @@ -0,0 +1 @@ +{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/dupes/foo b/accounts/keystore/testdata/dupes/foo new file mode 100644 index 000000000..c57060aea --- /dev/null +++ b/accounts/keystore/testdata/dupes/foo @@ -0,0 +1 @@ +{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/.hiddenfile b/accounts/keystore/testdata/keystore/.hiddenfile new file mode 100644 index 000000000..d91faccde --- /dev/null +++ b/accounts/keystore/testdata/keystore/.hiddenfile @@ -0,0 +1 @@ +{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} diff --git a/accounts/keystore/testdata/keystore/README b/accounts/keystore/testdata/keystore/README new file mode 100644 index 000000000..a5a86f964 --- /dev/null +++ b/accounts/keystore/testdata/keystore/README @@ -0,0 +1,21 @@ +This directory contains accounts for testing. +The passphrase that unlocks them is "foobar". + +The "good" key files which are supposed to be loadable are: + +- File: UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 + Address: 0x7ef5a6135f1fd6a02593eedc869c6d41d934aef8 +- File: aaa + Address: 0xf466859ead1932d743d622cb74fc058882e8648a +- File: zzz + Address: 0x289d485d9771714cce91d3393d764e1311907acc + +The other files (including this README) are broken in various ways +and should not be picked up by package accounts: + +- File: no-address (missing address field, otherwise same as "aaa") +- File: garbage (file with random data) +- File: empty (file with no content) +- File: swapfile~ (should be skipped) +- File: .hiddenfile (should be skipped) +- File: foo/... (should be skipped because it is a directory) diff --git a/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 b/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 new file mode 100644 index 000000000..c57060aea --- /dev/null +++ b/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 @@ -0,0 +1 @@ +{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/aaa b/accounts/keystore/testdata/keystore/aaa new file mode 100644 index 000000000..a3868ec6d --- /dev/null +++ b/accounts/keystore/testdata/keystore/aaa @@ -0,0 +1 @@ +{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/empty b/accounts/keystore/testdata/keystore/empty new file mode 100644 index 000000000..e69de29bb diff --git a/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e b/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e new file mode 100644 index 000000000..309841e52 --- /dev/null +++ b/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e @@ -0,0 +1 @@ +{"address":"fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e","crypto":{"cipher":"aes-128-ctr","ciphertext":"8124d5134aa4a927c79fd852989e4b5419397566f04b0936a1eb1d168c7c68a5","cipherparams":{"iv":"e2febe17176414dd2cda28287947eb2f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":4096,"p":6,"r":8,"salt":"44b415ede89f3bdd6830390a21b78965f571b347a589d1d943029f016c5e8bd5"},"mac":"5e149ff25bfd9dd45746a84bb2bcd2f015f2cbca2b6d25c5de8c29617f71fe5b"},"id":"d6ac5452-2b2c-4d3c-ad80-4bf0327d971c","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/garbage b/accounts/keystore/testdata/keystore/garbage new file mode 100644 index 0000000000000000000000000000000000000000..ff45091e714078dd7d3b4ea95964452e33a895f7 GIT binary patch literal 300 zcmV+{0n`3r1xkOa0KiH=0-y31ays31&4D+~b{#6-MH z)8?iosg+26q81!5ujp29iM}4_d}^;*-$8$htAbEpk(KDl*$;NvD$v8GZL@TRuT#)+ zq*|PXNljY5_xwCfoMayTjJ(vY;=t!uVJT5-Fn0O7W{#e;Ho?+NsQQi=!GV>j#9U#& zAbp7L1M-8N-V+7}EDxG9CNuhKbj?($B?=E1a1Xi%v;bYvR+C$EjApbg!W^>zB$Cd( z+NKd!El}@p)NJLnQ}B=D#e5uCh87_~lKd2z=idP7$. + +package keystore + +import ( + "math/big" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +// keystoreWallet implements the accounts.Wallet interface for the original +// keystore. +type keystoreWallet struct { + account accounts.Account // Single account contained in this wallet + keystore *KeyStore // Keystore where the account originates from +} + +// URL implements accounts.Wallet, returning the URL of the account within. +func (w *keystoreWallet) URL() accounts.URL { + return w.account.URL +} + +// Status implements accounts.Wallet, returning whether the account held by the +// keystore wallet is unlocked or not. +func (w *keystoreWallet) Status() (string, error) { + w.keystore.mu.RLock() + defer w.keystore.mu.RUnlock() + + if _, ok := w.keystore.unlocked[w.account.Address]; ok { + return "Unlocked", nil + } + return "Locked", nil +} + +// Open implements accounts.Wallet, but is a noop for plain wallets since there +// is no connection or decryption step necessary to access the list of accounts. +func (w *keystoreWallet) Open(passphrase string) error { return nil } + +// Close implements accounts.Wallet, but is a noop for plain wallets since there +// is no meaningful open operation. +func (w *keystoreWallet) Close() error { return nil } + +// Accounts implements accounts.Wallet, returning an account list consisting of +// a single account that the plain kestore wallet contains. +func (w *keystoreWallet) Accounts() []accounts.Account { + return []accounts.Account{w.account} +} + +// Contains implements accounts.Wallet, returning whether a particular account is +// or is not wrapped by this wallet instance. +func (w *keystoreWallet) Contains(account accounts.Account) bool { + return account.Address == w.account.Address && (account.URL == (accounts.URL{}) || account.URL == w.account.URL) +} + +// Derive implements accounts.Wallet, but is a noop for plain wallets since there +// is no notion of hierarchical account derivation for plain keystore accounts. +func (w *keystoreWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { + return accounts.Account{}, accounts.ErrNotSupported +} + +// SelfDerive implements accounts.Wallet, but is a noop for plain wallets since +// there is no notion of hierarchical account derivation for plain keystore accounts. +func (w *keystoreWallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { +} + +// signHash attempts to sign the given hash with +// the given account. If the wallet does not wrap this particular account, an +// error is returned to avoid account leakage (even though in theory we may be +// able to sign via our shared keystore backend). +func (w *keystoreWallet) signHash(account accounts.Account, hash []byte) ([]byte, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignHash(account, hash) +} + +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed +func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { + return w.signHash(account, crypto.Keccak256(data)) +} + +// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed +func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data)) +} + +func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) { + return w.signHash(account, accounts.TextHash(text)) +} + +// SignTextWithPassphrase implements accounts.Wallet, attempting to sign the +// given hash with the given account using passphrase as extra authentication. +func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text)) +} + +// SignTx implements accounts.Wallet, attempting to sign the given transaction +// with the given account. If the wallet does not wrap this particular account, +// an error is returned to avoid account leakage (even though in theory we may +// be able to sign via our shared keystore backend). +func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignTx(account, tx, chainID) +} + +// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given +// transaction with the given account using passphrase as extra authentication. +func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignTxWithPassphrase(account, passphrase, tx, chainID) +} diff --git a/accounts/keystore/watch.go b/accounts/keystore/watch.go new file mode 100644 index 000000000..bbcfb9925 --- /dev/null +++ b/accounts/keystore/watch.go @@ -0,0 +1,108 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build darwin,!ios freebsd linux,!arm64 netbsd solaris + +package keystore + +import ( + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/rjeczalik/notify" +) + +type watcher struct { + ac *accountCache + starting bool + running bool + ev chan notify.EventInfo + quit chan struct{} +} + +func newWatcher(ac *accountCache) *watcher { + return &watcher{ + ac: ac, + ev: make(chan notify.EventInfo, 10), + quit: make(chan struct{}), + } +} + +// starts the watcher loop in the background. +// Start a watcher in the background if that's not already in progress. +// The caller must hold w.ac.mu. +func (w *watcher) start() { + if w.starting || w.running { + return + } + w.starting = true + go w.loop() +} + +func (w *watcher) close() { + close(w.quit) +} + +func (w *watcher) loop() { + defer func() { + w.ac.mu.Lock() + w.running = false + w.starting = false + w.ac.mu.Unlock() + }() + logger := log.New("path", w.ac.keydir) + + if err := notify.Watch(w.ac.keydir, w.ev, notify.All); err != nil { + logger.Trace("Failed to watch keystore folder", "err", err) + return + } + defer notify.Stop(w.ev) + logger.Trace("Started watching keystore folder") + defer logger.Trace("Stopped watching keystore folder") + + w.ac.mu.Lock() + w.running = true + w.ac.mu.Unlock() + + // Wait for file system events and reload. + // When an event occurs, the reload call is delayed a bit so that + // multiple events arriving quickly only cause a single reload. + var ( + debounceDuration = 500 * time.Millisecond + rescanTriggered = false + debounce = time.NewTimer(0) + ) + // Ignore initial trigger + if !debounce.Stop() { + <-debounce.C + } + defer debounce.Stop() + for { + select { + case <-w.quit: + return + case <-w.ev: + // Trigger the scan (with delay), if not already triggered + if !rescanTriggered { + debounce.Reset(debounceDuration) + rescanTriggered = true + } + case <-debounce.C: + w.ac.scanAccounts() + rescanTriggered = false + } + } +} diff --git a/accounts/keystore/watch_fallback.go b/accounts/keystore/watch_fallback.go new file mode 100644 index 000000000..7c5e9cb2e --- /dev/null +++ b/accounts/keystore/watch_fallback.go @@ -0,0 +1,28 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris + +// This is the fallback implementation of directory watching. +// It is used on unsupported platforms. + +package keystore + +type watcher struct{ running bool } + +func newWatcher(*accountCache) *watcher { return new(watcher) } +func (*watcher) start() {} +func (*watcher) close() {} diff --git a/accounts/manager.go b/accounts/manager.go new file mode 100644 index 000000000..3cf3422e7 --- /dev/null +++ b/accounts/manager.go @@ -0,0 +1,214 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package accounts + +import ( + "reflect" + "sort" + "sync" + + "github.com/ethereum/go-ethereum/event" +) + +// Config contains the settings of the global account manager. +// +// TODO(rjl493456442, karalabe, holiman): Get rid of this when account management +// is removed in favor of Clef. +type Config struct { + InsecureUnlockAllowed bool // Whether account unlocking in insecure environment is allowed +} + +// Manager is an overarching account manager that can communicate with various +// backends for signing transactions. +type Manager struct { + config *Config // Global account manager configurations + backends map[reflect.Type][]Backend // Index of backends currently registered + updaters []event.Subscription // Wallet update subscriptions for all backends + updates chan WalletEvent // Subscription sink for backend wallet changes + wallets []Wallet // Cache of all wallets from all registered backends + + feed event.Feed // Wallet feed notifying of arrivals/departures + + quit chan chan error + lock sync.RWMutex +} + +// NewManager creates a generic account manager to sign transaction via various +// supported backends. +func NewManager(config *Config, backends ...Backend) *Manager { + // Retrieve the initial list of wallets from the backends and sort by URL + var wallets []Wallet + for _, backend := range backends { + wallets = merge(wallets, backend.Wallets()...) + } + // Subscribe to wallet notifications from all backends + updates := make(chan WalletEvent, 4*len(backends)) + + subs := make([]event.Subscription, len(backends)) + for i, backend := range backends { + subs[i] = backend.Subscribe(updates) + } + // Assemble the account manager and return + am := &Manager{ + config: config, + backends: make(map[reflect.Type][]Backend), + updaters: subs, + updates: updates, + wallets: wallets, + quit: make(chan chan error), + } + for _, backend := range backends { + kind := reflect.TypeOf(backend) + am.backends[kind] = append(am.backends[kind], backend) + } + go am.update() + + return am +} + +// Close terminates the account manager's internal notification processes. +func (am *Manager) Close() error { + errc := make(chan error) + am.quit <- errc + return <-errc +} + +// Config returns the configuration of account manager. +func (am *Manager) Config() *Config { + return am.config +} + +// update is the wallet event loop listening for notifications from the backends +// and updating the cache of wallets. +func (am *Manager) update() { + // Close all subscriptions when the manager terminates + defer func() { + am.lock.Lock() + for _, sub := range am.updaters { + sub.Unsubscribe() + } + am.updaters = nil + am.lock.Unlock() + }() + + // Loop until termination + for { + select { + case event := <-am.updates: + // Wallet event arrived, update local cache + am.lock.Lock() + switch event.Kind { + case WalletArrived: + am.wallets = merge(am.wallets, event.Wallet) + case WalletDropped: + am.wallets = drop(am.wallets, event.Wallet) + } + am.lock.Unlock() + + // Notify any listeners of the event + am.feed.Send(event) + + case errc := <-am.quit: + // Manager terminating, return + errc <- nil + return + } + } +} + +// Backends retrieves the backend(s) with the given type from the account manager. +func (am *Manager) Backends(kind reflect.Type) []Backend { + return am.backends[kind] +} + +// Wallets returns all signer accounts registered under this account manager. +func (am *Manager) Wallets() []Wallet { + am.lock.RLock() + defer am.lock.RUnlock() + + cpy := make([]Wallet, len(am.wallets)) + copy(cpy, am.wallets) + return cpy +} + +// Wallet retrieves the wallet associated with a particular URL. +func (am *Manager) Wallet(url string) (Wallet, error) { + am.lock.RLock() + defer am.lock.RUnlock() + + parsed, err := parseURL(url) + if err != nil { + return nil, err + } + for _, wallet := range am.Wallets() { + if wallet.URL() == parsed { + return wallet, nil + } + } + return nil, ErrUnknownWallet +} + +// Find attempts to locate the wallet corresponding to a specific account. Since +// accounts can be dynamically added to and removed from wallets, this method has +// a linear runtime in the number of wallets. +func (am *Manager) Find(account Account) (Wallet, error) { + am.lock.RLock() + defer am.lock.RUnlock() + + for _, wallet := range am.wallets { + if wallet.Contains(account) { + return wallet, nil + } + } + return nil, ErrUnknownAccount +} + +// Subscribe creates an async subscription to receive notifications when the +// manager detects the arrival or departure of a wallet from any of its backends. +func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription { + return am.feed.Subscribe(sink) +} + +// merge is a sorted analogue of append for wallets, where the ordering of the +// origin list is preserved by inserting new wallets at the correct position. +// +// The original slice is assumed to be already sorted by URL. +func merge(slice []Wallet, wallets ...Wallet) []Wallet { + for _, wallet := range wallets { + n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) + if n == len(slice) { + slice = append(slice, wallet) + continue + } + slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...) + } + return slice +} + +// drop is the couterpart of merge, which looks up wallets from within the sorted +// cache and removes the ones specified. +func drop(slice []Wallet, wallets ...Wallet) []Wallet { + for _, wallet := range wallets { + n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) + if n == len(slice) { + // Wallet not found, may happen during startup + continue + } + slice = append(slice[:n], slice[n+1:]...) + } + return slice +} diff --git a/accounts/scwallet/README.md b/accounts/scwallet/README.md new file mode 100644 index 000000000..20519e092 --- /dev/null +++ b/accounts/scwallet/README.md @@ -0,0 +1,102 @@ +# Using the smartcard wallet + +## Requirements + + * A USB smartcard reader + * A keycard that supports the status app + * PCSCD version 4.3 running on your system **Only version 4.3 is currently supported** + +## Preparing the smartcard + + **WARNING: FOILLOWING THESE INSTRUCTIONS WILL DESTROY THE MASTER KEY ON YOUR CARD. ONLY PROCEED IF NO FUNDS ARE ASSOCIATED WITH THESE ACCOUNTS** + + You can use status' [keycard-cli](https://github.com/status-im/keycard-cli) and you should get _at least_ version 2.1.1 of their [smartcard application](https://github.com/status-im/status-keycard/releases/download/2.2.1/keycard_v2.2.1.cap) + + You also need to make sure that the PCSC daemon is running on your system. + + Then, you can install the application to the card by typing: + + ``` + keycard install -a keycard_v2.2.1.cap && keycard init + ``` + + At the end of this process, you will be provided with a PIN, a PUK and a pairing password. Write them down, you'll need them shortly. + + Start `geth` with the `console` command. You will notice the following warning: + + ``` + WARN [04-09|16:58:38.898] Failed to open wallet url=pcsc://044def09 err="smartcard: pairing password needed" + ``` + + Write down the URL (`pcsc://044def09` in this example). Then ask `geth` to open the wallet: + + ``` + > personal.openWallet("pcsc://044def09") + Please enter the pairing password: + ``` + + Enter the pairing password that you have received during card initialization. Same with the PIN that you will subsequently be + asked for. + + If everything goes well, you should see your new account when typing `personal` on the console: + + ``` + > personal + WARN [04-09|17:02:07.330] Smartcard wallet account derivation failed url=pcsc://044def09 err="Unexpected response status Cla=0x80, Ins=0xd1, Sw=0x6985" + { + listAccounts: [], + listWallets: [{ + status: "Empty, waiting for initialization", + url: "pcsc://044def09" + }], + ... + } + ``` + + So the communication with the card is working, but there is no key associated with this wallet. Let's create it: + + ``` + > personal.initializeWallet("pcsc://044def09") + "tilt ... impact" + ``` + + You should get a list of words, this is your seed so write them down. Your wallet should now be initialized: + + ``` + > personal.listWallets + [{ + accounts: [{ + address: "0x678b7cd55c61917defb23546a41803c5bfefbc7a", + url: "pcsc://044d/m/44'/60'/0'/0/0" + }], + status: "Online", + url: "pcsc://044def09" + }] + ``` + + You're all set! + +## Usage + + 1. Start `geth` with the `console` command + 2. Check the card's URL by checking `personal.listWallets`: + +``` + listWallets: [{ + status: "Online, can derive public keys", + url: "pcsc://a4d73015" + }] +``` + + 3. Open the wallet, you will be prompted for your pairing password, then PIN: + +``` +personal.openWallet("pcsc://a4d73015") +``` + + 4. Check that creation was successful by typing e.g. `personal`. Then use it like a regular wallet. + +## Known issues + + * Starting geth with a valid card seems to make firefox crash. + * PCSC version 4.4 should work, but is currently untested diff --git a/accounts/scwallet/apdu.go b/accounts/scwallet/apdu.go new file mode 100644 index 000000000..bd3660604 --- /dev/null +++ b/accounts/scwallet/apdu.go @@ -0,0 +1,87 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package scwallet + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +// commandAPDU represents an application data unit sent to a smartcard. +type commandAPDU struct { + Cla, Ins, P1, P2 uint8 // Class, Instruction, Parameter 1, Parameter 2 + Data []byte // Command data + Le uint8 // Command data length +} + +// serialize serializes a command APDU. +func (ca commandAPDU) serialize() ([]byte, error) { + buf := new(bytes.Buffer) + + if err := binary.Write(buf, binary.BigEndian, ca.Cla); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, ca.Ins); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, ca.P1); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, ca.P2); err != nil { + return nil, err + } + if len(ca.Data) > 0 { + if err := binary.Write(buf, binary.BigEndian, uint8(len(ca.Data))); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, ca.Data); err != nil { + return nil, err + } + } + if err := binary.Write(buf, binary.BigEndian, ca.Le); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// responseAPDU represents an application data unit received from a smart card. +type responseAPDU struct { + Data []byte // response data + Sw1, Sw2 uint8 // status words 1 and 2 +} + +// deserialize deserializes a response APDU. +func (ra *responseAPDU) deserialize(data []byte) error { + if len(data) < 2 { + return fmt.Errorf("can not deserialize data: payload too short (%d < 2)", len(data)) + } + + ra.Data = make([]byte, len(data)-2) + + buf := bytes.NewReader(data) + if err := binary.Read(buf, binary.BigEndian, &ra.Data); err != nil { + return err + } + if err := binary.Read(buf, binary.BigEndian, &ra.Sw1); err != nil { + return err + } + if err := binary.Read(buf, binary.BigEndian, &ra.Sw2); err != nil { + return err + } + return nil +} diff --git a/accounts/scwallet/hub.go b/accounts/scwallet/hub.go new file mode 100644 index 000000000..c259f711f --- /dev/null +++ b/accounts/scwallet/hub.go @@ -0,0 +1,302 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// This package implements support for smartcard-based hardware wallets such as +// the one written by Status: https://github.com/status-im/hardware-wallet +// +// This implementation of smartcard wallets have a different interaction process +// to other types of hardware wallet. The process works like this: +// +// 1. (First use with a given client) Establish a pairing between hardware +// wallet and client. This requires a secret value called a 'pairing password'. +// You can pair with an unpaired wallet with `personal.openWallet(URI, pairing password)`. +// 2. (First use only) Initialize the wallet, which generates a keypair, stores +// it on the wallet, and returns it so the user can back it up. You can +// initialize a wallet with `personal.initializeWallet(URI)`. +// 3. Connect to the wallet using the pairing information established in step 1. +// You can connect to a paired wallet with `personal.openWallet(URI, PIN)`. +// 4. Interact with the wallet as normal. + +package scwallet + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + "sort" + "sync" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + pcsc "github.com/gballet/go-libpcsclite" +) + +// Scheme is the URI prefix for smartcard wallets. +const Scheme = "pcsc" + +// refreshCycle is the maximum time between wallet refreshes (if USB hotplug +// notifications don't work). +const refreshCycle = time.Second + +// refreshThrottling is the minimum time between wallet refreshes to avoid thrashing. +const refreshThrottling = 500 * time.Millisecond + +// smartcardPairing contains information about a smart card we have paired with +// or might pair with the hub. +type smartcardPairing struct { + PublicKey []byte `json:"publicKey"` + PairingIndex uint8 `json:"pairingIndex"` + PairingKey []byte `json:"pairingKey"` + Accounts map[common.Address]accounts.DerivationPath `json:"accounts"` +} + +// Hub is a accounts.Backend that can find and handle generic PC/SC hardware wallets. +type Hub struct { + scheme string // Protocol scheme prefixing account and wallet URLs. + + context *pcsc.Client + datadir string + pairings map[string]smartcardPairing + + refreshed time.Time // Time instance when the list of wallets was last refreshed + wallets map[string]*Wallet // Mapping from reader names to wallet instances + updateFeed event.Feed // Event feed to notify wallet additions/removals + updateScope event.SubscriptionScope // Subscription scope tracking current live listeners + updating bool // Whether the event notification loop is running + + quit chan chan error + + stateLock sync.RWMutex // Protects the internals of the hub from racey access +} + +func (hub *Hub) readPairings() error { + hub.pairings = make(map[string]smartcardPairing) + pairingFile, err := os.Open(filepath.Join(hub.datadir, "smartcards.json")) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + + pairingData, err := ioutil.ReadAll(pairingFile) + if err != nil { + return err + } + var pairings []smartcardPairing + if err := json.Unmarshal(pairingData, &pairings); err != nil { + return err + } + + for _, pairing := range pairings { + hub.pairings[string(pairing.PublicKey)] = pairing + } + return nil +} + +func (hub *Hub) writePairings() error { + pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + return err + } + defer pairingFile.Close() + + pairings := make([]smartcardPairing, 0, len(hub.pairings)) + for _, pairing := range hub.pairings { + pairings = append(pairings, pairing) + } + + pairingData, err := json.Marshal(pairings) + if err != nil { + return err + } + + if _, err := pairingFile.Write(pairingData); err != nil { + return err + } + + return nil +} + +func (hub *Hub) pairing(wallet *Wallet) *smartcardPairing { + if pairing, ok := hub.pairings[string(wallet.PublicKey)]; ok { + return &pairing + } + return nil +} + +func (hub *Hub) setPairing(wallet *Wallet, pairing *smartcardPairing) error { + if pairing == nil { + delete(hub.pairings, string(wallet.PublicKey)) + } else { + hub.pairings[string(wallet.PublicKey)] = *pairing + } + return hub.writePairings() +} + +// NewHub creates a new hardware wallet manager for smartcards. +func NewHub(scheme string, datadir string) (*Hub, error) { + context, err := pcsc.EstablishContext(pcsc.ScopeSystem) + if err != nil { + return nil, err + } + hub := &Hub{ + scheme: scheme, + context: context, + datadir: datadir, + wallets: make(map[string]*Wallet), + quit: make(chan chan error), + } + if err := hub.readPairings(); err != nil { + return nil, err + } + hub.refreshWallets() + return hub, nil +} + +// Wallets implements accounts.Backend, returning all the currently tracked smart +// cards that appear to be hardware wallets. +func (hub *Hub) Wallets() []accounts.Wallet { + // Make sure the list of wallets is up to date + hub.refreshWallets() + + hub.stateLock.RLock() + defer hub.stateLock.RUnlock() + + cpy := make([]accounts.Wallet, 0, len(hub.wallets)) + for _, wallet := range hub.wallets { + cpy = append(cpy, wallet) + } + sort.Sort(accounts.WalletsByURL(cpy)) + return cpy +} + +// refreshWallets scans the devices attached to the machine and updates the +// list of wallets based on the found devices. +func (hub *Hub) refreshWallets() { + // Don't scan the USB like crazy it the user fetches wallets in a loop + hub.stateLock.RLock() + elapsed := time.Since(hub.refreshed) + hub.stateLock.RUnlock() + + if elapsed < refreshThrottling { + return + } + // Retrieve all the smart card reader to check for cards + readers, err := hub.context.ListReaders() + if err != nil { + // This is a perverted hack, the scard library returns an error if no card + // readers are present instead of simply returning an empty list. We don't + // want to fill the user's log with errors, so filter those out. + if err.Error() != "scard: Cannot find a smart card reader." { + log.Error("Failed to enumerate smart card readers", "err", err) + return + } + } + // Transform the current list of wallets into the new one + hub.stateLock.Lock() + + events := []accounts.WalletEvent{} + seen := make(map[string]struct{}) + + for _, reader := range readers { + // Mark the reader as present + seen[reader] = struct{}{} + + // If we alreay know about this card, skip to the next reader, otherwise clean up + if wallet, ok := hub.wallets[reader]; ok { + if err := wallet.ping(); err == nil { + continue + } + wallet.Close() + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) + delete(hub.wallets, reader) + } + // New card detected, try to connect to it + card, err := hub.context.Connect(reader, pcsc.ShareShared, pcsc.ProtocolAny) + if err != nil { + log.Debug("Failed to open smart card", "reader", reader, "err", err) + continue + } + wallet := NewWallet(hub, card) + if err = wallet.connect(); err != nil { + log.Debug("Failed to connect to smart card", "reader", reader, "err", err) + card.Disconnect(pcsc.LeaveCard) + continue + } + // Card connected, start tracking in amongs the wallets + hub.wallets[reader] = wallet + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) + } + // Remove any wallets no longer present + for reader, wallet := range hub.wallets { + if _, ok := seen[reader]; !ok { + wallet.Close() + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) + delete(hub.wallets, reader) + } + } + hub.refreshed = time.Now() + hub.stateLock.Unlock() + + for _, event := range events { + hub.updateFeed.Send(event) + } +} + +// Subscribe implements accounts.Backend, creating an async subscription to +// receive notifications on the addition or removal of smart card wallets. +func (hub *Hub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { + // We need the mutex to reliably start/stop the update loop + hub.stateLock.Lock() + defer hub.stateLock.Unlock() + + // Subscribe the caller and track the subscriber count + sub := hub.updateScope.Track(hub.updateFeed.Subscribe(sink)) + + // Subscribers require an active notification loop, start it + if !hub.updating { + hub.updating = true + go hub.updater() + } + return sub +} + +// updater is responsible for maintaining an up-to-date list of wallets managed +// by the smart card hub, and for firing wallet addition/removal events. +func (hub *Hub) updater() { + for { + // TODO: Wait for a USB hotplug event (not supported yet) or a refresh timeout + // <-hub.changes + time.Sleep(refreshCycle) + + // Run the wallet refresher + hub.refreshWallets() + + // If all our subscribers left, stop the updater + hub.stateLock.Lock() + if hub.updateScope.Count() == 0 { + hub.updating = false + hub.stateLock.Unlock() + return + } + hub.stateLock.Unlock() + } +} diff --git a/accounts/scwallet/securechannel.go b/accounts/scwallet/securechannel.go new file mode 100644 index 000000000..fad876a01 --- /dev/null +++ b/accounts/scwallet/securechannel.go @@ -0,0 +1,346 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package scwallet + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "crypto/sha512" + "fmt" + + "github.com/ethereum/go-ethereum/crypto" + pcsc "github.com/gballet/go-libpcsclite" + "github.com/wsddn/go-ecdh" + "golang.org/x/crypto/pbkdf2" + "golang.org/x/text/unicode/norm" +) + +const ( + maxPayloadSize = 223 + pairP1FirstStep = 0 + pairP1LastStep = 1 + + scSecretLength = 32 + scBlockSize = 16 + + insOpenSecureChannel = 0x10 + insMutuallyAuthenticate = 0x11 + insPair = 0x12 + insUnpair = 0x13 + + pairingSalt = "Keycard Pairing Password Salt" +) + +// SecureChannelSession enables secure communication with a hardware wallet. +type SecureChannelSession struct { + card *pcsc.Card // A handle to the smartcard for communication + secret []byte // A shared secret generated from our ECDSA keys + publicKey []byte // Our own ephemeral public key + PairingKey []byte // A permanent shared secret for a pairing, if present + sessionEncKey []byte // The current session encryption key + sessionMacKey []byte // The current session MAC key + iv []byte // The current IV + PairingIndex uint8 // The pairing index +} + +// NewSecureChannelSession creates a new secure channel for the given card and public key. +func NewSecureChannelSession(card *pcsc.Card, keyData []byte) (*SecureChannelSession, error) { + // Generate an ECDSA keypair for ourselves + gen := ecdh.NewEllipticECDH(crypto.S256()) + private, public, err := gen.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + + cardPublic, ok := gen.Unmarshal(keyData) + if !ok { + return nil, fmt.Errorf("Could not unmarshal public key from card") + } + + secret, err := gen.GenerateSharedSecret(private, cardPublic) + if err != nil { + return nil, err + } + + return &SecureChannelSession{ + card: card, + secret: secret, + publicKey: gen.Marshal(public), + }, nil +} + +// Pair establishes a new pairing with the smartcard. +func (s *SecureChannelSession) Pair(pairingPassword []byte) error { + secretHash := pbkdf2.Key(norm.NFKD.Bytes(pairingPassword), norm.NFKD.Bytes([]byte(pairingSalt)), 50000, 32, sha256.New) + + challenge := make([]byte, 32) + if _, err := rand.Read(challenge); err != nil { + return err + } + + response, err := s.pair(pairP1FirstStep, challenge) + if err != nil { + return err + } + + md := sha256.New() + md.Write(secretHash[:]) + md.Write(challenge) + + expectedCryptogram := md.Sum(nil) + cardCryptogram := response.Data[:32] + cardChallenge := response.Data[32:64] + + if !bytes.Equal(expectedCryptogram, cardCryptogram) { + return fmt.Errorf("Invalid card cryptogram %v != %v", expectedCryptogram, cardCryptogram) + } + + md.Reset() + md.Write(secretHash[:]) + md.Write(cardChallenge) + response, err = s.pair(pairP1LastStep, md.Sum(nil)) + if err != nil { + return err + } + + md.Reset() + md.Write(secretHash[:]) + md.Write(response.Data[1:]) + s.PairingKey = md.Sum(nil) + s.PairingIndex = response.Data[0] + + return nil +} + +// Unpair disestablishes an existing pairing. +func (s *SecureChannelSession) Unpair() error { + if s.PairingKey == nil { + return fmt.Errorf("Cannot unpair: not paired") + } + + _, err := s.transmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{}) + if err != nil { + return err + } + s.PairingKey = nil + // Close channel + s.iv = nil + return nil +} + +// Open initializes the secure channel. +func (s *SecureChannelSession) Open() error { + if s.iv != nil { + return fmt.Errorf("Session already opened") + } + + response, err := s.open() + if err != nil { + return err + } + + // Generate the encryption/mac key by hashing our shared secret, + // pairing key, and the first bytes returned from the Open APDU. + md := sha512.New() + md.Write(s.secret) + md.Write(s.PairingKey) + md.Write(response.Data[:scSecretLength]) + keyData := md.Sum(nil) + s.sessionEncKey = keyData[:scSecretLength] + s.sessionMacKey = keyData[scSecretLength : scSecretLength*2] + + // The IV is the last bytes returned from the Open APDU. + s.iv = response.Data[scSecretLength:] + + return s.mutuallyAuthenticate() +} + +// mutuallyAuthenticate is an internal method to authenticate both ends of the +// connection. +func (s *SecureChannelSession) mutuallyAuthenticate() error { + data := make([]byte, scSecretLength) + if _, err := rand.Read(data); err != nil { + return err + } + + response, err := s.transmitEncrypted(claSCWallet, insMutuallyAuthenticate, 0, 0, data) + if err != nil { + return err + } + if response.Sw1 != 0x90 || response.Sw2 != 0x00 { + return fmt.Errorf("Got unexpected response from MUTUALLY_AUTHENTICATE: 0x%x%x", response.Sw1, response.Sw2) + } + + if len(response.Data) != scSecretLength { + return fmt.Errorf("Response from MUTUALLY_AUTHENTICATE was %d bytes, expected %d", len(response.Data), scSecretLength) + } + + return nil +} + +// open is an internal method that sends an open APDU. +func (s *SecureChannelSession) open() (*responseAPDU, error) { + return transmit(s.card, &commandAPDU{ + Cla: claSCWallet, + Ins: insOpenSecureChannel, + P1: s.PairingIndex, + P2: 0, + Data: s.publicKey, + Le: 0, + }) +} + +// pair is an internal method that sends a pair APDU. +func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*responseAPDU, error) { + return transmit(s.card, &commandAPDU{ + Cla: claSCWallet, + Ins: insPair, + P1: p1, + P2: 0, + Data: data, + Le: 0, + }) +} + +// transmitEncrypted sends an encrypted message, and decrypts and returns the response. +func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) { + if s.iv == nil { + return nil, fmt.Errorf("Channel not open") + } + + data, err := s.encryptAPDU(data) + if err != nil { + return nil, err + } + meta := [16]byte{cla, ins, p1, p2, byte(len(data) + scBlockSize)} + if err = s.updateIV(meta[:], data); err != nil { + return nil, err + } + + fulldata := make([]byte, len(s.iv)+len(data)) + copy(fulldata, s.iv) + copy(fulldata[len(s.iv):], data) + + response, err := transmit(s.card, &commandAPDU{ + Cla: cla, + Ins: ins, + P1: p1, + P2: p2, + Data: fulldata, + }) + if err != nil { + return nil, err + } + + rmeta := [16]byte{byte(len(response.Data))} + rmac := response.Data[:len(s.iv)] + rdata := response.Data[len(s.iv):] + plainData, err := s.decryptAPDU(rdata) + if err != nil { + return nil, err + } + + if err = s.updateIV(rmeta[:], rdata); err != nil { + return nil, err + } + if !bytes.Equal(s.iv, rmac) { + return nil, fmt.Errorf("Invalid MAC in response") + } + + rapdu := &responseAPDU{} + rapdu.deserialize(plainData) + + if rapdu.Sw1 != sw1Ok { + return nil, fmt.Errorf("Unexpected response status Cla=0x%x, Ins=0x%x, Sw=0x%x%x", cla, ins, rapdu.Sw1, rapdu.Sw2) + } + + return rapdu, nil +} + +// encryptAPDU is an internal method that serializes and encrypts an APDU. +func (s *SecureChannelSession) encryptAPDU(data []byte) ([]byte, error) { + if len(data) > maxPayloadSize { + return nil, fmt.Errorf("Payload of %d bytes exceeds maximum of %d", len(data), maxPayloadSize) + } + data = pad(data, 0x80) + + ret := make([]byte, len(data)) + + a, err := aes.NewCipher(s.sessionEncKey) + if err != nil { + return nil, err + } + crypter := cipher.NewCBCEncrypter(a, s.iv) + crypter.CryptBlocks(ret, data) + return ret, nil +} + +// pad applies message padding to a 16 byte boundary. +func pad(data []byte, terminator byte) []byte { + padded := make([]byte, (len(data)/16+1)*16) + copy(padded, data) + padded[len(data)] = terminator + return padded +} + +// decryptAPDU is an internal method that decrypts and deserializes an APDU. +func (s *SecureChannelSession) decryptAPDU(data []byte) ([]byte, error) { + a, err := aes.NewCipher(s.sessionEncKey) + if err != nil { + return nil, err + } + + ret := make([]byte, len(data)) + + crypter := cipher.NewCBCDecrypter(a, s.iv) + crypter.CryptBlocks(ret, data) + return unpad(ret, 0x80) +} + +// unpad strips padding from a message. +func unpad(data []byte, terminator byte) ([]byte, error) { + for i := 1; i <= 16; i++ { + switch data[len(data)-i] { + case 0: + continue + case terminator: + return data[:len(data)-i], nil + default: + return nil, fmt.Errorf("Expected end of padding, got %d", data[len(data)-i]) + } + } + return nil, fmt.Errorf("Expected end of padding, got 0") +} + +// updateIV is an internal method that updates the initialization vector after +// each message exchanged. +func (s *SecureChannelSession) updateIV(meta, data []byte) error { + data = pad(data, 0) + a, err := aes.NewCipher(s.sessionMacKey) + if err != nil { + return err + } + crypter := cipher.NewCBCEncrypter(a, make([]byte, 16)) + crypter.CryptBlocks(meta, meta) + crypter.CryptBlocks(data, data) + // The first 16 bytes of the last block is the MAC + s.iv = data[len(data)-32 : len(data)-16] + return nil +} diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go new file mode 100644 index 000000000..4c0824eb9 --- /dev/null +++ b/accounts/scwallet/wallet.go @@ -0,0 +1,1082 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package scwallet + +import ( + "bytes" + "context" + "crypto/hmac" + "crypto/sha256" + "crypto/sha512" + "encoding/asn1" + "encoding/binary" + "errors" + "fmt" + "math/big" + "sort" + "strings" + "sync" + "time" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" + "github.com/ethereum/go-ethereum/log" + pcsc "github.com/gballet/go-libpcsclite" + "github.com/status-im/keycard-go/derivationpath" +) + +// ErrPairingPasswordNeeded is returned if opening the smart card requires pairing with a pairing +// password. In this case, the calling application should request user input to enter +// the pairing password and send it back. +var ErrPairingPasswordNeeded = errors.New("smartcard: pairing password needed") + +// ErrPINNeeded is returned if opening the smart card requires a PIN code. In +// this case, the calling application should request user input to enter the PIN +// and send it back. +var ErrPINNeeded = errors.New("smartcard: pin needed") + +// ErrPINUnblockNeeded is returned if opening the smart card requires a PIN code, +// but all PIN attempts have already been exhausted. In this case the calling +// application should request user input for the PUK and a new PIN code to set +// fo the card. +var ErrPINUnblockNeeded = errors.New("smartcard: pin unblock needed") + +// ErrAlreadyOpen is returned if the smart card is attempted to be opened, but +// there is already a paired and unlocked session. +var ErrAlreadyOpen = errors.New("smartcard: already open") + +// ErrPubkeyMismatch is returned if the public key recovered from a signature +// does not match the one expected by the user. +var ErrPubkeyMismatch = errors.New("smartcard: recovered public key mismatch") + +var ( + appletAID = []byte{0xA0, 0x00, 0x00, 0x08, 0x04, 0x00, 0x01, 0x01, 0x01} + // DerivationSignatureHash is used to derive the public key from the signature of this hash + DerivationSignatureHash = sha256.Sum256(common.Hash{}.Bytes()) +) + +// List of APDU command-related constants +const ( + claISO7816 = 0 + claSCWallet = 0x80 + + insSelect = 0xA4 + insGetResponse = 0xC0 + sw1GetResponse = 0x61 + sw1Ok = 0x90 + + insVerifyPin = 0x20 + insUnblockPin = 0x22 + insExportKey = 0xC2 + insSign = 0xC0 + insLoadKey = 0xD0 + insDeriveKey = 0xD1 + insStatus = 0xF2 +) + +// List of ADPU command parameters +const ( + P1DeriveKeyFromMaster = uint8(0x00) + P1DeriveKeyFromParent = uint8(0x01) + P1DeriveKeyFromCurrent = uint8(0x10) + statusP1WalletStatus = uint8(0x00) + statusP1Path = uint8(0x01) + signP1PrecomputedHash = uint8(0x01) + signP2OnlyBlock = uint8(0x81) + exportP1Any = uint8(0x00) + exportP2Pubkey = uint8(0x01) +) + +// Minimum time to wait between self derivation attempts, even it the user is +// requesting accounts like crazy. +const selfDeriveThrottling = time.Second + +// Wallet represents a smartcard wallet instance. +type Wallet struct { + Hub *Hub // A handle to the Hub that instantiated this wallet. + PublicKey []byte // The wallet's public key (used for communication and identification, not signing!) + + lock sync.Mutex // Lock that gates access to struct fields and communication with the card + card *pcsc.Card // A handle to the smartcard interface for the wallet. + session *Session // The secure communication session with the card + log log.Logger // Contextual logger to tag the base with its id + + deriveNextPaths []accounts.DerivationPath // Next derivation paths for account auto-discovery (multiple bases supported) + deriveNextAddrs []common.Address // Next derived account addresses for auto-discovery (multiple bases supported) + deriveChain ethereum.ChainStateReader // Blockchain state reader to discover used account with + deriveReq chan chan struct{} // Channel to request a self-derivation on + deriveQuit chan chan error // Channel to terminate the self-deriver with +} + +// NewWallet constructs and returns a new Wallet instance. +func NewWallet(hub *Hub, card *pcsc.Card) *Wallet { + wallet := &Wallet{ + Hub: hub, + card: card, + } + return wallet +} + +// transmit sends an APDU to the smartcard and receives and decodes the response. +// It automatically handles requests by the card to fetch the return data separately, +// and returns an error if the response status code is not success. +func transmit(card *pcsc.Card, command *commandAPDU) (*responseAPDU, error) { + data, err := command.serialize() + if err != nil { + return nil, err + } + + responseData, _, err := card.Transmit(data) + if err != nil { + return nil, err + } + + response := new(responseAPDU) + if err = response.deserialize(responseData); err != nil { + return nil, err + } + + // Are we being asked to fetch the response separately? + if response.Sw1 == sw1GetResponse && (command.Cla != claISO7816 || command.Ins != insGetResponse) { + return transmit(card, &commandAPDU{ + Cla: claISO7816, + Ins: insGetResponse, + P1: 0, + P2: 0, + Data: nil, + Le: response.Sw2, + }) + } + + if response.Sw1 != sw1Ok { + return nil, fmt.Errorf("Unexpected insecure response status Cla=0x%x, Ins=0x%x, Sw=0x%x%x", command.Cla, command.Ins, response.Sw1, response.Sw2) + } + + return response, nil +} + +// applicationInfo encodes information about the smartcard application - its +// instance UID and public key. +type applicationInfo struct { + InstanceUID []byte `asn1:"tag:15"` + PublicKey []byte `asn1:"tag:0"` +} + +// connect connects to the wallet application and establishes a secure channel with it. +// must be called before any other interaction with the wallet. +func (w *Wallet) connect() error { + w.lock.Lock() + defer w.lock.Unlock() + + appinfo, err := w.doselect() + if err != nil { + return err + } + + channel, err := NewSecureChannelSession(w.card, appinfo.PublicKey) + if err != nil { + return err + } + + w.PublicKey = appinfo.PublicKey + w.log = log.New("url", w.URL()) + w.session = &Session{ + Wallet: w, + Channel: channel, + } + return nil +} + +// doselect is an internal (unlocked) function to send a SELECT APDU to the card. +func (w *Wallet) doselect() (*applicationInfo, error) { + response, err := transmit(w.card, &commandAPDU{ + Cla: claISO7816, + Ins: insSelect, + P1: 4, + P2: 0, + Data: appletAID, + }) + if err != nil { + return nil, err + } + + appinfo := new(applicationInfo) + if _, err := asn1.UnmarshalWithParams(response.Data, appinfo, "tag:4"); err != nil { + return nil, err + } + return appinfo, nil +} + +// ping checks the card's status and returns an error if unsuccessful. +func (w *Wallet) ping() error { + w.lock.Lock() + defer w.lock.Unlock() + + // We can't ping if not paired + if !w.session.paired() { + return nil + } + if _, err := w.session.walletStatus(); err != nil { + return err + } + return nil +} + +// release releases any resources held by an open wallet instance. +func (w *Wallet) release() error { + if w.session != nil { + return w.session.release() + } + return nil +} + +// pair is an internal (unlocked) function for establishing a new pairing +// with the wallet. +func (w *Wallet) pair(puk []byte) error { + if w.session.paired() { + return fmt.Errorf("Wallet already paired") + } + pairing, err := w.session.pair(puk) + if err != nil { + return err + } + if err = w.Hub.setPairing(w, &pairing); err != nil { + return err + } + return w.session.authenticate(pairing) +} + +// Unpair deletes an existing wallet pairing. +func (w *Wallet) Unpair(pin []byte) error { + w.lock.Lock() + defer w.lock.Unlock() + + if !w.session.paired() { + return fmt.Errorf("wallet %x not paired", w.PublicKey) + } + if err := w.session.verifyPin(pin); err != nil { + return fmt.Errorf("failed to verify pin: %s", err) + } + if err := w.session.unpair(); err != nil { + return fmt.Errorf("failed to unpair: %s", err) + } + if err := w.Hub.setPairing(w, nil); err != nil { + return err + } + return nil +} + +// URL retrieves the canonical path under which this wallet is reachable. It is +// user by upper layers to define a sorting order over all wallets from multiple +// backends. +func (w *Wallet) URL() accounts.URL { + return accounts.URL{ + Scheme: w.Hub.scheme, + Path: fmt.Sprintf("%x", w.PublicKey[1:5]), // Byte #0 isn't unique; 1:5 covers << 64K cards, bump to 1:9 for << 4M + } +} + +// Status returns a textual status to aid the user in the current state of the +// wallet. It also returns an error indicating any failure the wallet might have +// encountered. +func (w *Wallet) Status() (string, error) { + w.lock.Lock() + defer w.lock.Unlock() + + // If the card is not paired, we can only wait + if !w.session.paired() { + return "Unpaired, waiting for pairing password", nil + } + // Yay, we have an encrypted session, retrieve the actual status + status, err := w.session.walletStatus() + if err != nil { + return fmt.Sprintf("Failed: %v", err), err + } + switch { + case !w.session.verified && status.PinRetryCount == 0: + return fmt.Sprintf("Blocked, waiting for PUK and new PIN"), nil + case !w.session.verified: + return fmt.Sprintf("Locked, waiting for PIN (%d attempts left)", status.PinRetryCount), nil + case !status.Initialized: + return fmt.Sprintf("Empty, waiting for initialization"), nil + default: + return fmt.Sprintf("Online"), nil + } +} + +// Open initializes access to a wallet instance. It is not meant to unlock or +// decrypt account keys, rather simply to establish a connection to hardware +// wallets and/or to access derivation seeds. +// +// The passphrase parameter may or may not be used by the implementation of a +// particular wallet instance. The reason there is no passwordless open method +// is to strive towards a uniform wallet handling, oblivious to the different +// backend providers. +// +// Please note, if you open a wallet, you must close it to release any allocated +// resources (especially important when working with hardware wallets). +func (w *Wallet) Open(passphrase string) error { + w.lock.Lock() + defer w.lock.Unlock() + + // If the session is already open, bail out + if w.session.verified { + return ErrAlreadyOpen + } + // If the smart card is not yet paired, attempt to do so either from a previous + // pairing key or form the supplied PUK code. + if !w.session.paired() { + // If a previous pairing exists, only ever try to use that + if pairing := w.Hub.pairing(w); pairing != nil { + if err := w.session.authenticate(*pairing); err != nil { + return fmt.Errorf("failed to authenticate card %x: %s", w.PublicKey[:4], err) + } + // Pairing still ok, fall through to PIN checks + } else { + // If no passphrase was supplied, request the PUK from the user + if passphrase == "" { + return ErrPairingPasswordNeeded + } + // Attempt to pair the smart card with the user supplied PUK + if err := w.pair([]byte(passphrase)); err != nil { + return err + } + // Pairing succeeded, fall through to PIN checks. This will of course fail, + // but we can't return ErrPINNeeded directly here becase we don't know whether + // a PIN check or a PIN reset is needed. + passphrase = "" + } + } + // The smart card was successfully paired, retrieve its status to check whether + // PIN verification or unblocking is needed. + status, err := w.session.walletStatus() + if err != nil { + return err + } + // Request the appropriate next authentication data, or use the one supplied + switch { + case passphrase == "" && status.PinRetryCount > 0: + return ErrPINNeeded + case passphrase == "": + return ErrPINUnblockNeeded + case status.PinRetryCount > 0: + if err := w.session.verifyPin([]byte(passphrase)); err != nil { + return err + } + default: + if err := w.session.unblockPin([]byte(passphrase)); err != nil { + return err + } + } + // Smart card paired and unlocked, initialize and register + w.deriveReq = make(chan chan struct{}) + w.deriveQuit = make(chan chan error) + + go w.selfDerive() + + // Notify anyone listening for wallet events that a new device is accessible + go w.Hub.updateFeed.Send(accounts.WalletEvent{Wallet: w, Kind: accounts.WalletOpened}) + + return nil +} + +// Close stops and closes the wallet, freeing any resources. +func (w *Wallet) Close() error { + // Ensure the wallet was opened + w.lock.Lock() + dQuit := w.deriveQuit + w.lock.Unlock() + + // Terminate the self-derivations + var derr error + if dQuit != nil { + errc := make(chan error) + dQuit <- errc + derr = <-errc // Save for later, we *must* close the USB + } + // Terminate the device connection + w.lock.Lock() + defer w.lock.Unlock() + + w.deriveQuit = nil + w.deriveReq = nil + + if err := w.release(); err != nil { + return err + } + return derr +} + +// selfDerive is an account derivation loop that upon request attempts to find +// new non-zero accounts. +func (w *Wallet) selfDerive() { + w.log.Debug("Smart card wallet self-derivation started") + defer w.log.Debug("Smart card wallet self-derivation stopped") + + // Execute self-derivations until termination or error + var ( + reqc chan struct{} + errc chan error + err error + ) + for errc == nil && err == nil { + // Wait until either derivation or termination is requested + select { + case errc = <-w.deriveQuit: + // Termination requested + continue + case reqc = <-w.deriveReq: + // Account discovery requested + } + // Derivation needs a chain and device access, skip if either unavailable + w.lock.Lock() + if w.session == nil || w.deriveChain == nil { + w.lock.Unlock() + reqc <- struct{}{} + continue + } + pairing := w.Hub.pairing(w) + + // Device lock obtained, derive the next batch of accounts + var ( + paths []accounts.DerivationPath + nextAcc accounts.Account + + nextPaths = append([]accounts.DerivationPath{}, w.deriveNextPaths...) + nextAddrs = append([]common.Address{}, w.deriveNextAddrs...) + + context = context.Background() + ) + for i := 0; i < len(nextAddrs); i++ { + for empty := false; !empty; { + // Retrieve the next derived Ethereum account + if nextAddrs[i] == (common.Address{}) { + if nextAcc, err = w.session.derive(nextPaths[i]); err != nil { + w.log.Warn("Smartcard wallet account derivation failed", "err", err) + break + } + nextAddrs[i] = nextAcc.Address + } + // Check the account's status against the current chain state + var ( + balance *big.Int + nonce uint64 + ) + balance, err = w.deriveChain.BalanceAt(context, nextAddrs[i], nil) + if err != nil { + w.log.Warn("Smartcard wallet balance retrieval failed", "err", err) + break + } + nonce, err = w.deriveChain.NonceAt(context, nextAddrs[i], nil) + if err != nil { + w.log.Warn("Smartcard wallet nonce retrieval failed", "err", err) + break + } + // If the next account is empty, stop self-derivation, but add for the last base path + if balance.Sign() == 0 && nonce == 0 { + empty = true + if i < len(nextAddrs)-1 { + break + } + } + // We've just self-derived a new account, start tracking it locally + path := make(accounts.DerivationPath, len(nextPaths[i])) + copy(path[:], nextPaths[i][:]) + paths = append(paths, path) + + // Display a log message to the user for new (or previously empty accounts) + if _, known := pairing.Accounts[nextAddrs[i]]; !known || !empty || nextAddrs[i] != w.deriveNextAddrs[i] { + w.log.Info("Smartcard wallet discovered new account", "address", nextAddrs[i], "path", path, "balance", balance, "nonce", nonce) + } + pairing.Accounts[nextAddrs[i]] = path + + // Fetch the next potential account + if !empty { + nextAddrs[i] = common.Address{} + nextPaths[i][len(nextPaths[i])-1]++ + } + } + } + // If there are new accounts, write them out + if len(paths) > 0 { + err = w.Hub.setPairing(w, pairing) + } + // Shift the self-derivation forward + w.deriveNextAddrs = nextAddrs + w.deriveNextPaths = nextPaths + + // Self derivation complete, release device lock + w.lock.Unlock() + + // Notify the user of termination and loop after a bit of time (to avoid trashing) + reqc <- struct{}{} + if err == nil { + select { + case errc = <-w.deriveQuit: + // Termination requested, abort + case <-time.After(selfDeriveThrottling): + // Waited enough, willing to self-derive again + } + } + } + // In case of error, wait for termination + if err != nil { + w.log.Debug("Smartcard wallet self-derivation failed", "err", err) + errc = <-w.deriveQuit + } + errc <- err +} + +// Accounts retrieves the list of signing accounts the wallet is currently aware +// of. For hierarchical deterministic wallets, the list will not be exhaustive, +// rather only contain the accounts explicitly pinned during account derivation. +func (w *Wallet) Accounts() []accounts.Account { + // Attempt self-derivation if it's running + reqc := make(chan struct{}, 1) + select { + case w.deriveReq <- reqc: + // Self-derivation request accepted, wait for it + <-reqc + default: + // Self-derivation offline, throttled or busy, skip + } + + w.lock.Lock() + defer w.lock.Unlock() + + if pairing := w.Hub.pairing(w); pairing != nil { + ret := make([]accounts.Account, 0, len(pairing.Accounts)) + for address, path := range pairing.Accounts { + ret = append(ret, w.makeAccount(address, path)) + } + sort.Sort(accounts.AccountsByURL(ret)) + return ret + } + return nil +} + +func (w *Wallet) makeAccount(address common.Address, path accounts.DerivationPath) accounts.Account { + return accounts.Account{ + Address: address, + URL: accounts.URL{ + Scheme: w.Hub.scheme, + Path: fmt.Sprintf("%x/%s", w.PublicKey[1:3], path.String()), + }, + } +} + +// Contains returns whether an account is part of this particular wallet or not. +func (w *Wallet) Contains(account accounts.Account) bool { + if pairing := w.Hub.pairing(w); pairing != nil { + _, ok := pairing.Accounts[account.Address] + return ok + } + return false +} + +// Initialize installs a keypair generated from the provided key into the wallet. +func (w *Wallet) Initialize(seed []byte) error { + go w.selfDerive() + // DO NOT lock at this stage, as the initialize + // function relies on Status() + return w.session.initialize(seed) +} + +// Derive attempts to explicitly derive a hierarchical deterministic account at +// the specified derivation path. If requested, the derived account will be added +// to the wallet's tracked account list. +func (w *Wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { + w.lock.Lock() + defer w.lock.Unlock() + + account, err := w.session.derive(path) + if err != nil { + return accounts.Account{}, err + } + + if pin { + pairing := w.Hub.pairing(w) + pairing.Accounts[account.Address] = path + if err := w.Hub.setPairing(w, pairing); err != nil { + return accounts.Account{}, err + } + } + + return account, nil +} + +// SelfDerive sets a base account derivation path from which the wallet attempts +// to discover non zero accounts and automatically add them to list of tracked +// accounts. +// +// Note, self derivaton will increment the last component of the specified path +// opposed to decending into a child path to allow discovering accounts starting +// from non zero components. +// +// Some hardware wallets switched derivation paths through their evolution, so +// this method supports providing multiple bases to discover old user accounts +// too. Only the last base will be used to derive the next empty account. +// +// You can disable automatic account discovery by calling SelfDerive with a nil +// chain state reader. +func (w *Wallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { + w.lock.Lock() + defer w.lock.Unlock() + + w.deriveNextPaths = make([]accounts.DerivationPath, len(bases)) + for i, base := range bases { + w.deriveNextPaths[i] = make(accounts.DerivationPath, len(base)) + copy(w.deriveNextPaths[i][:], base[:]) + } + w.deriveNextAddrs = make([]common.Address, len(bases)) + w.deriveChain = chain +} + +// SignData requests the wallet to sign the hash of the given data. +// +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +// +// If the wallet requires additional authentication to sign the request (e.g. +// a password to decrypt the account, or a PIN code o verify the transaction), +// an AuthNeededError instance will be returned, containing infos for the user +// about which fields or actions are needed. The user may retry by providing +// the needed details via SignDataWithPassphrase, or by other means (e.g. unlock +// the account in a keystore). +func (w *Wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { + return w.signHash(account, crypto.Keccak256(data)) +} + +func (w *Wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) { + w.lock.Lock() + defer w.lock.Unlock() + + path, err := w.findAccountPath(account) + if err != nil { + return nil, err + } + + return w.session.sign(path, hash) +} + +// SignTx requests the wallet to sign the given transaction. +// +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +// +// If the wallet requires additional authentication to sign the request (e.g. +// a password to decrypt the account, or a PIN code o verify the transaction), +// an AuthNeededError instance will be returned, containing infos for the user +// about which fields or actions are needed. The user may retry by providing +// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock +// the account in a keystore). +func (w *Wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + signer := types.NewEIP155Signer(chainID) + hash := signer.Hash(tx) + sig, err := w.signHash(account, hash[:]) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, sig) +} + +// SignDataWithPassphrase requests the wallet to sign the given hash with the +// given passphrase as extra authentication information. +// +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +func (w *Wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { + return w.signHashWithPassphrase(account, passphrase, crypto.Keccak256(data)) +} + +func (w *Wallet) signHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) { + if !w.session.verified { + if err := w.Open(passphrase); err != nil { + return nil, err + } + } + + return w.signHash(account, hash) +} + +// SignText requests the wallet to sign the hash of a given piece of data, prefixed +// by the Ethereum prefix scheme +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +// +// If the wallet requires additional authentication to sign the request (e.g. +// a password to decrypt the account, or a PIN code o verify the transaction), +// an AuthNeededError instance will be returned, containing infos for the user +// about which fields or actions are needed. The user may retry by providing +// the needed details via SignHashWithPassphrase, or by other means (e.g. unlock +// the account in a keystore). +func (w *Wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { + return w.signHash(account, accounts.TextHash(text)) +} + +// SignTextWithPassphrase implements accounts.Wallet, attempting to sign the +// given hash with the given account using passphrase as extra authentication +func (w *Wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { + return w.signHashWithPassphrase(account, passphrase, crypto.Keccak256(accounts.TextHash(text))) +} + +// SignTxWithPassphrase requests the wallet to sign the given transaction, with the +// given passphrase as extra authentication information. +// +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +func (w *Wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + if !w.session.verified { + if err := w.Open(passphrase); err != nil { + return nil, err + } + } + return w.SignTx(account, tx, chainID) +} + +// findAccountPath returns the derivation path for the provided account. +// It first checks for the address in the list of pinned accounts, and if it is +// not found, attempts to parse the derivation path from the account's URL. +func (w *Wallet) findAccountPath(account accounts.Account) (accounts.DerivationPath, error) { + pairing := w.Hub.pairing(w) + if path, ok := pairing.Accounts[account.Address]; ok { + return path, nil + } + + // Look for the path in the URL + if account.URL.Scheme != w.Hub.scheme { + return nil, fmt.Errorf("Scheme %s does not match wallet scheme %s", account.URL.Scheme, w.Hub.scheme) + } + + parts := strings.SplitN(account.URL.Path, "/", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("Invalid URL format: %s", account.URL) + } + + if parts[0] != fmt.Sprintf("%x", w.PublicKey[1:3]) { + return nil, fmt.Errorf("URL %s is not for this wallet", account.URL) + } + + return accounts.ParseDerivationPath(parts[1]) +} + +// Session represents a secured communication session with the wallet. +type Session struct { + Wallet *Wallet // A handle to the wallet that opened the session + Channel *SecureChannelSession // A secure channel for encrypted messages + verified bool // Whether the pin has been verified in this session. +} + +// pair establishes a new pairing over this channel, using the provided secret. +func (s *Session) pair(secret []byte) (smartcardPairing, error) { + err := s.Channel.Pair(secret) + if err != nil { + return smartcardPairing{}, err + } + + return smartcardPairing{ + PublicKey: s.Wallet.PublicKey, + PairingIndex: s.Channel.PairingIndex, + PairingKey: s.Channel.PairingKey, + Accounts: make(map[common.Address]accounts.DerivationPath), + }, nil +} + +// unpair deletes an existing pairing. +func (s *Session) unpair() error { + if !s.verified { + return fmt.Errorf("Unpair requires that the PIN be verified") + } + return s.Channel.Unpair() +} + +// verifyPin unlocks a wallet with the provided pin. +func (s *Session) verifyPin(pin []byte) error { + if _, err := s.Channel.transmitEncrypted(claSCWallet, insVerifyPin, 0, 0, pin); err != nil { + return err + } + s.verified = true + return nil +} + +// unblockPin unblocks a wallet with the provided puk and resets the pin to the +// new one specified. +func (s *Session) unblockPin(pukpin []byte) error { + if _, err := s.Channel.transmitEncrypted(claSCWallet, insUnblockPin, 0, 0, pukpin); err != nil { + return err + } + s.verified = true + return nil +} + +// release releases resources associated with the channel. +func (s *Session) release() error { + return s.Wallet.card.Disconnect(pcsc.LeaveCard) +} + +// paired returns true if a valid pairing exists. +func (s *Session) paired() bool { + return s.Channel.PairingKey != nil +} + +// authenticate uses an existing pairing to establish a secure channel. +func (s *Session) authenticate(pairing smartcardPairing) error { + if !bytes.Equal(s.Wallet.PublicKey, pairing.PublicKey) { + return fmt.Errorf("Cannot pair using another wallet's pairing; %x != %x", s.Wallet.PublicKey, pairing.PublicKey) + } + s.Channel.PairingKey = pairing.PairingKey + s.Channel.PairingIndex = pairing.PairingIndex + return s.Channel.Open() +} + +// walletStatus describes a smartcard wallet's status information. +type walletStatus struct { + PinRetryCount int // Number of remaining PIN retries + PukRetryCount int // Number of remaining PUK retries + Initialized bool // Whether the card has been initialized with a private key +} + +// walletStatus fetches the wallet's status from the card. +func (s *Session) walletStatus() (*walletStatus, error) { + response, err := s.Channel.transmitEncrypted(claSCWallet, insStatus, statusP1WalletStatus, 0, nil) + if err != nil { + return nil, err + } + + status := new(walletStatus) + if _, err := asn1.UnmarshalWithParams(response.Data, status, "tag:3"); err != nil { + return nil, err + } + return status, nil +} + +// derivationPath fetches the wallet's current derivation path from the card. +func (s *Session) derivationPath() (accounts.DerivationPath, error) { + response, err := s.Channel.transmitEncrypted(claSCWallet, insStatus, statusP1Path, 0, nil) + if err != nil { + return nil, err + } + buf := bytes.NewReader(response.Data) + path := make(accounts.DerivationPath, len(response.Data)/4) + return path, binary.Read(buf, binary.BigEndian, &path) +} + +// initializeData contains data needed to initialize the smartcard wallet. +type initializeData struct { + PublicKey []byte `asn1:"tag:0"` + PrivateKey []byte `asn1:"tag:1"` + ChainCode []byte `asn1:"tag:2"` +} + +// initialize initializes the card with new key data. +func (s *Session) initialize(seed []byte) error { + // Check that the wallet isn't currently initialized, + // otherwise the key would be overwritten. + status, err := s.Wallet.Status() + if err != nil { + return err + } + if status == "Online" { + return fmt.Errorf("card is already initialized, cowardly refusing to proceed") + } + + s.Wallet.lock.Lock() + defer s.Wallet.lock.Unlock() + + // HMAC the seed to produce the private key and chain code + mac := hmac.New(sha512.New, []byte("Bitcoin seed")) + mac.Write(seed) + seed = mac.Sum(nil) + + key, err := crypto.ToECDSA(seed[:32]) + if err != nil { + return err + } + + id := initializeData{} + id.PublicKey = crypto.FromECDSAPub(&key.PublicKey) + id.PrivateKey = seed[:32] + id.ChainCode = seed[32:] + data, err := asn1.Marshal(id) + if err != nil { + return err + } + + // Nasty hack to force the top-level struct tag to be context-specific + data[0] = 0xA1 + + _, err = s.Channel.transmitEncrypted(claSCWallet, insLoadKey, 0x02, 0, data) + return err +} + +// derive derives a new HD key path on the card. +func (s *Session) derive(path accounts.DerivationPath) (accounts.Account, error) { + startingPoint, path, err := derivationpath.Decode(path.String()) + if err != nil { + return accounts.Account{}, err + } + + var p1 uint8 + switch startingPoint { + case derivationpath.StartingPointMaster: + p1 = P1DeriveKeyFromMaster + case derivationpath.StartingPointParent: + p1 = P1DeriveKeyFromParent + case derivationpath.StartingPointCurrent: + p1 = P1DeriveKeyFromCurrent + default: + return accounts.Account{}, fmt.Errorf("invalid startingPoint %d", startingPoint) + } + + data := new(bytes.Buffer) + for _, segment := range path { + if err := binary.Write(data, binary.BigEndian, segment); err != nil { + return accounts.Account{}, err + } + } + + _, err = s.Channel.transmitEncrypted(claSCWallet, insDeriveKey, p1, 0, data.Bytes()) + if err != nil { + return accounts.Account{}, err + } + + response, err := s.Channel.transmitEncrypted(claSCWallet, insSign, 0, 0, DerivationSignatureHash[:]) + if err != nil { + return accounts.Account{}, err + } + + sigdata := new(signatureData) + if _, err := asn1.UnmarshalWithParams(response.Data, sigdata, "tag:0"); err != nil { + return accounts.Account{}, err + } + rbytes, sbytes := sigdata.Signature.R.Bytes(), sigdata.Signature.S.Bytes() + sig := make([]byte, 65) + copy(sig[32-len(rbytes):32], rbytes) + copy(sig[64-len(sbytes):64], sbytes) + + pubkey, err := determinePublicKey(sig, sigdata.PublicKey) + if err != nil { + return accounts.Account{}, err + } + + pub, err := crypto.UnmarshalPubkey(pubkey) + if err != nil { + return accounts.Account{}, err + } + return s.Wallet.makeAccount(crypto.PubkeyToAddress(*pub), path), nil +} + +// keyExport contains information on an exported keypair. +type keyExport struct { + PublicKey []byte `asn1:"tag:0"` + PrivateKey []byte `asn1:"tag:1,optional"` +} + +// publicKey returns the public key for the current derivation path. +func (s *Session) publicKey() ([]byte, error) { + response, err := s.Channel.transmitEncrypted(claSCWallet, insExportKey, exportP1Any, exportP2Pubkey, nil) + if err != nil { + return nil, err + } + keys := new(keyExport) + if _, err := asn1.UnmarshalWithParams(response.Data, keys, "tag:1"); err != nil { + return nil, err + } + return keys.PublicKey, nil +} + +// signatureData contains information on a signature - the signature itself and +// the corresponding public key. +type signatureData struct { + PublicKey []byte `asn1:"tag:0"` + Signature struct { + R *big.Int + S *big.Int + } +} + +// sign asks the card to sign a message, and returns a valid signature after +// recovering the v value. +func (s *Session) sign(path accounts.DerivationPath, hash []byte) ([]byte, error) { + startTime := time.Now() + _, err := s.derive(path) + if err != nil { + return nil, err + } + deriveTime := time.Now() + + response, err := s.Channel.transmitEncrypted(claSCWallet, insSign, signP1PrecomputedHash, signP2OnlyBlock, hash) + if err != nil { + return nil, err + } + sigdata := new(signatureData) + if _, err := asn1.UnmarshalWithParams(response.Data, sigdata, "tag:0"); err != nil { + return nil, err + } + // Serialize the signature + rbytes, sbytes := sigdata.Signature.R.Bytes(), sigdata.Signature.S.Bytes() + sig := make([]byte, 65) + copy(sig[32-len(rbytes):32], rbytes) + copy(sig[64-len(sbytes):64], sbytes) + + // Recover the V value. + sig, err = makeRecoverableSignature(hash, sig, sigdata.PublicKey) + if err != nil { + return nil, err + } + log.Debug("Signed using smartcard", "deriveTime", deriveTime.Sub(startTime), "signingTime", time.Since(deriveTime)) + + return sig, nil +} + +// determinePublicKey uses a signature and the X component of a public key to +// recover the entire public key. +func determinePublicKey(sig, pubkeyX []byte) ([]byte, error) { + for v := 0; v < 2; v++ { + sig[64] = byte(v) + pubkey, err := crypto.Ecrecover(DerivationSignatureHash[:], sig) + if err == nil { + if bytes.Equal(pubkey, pubkeyX) { + return pubkey, nil + } + } else if v == 1 || err != secp256k1.ErrRecoverFailed { + return nil, err + } + } + return nil, ErrPubkeyMismatch +} + +// makeRecoverableSignature uses a signature and an expected public key to +// recover the v value and produce a recoverable signature. +func makeRecoverableSignature(hash, sig, expectedPubkey []byte) ([]byte, error) { + for v := 0; v < 2; v++ { + sig[64] = byte(v) + pubkey, err := crypto.Ecrecover(hash, sig) + if err == nil { + if bytes.Equal(pubkey, expectedPubkey) { + return sig, nil + } + } else if v == 1 || err != secp256k1.ErrRecoverFailed { + return nil, err + } + } + return nil, ErrPubkeyMismatch +} diff --git a/accounts/sort.go b/accounts/sort.go new file mode 100644 index 000000000..f46762114 --- /dev/null +++ b/accounts/sort.go @@ -0,0 +1,31 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package accounts + +// AccountsByURL implements sort.Interface for []Account based on the URL field. +type AccountsByURL []Account + +func (a AccountsByURL) Len() int { return len(a) } +func (a AccountsByURL) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a AccountsByURL) Less(i, j int) bool { return a[i].URL.Cmp(a[j].URL) < 0 } + +// WalletsByURL implements sort.Interface for []Wallet based on the URL field. +type WalletsByURL []Wallet + +func (w WalletsByURL) Len() int { return len(w) } +func (w WalletsByURL) Swap(i, j int) { w[i], w[j] = w[j], w[i] } +func (w WalletsByURL) Less(i, j int) bool { return w[i].URL().Cmp(w[j].URL()) < 0 } diff --git a/accounts/url.go b/accounts/url.go new file mode 100644 index 000000000..a5add1021 --- /dev/null +++ b/accounts/url.go @@ -0,0 +1,104 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package accounts + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +// URL represents the canonical identification URL of a wallet or account. +// +// It is a simplified version of url.URL, with the important limitations (which +// are considered features here) that it contains value-copyable components only, +// as well as that it doesn't do any URL encoding/decoding of special characters. +// +// The former is important to allow an account to be copied without leaving live +// references to the original version, whereas the latter is important to ensure +// one single canonical form opposed to many allowed ones by the RFC 3986 spec. +// +// As such, these URLs should not be used outside of the scope of an Ethereum +// wallet or account. +type URL struct { + Scheme string // Protocol scheme to identify a capable account backend + Path string // Path for the backend to identify a unique entity +} + +// parseURL converts a user supplied URL into the accounts specific structure. +func parseURL(url string) (URL, error) { + parts := strings.Split(url, "://") + if len(parts) != 2 || parts[0] == "" { + return URL{}, errors.New("protocol scheme missing") + } + return URL{ + Scheme: parts[0], + Path: parts[1], + }, nil +} + +// String implements the stringer interface. +func (u URL) String() string { + if u.Scheme != "" { + return fmt.Sprintf("%s://%s", u.Scheme, u.Path) + } + return u.Path +} + +// TerminalString implements the log.TerminalStringer interface. +func (u URL) TerminalString() string { + url := u.String() + if len(url) > 32 { + return url[:31] + "…" + } + return url +} + +// MarshalJSON implements the json.Marshaller interface. +func (u URL) MarshalJSON() ([]byte, error) { + return json.Marshal(u.String()) +} + +// UnmarshalJSON parses url. +func (u *URL) UnmarshalJSON(input []byte) error { + var textURL string + err := json.Unmarshal(input, &textURL) + if err != nil { + return err + } + url, err := parseURL(textURL) + if err != nil { + return err + } + u.Scheme = url.Scheme + u.Path = url.Path + return nil +} + +// Cmp compares x and y and returns: +// +// -1 if x < y +// 0 if x == y +// +1 if x > y +// +func (u URL) Cmp(url URL) int { + if u.Scheme == url.Scheme { + return strings.Compare(u.Path, url.Path) + } + return strings.Compare(u.Scheme, url.Scheme) +} diff --git a/accounts/url_test.go b/accounts/url_test.go new file mode 100644 index 000000000..802772871 --- /dev/null +++ b/accounts/url_test.go @@ -0,0 +1,96 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package accounts + +import ( + "testing" +) + +func TestURLParsing(t *testing.T) { + url, err := parseURL("https://ethereum.org") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if url.Scheme != "https" { + t.Errorf("expected: %v, got: %v", "https", url.Scheme) + } + if url.Path != "ethereum.org" { + t.Errorf("expected: %v, got: %v", "ethereum.org", url.Path) + } + + _, err = parseURL("ethereum.org") + if err == nil { + t.Error("expected err, got: nil") + } +} + +func TestURLString(t *testing.T) { + url := URL{Scheme: "https", Path: "ethereum.org"} + if url.String() != "https://ethereum.org" { + t.Errorf("expected: %v, got: %v", "https://ethereum.org", url.String()) + } + + url = URL{Scheme: "", Path: "ethereum.org"} + if url.String() != "ethereum.org" { + t.Errorf("expected: %v, got: %v", "ethereum.org", url.String()) + } +} + +func TestURLMarshalJSON(t *testing.T) { + url := URL{Scheme: "https", Path: "ethereum.org"} + json, err := url.MarshalJSON() + if err != nil { + t.Errorf("unexpcted error: %v", err) + } + if string(json) != "\"https://ethereum.org\"" { + t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json)) + } +} + +func TestURLUnmarshalJSON(t *testing.T) { + url := &URL{} + err := url.UnmarshalJSON([]byte("\"https://ethereum.org\"")) + if err != nil { + t.Errorf("unexpcted error: %v", err) + } + if url.Scheme != "https" { + t.Errorf("expected: %v, got: %v", "https", url.Scheme) + } + if url.Path != "ethereum.org" { + t.Errorf("expected: %v, got: %v", "https", url.Path) + } +} + +func TestURLComparison(t *testing.T) { + tests := []struct { + urlA URL + urlB URL + expect int + }{ + {URL{"https", "ethereum.org"}, URL{"https", "ethereum.org"}, 0}, + {URL{"http", "ethereum.org"}, URL{"https", "ethereum.org"}, -1}, + {URL{"https", "ethereum.org/a"}, URL{"https", "ethereum.org"}, 1}, + {URL{"https", "abc.org"}, URL{"https", "ethereum.org"}, -1}, + } + + for i, tt := range tests { + result := tt.urlA.Cmp(tt.urlB) + if result != tt.expect { + t.Errorf("test %d: cmp mismatch: expected: %d, got: %d", i, tt.expect, result) + } + } +} diff --git a/accounts/usbwallet/hub.go b/accounts/usbwallet/hub.go new file mode 100644 index 000000000..a91340b4f --- /dev/null +++ b/accounts/usbwallet/hub.go @@ -0,0 +1,240 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package usbwallet + +import ( + "errors" + "runtime" + "sync" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/karalabe/hid" +) + +// LedgerScheme is the protocol scheme prefixing account and wallet URLs. +const LedgerScheme = "ledger" + +// TrezorScheme is the protocol scheme prefixing account and wallet URLs. +const TrezorScheme = "trezor" + +// refreshCycle is the maximum time between wallet refreshes (if USB hotplug +// notifications don't work). +const refreshCycle = time.Second + +// refreshThrottling is the minimum time between wallet refreshes to avoid USB +// trashing. +const refreshThrottling = 500 * time.Millisecond + +// Hub is a accounts.Backend that can find and handle generic USB hardware wallets. +type Hub struct { + scheme string // Protocol scheme prefixing account and wallet URLs. + vendorID uint16 // USB vendor identifier used for device discovery + productIDs []uint16 // USB product identifiers used for device discovery + usageID uint16 // USB usage page identifier used for macOS device discovery + endpointID int // USB endpoint identifier used for non-macOS device discovery + makeDriver func(log.Logger) driver // Factory method to construct a vendor specific driver + + refreshed time.Time // Time instance when the list of wallets was last refreshed + wallets []accounts.Wallet // List of USB wallet devices currently tracking + updateFeed event.Feed // Event feed to notify wallet additions/removals + updateScope event.SubscriptionScope // Subscription scope tracking current live listeners + updating bool // Whether the event notification loop is running + + quit chan chan error + + stateLock sync.RWMutex // Protects the internals of the hub from racey access + + // TODO(karalabe): remove if hotplug lands on Windows + commsPend int // Number of operations blocking enumeration + commsLock sync.Mutex // Lock protecting the pending counter and enumeration +} + +// NewLedgerHub creates a new hardware wallet manager for Ledger devices. +func NewLedgerHub() (*Hub, error) { + return newHub(LedgerScheme, 0x2c97, []uint16{0x0000 /* Ledger Blue */, 0x0001 /* Ledger Nano S */}, 0xffa0, 0, newLedgerDriver) +} + +// NewTrezorHub creates a new hardware wallet manager for Trezor devices. +func NewTrezorHub() (*Hub, error) { + return newHub(TrezorScheme, 0x534c, []uint16{0x0001 /* Trezor 1 */}, 0xff00, 0, newTrezorDriver) +} + +// newHub creates a new hardware wallet manager for generic USB devices. +func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, endpointID int, makeDriver func(log.Logger) driver) (*Hub, error) { + if !hid.Supported() { + return nil, errors.New("unsupported platform") + } + hub := &Hub{ + scheme: scheme, + vendorID: vendorID, + productIDs: productIDs, + usageID: usageID, + endpointID: endpointID, + makeDriver: makeDriver, + quit: make(chan chan error), + } + hub.refreshWallets() + return hub, nil +} + +// Wallets implements accounts.Backend, returning all the currently tracked USB +// devices that appear to be hardware wallets. +func (hub *Hub) Wallets() []accounts.Wallet { + // Make sure the list of wallets is up to date + hub.refreshWallets() + + hub.stateLock.RLock() + defer hub.stateLock.RUnlock() + + cpy := make([]accounts.Wallet, len(hub.wallets)) + copy(cpy, hub.wallets) + return cpy +} + +// refreshWallets scans the USB devices attached to the machine and updates the +// list of wallets based on the found devices. +func (hub *Hub) refreshWallets() { + // Don't scan the USB like crazy it the user fetches wallets in a loop + hub.stateLock.RLock() + elapsed := time.Since(hub.refreshed) + hub.stateLock.RUnlock() + + if elapsed < refreshThrottling { + return + } + // Retrieve the current list of USB wallet devices + var devices []hid.DeviceInfo + + if runtime.GOOS == "linux" { + // hidapi on Linux opens the device during enumeration to retrieve some infos, + // breaking the Ledger protocol if that is waiting for user confirmation. This + // is a bug acknowledged at Ledger, but it won't be fixed on old devices so we + // need to prevent concurrent comms ourselves. The more elegant solution would + // be to ditch enumeration in favor of hotplug events, but that don't work yet + // on Windows so if we need to hack it anyway, this is more elegant for now. + hub.commsLock.Lock() + if hub.commsPend > 0 { // A confirmation is pending, don't refresh + hub.commsLock.Unlock() + return + } + } + for _, info := range hid.Enumerate(hub.vendorID, 0) { + for _, id := range hub.productIDs { + if info.ProductID == id && (info.UsagePage == hub.usageID || info.Interface == hub.endpointID) { + devices = append(devices, info) + break + } + } + } + if runtime.GOOS == "linux" { + // See rationale before the enumeration why this is needed and only on Linux. + hub.commsLock.Unlock() + } + // Transform the current list of wallets into the new one + hub.stateLock.Lock() + + var ( + wallets = make([]accounts.Wallet, 0, len(devices)) + events []accounts.WalletEvent + ) + + for _, device := range devices { + url := accounts.URL{Scheme: hub.scheme, Path: device.Path} + + // Drop wallets in front of the next device or those that failed for some reason + for len(hub.wallets) > 0 { + // Abort if we're past the current device and found an operational one + _, failure := hub.wallets[0].Status() + if hub.wallets[0].URL().Cmp(url) >= 0 || failure == nil { + break + } + // Drop the stale and failed devices + events = append(events, accounts.WalletEvent{Wallet: hub.wallets[0], Kind: accounts.WalletDropped}) + hub.wallets = hub.wallets[1:] + } + // If there are no more wallets or the device is before the next, wrap new wallet + if len(hub.wallets) == 0 || hub.wallets[0].URL().Cmp(url) > 0 { + logger := log.New("url", url) + wallet := &wallet{hub: hub, driver: hub.makeDriver(logger), url: &url, info: device, log: logger} + + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) + wallets = append(wallets, wallet) + continue + } + // If the device is the same as the first wallet, keep it + if hub.wallets[0].URL().Cmp(url) == 0 { + wallets = append(wallets, hub.wallets[0]) + hub.wallets = hub.wallets[1:] + continue + } + } + // Drop any leftover wallets and set the new batch + for _, wallet := range hub.wallets { + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) + } + hub.refreshed = time.Now() + hub.wallets = wallets + hub.stateLock.Unlock() + + // Fire all wallet events and return + for _, event := range events { + hub.updateFeed.Send(event) + } +} + +// Subscribe implements accounts.Backend, creating an async subscription to +// receive notifications on the addition or removal of USB wallets. +func (hub *Hub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { + // We need the mutex to reliably start/stop the update loop + hub.stateLock.Lock() + defer hub.stateLock.Unlock() + + // Subscribe the caller and track the subscriber count + sub := hub.updateScope.Track(hub.updateFeed.Subscribe(sink)) + + // Subscribers require an active notification loop, start it + if !hub.updating { + hub.updating = true + go hub.updater() + } + return sub +} + +// updater is responsible for maintaining an up-to-date list of wallets managed +// by the USB hub, and for firing wallet addition/removal events. +func (hub *Hub) updater() { + for { + // TODO: Wait for a USB hotplug event (not supported yet) or a refresh timeout + // <-hub.changes + time.Sleep(refreshCycle) + + // Run the wallet refresher + hub.refreshWallets() + + // If all our subscribers left, stop the updater + hub.stateLock.Lock() + if hub.updateScope.Count() == 0 { + hub.updating = false + hub.stateLock.Unlock() + return + } + hub.stateLock.Unlock() + } +} diff --git a/accounts/usbwallet/ledger.go b/accounts/usbwallet/ledger.go new file mode 100644 index 000000000..c30903b5b --- /dev/null +++ b/accounts/usbwallet/ledger.go @@ -0,0 +1,464 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// This file contains the implementation for interacting with the Ledger hardware +// wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo: +// https://raw.githubusercontent.com/LedgerHQ/blue-app-eth/master/doc/ethapp.asc + +package usbwallet + +import ( + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +// ledgerOpcode is an enumeration encoding the supported Ledger opcodes. +type ledgerOpcode byte + +// ledgerParam1 is an enumeration encoding the supported Ledger parameters for +// specific opcodes. The same parameter values may be reused between opcodes. +type ledgerParam1 byte + +// ledgerParam2 is an enumeration encoding the supported Ledger parameters for +// specific opcodes. The same parameter values may be reused between opcodes. +type ledgerParam2 byte + +const ( + ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path + ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters + ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration + + ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet + ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing + ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing + ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address +) + +// errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange +// if the device replies with a mismatching header. This usually means the device +// is in browser mode. +var errLedgerReplyInvalidHeader = errors.New("ledger: invalid reply header") + +// errLedgerInvalidVersionReply is the error message returned by a Ledger version retrieval +// when a response does arrive, but it does not contain the expected data. +var errLedgerInvalidVersionReply = errors.New("ledger: invalid version reply") + +// ledgerDriver implements the communication with a Ledger hardware wallet. +type ledgerDriver struct { + device io.ReadWriter // USB device connection to communicate through + version [3]byte // Current version of the Ledger firmware (zero if app is offline) + browser bool // Flag whether the Ledger is in browser mode (reply channel mismatch) + failure error // Any failure that would make the device unusable + log log.Logger // Contextual logger to tag the ledger with its id +} + +// newLedgerDriver creates a new instance of a Ledger USB protocol driver. +func newLedgerDriver(logger log.Logger) driver { + return &ledgerDriver{ + log: logger, + } +} + +// Status implements usbwallet.driver, returning various states the Ledger can +// currently be in. +func (w *ledgerDriver) Status() (string, error) { + if w.failure != nil { + return fmt.Sprintf("Failed: %v", w.failure), w.failure + } + if w.browser { + return "Ethereum app in browser mode", w.failure + } + if w.offline() { + return "Ethereum app offline", w.failure + } + return fmt.Sprintf("Ethereum app v%d.%d.%d online", w.version[0], w.version[1], w.version[2]), w.failure +} + +// offline returns whether the wallet and the Ethereum app is offline or not. +// +// The method assumes that the state lock is held! +func (w *ledgerDriver) offline() bool { + return w.version == [3]byte{0, 0, 0} +} + +// Open implements usbwallet.driver, attempting to initialize the connection to the +// Ledger hardware wallet. The Ledger does not require a user passphrase, so that +// parameter is silently discarded. +func (w *ledgerDriver) Open(device io.ReadWriter, passphrase string) error { + w.device, w.failure = device, nil + + _, err := w.ledgerDerive(accounts.DefaultBaseDerivationPath) + if err != nil { + // Ethereum app is not running or in browser mode, nothing more to do, return + if err == errLedgerReplyInvalidHeader { + w.browser = true + } + return nil + } + // Try to resolve the Ethereum app's version, will fail prior to v1.0.2 + if w.version, err = w.ledgerVersion(); err != nil { + w.version = [3]byte{1, 0, 0} // Assume worst case, can't verify if v1.0.0 or v1.0.1 + } + return nil +} + +// Close implements usbwallet.driver, cleaning up and metadata maintained within +// the Ledger driver. +func (w *ledgerDriver) Close() error { + w.browser, w.version = false, [3]byte{} + return nil +} + +// Heartbeat implements usbwallet.driver, performing a sanity check against the +// Ledger to see if it's still online. +func (w *ledgerDriver) Heartbeat() error { + if _, err := w.ledgerVersion(); err != nil && err != errLedgerInvalidVersionReply { + w.failure = err + return err + } + return nil +} + +// Derive implements usbwallet.driver, sending a derivation request to the Ledger +// and returning the Ethereum address located on that derivation path. +func (w *ledgerDriver) Derive(path accounts.DerivationPath) (common.Address, error) { + return w.ledgerDerive(path) +} + +// SignTx implements usbwallet.driver, sending the transaction to the Ledger and +// waiting for the user to confirm or deny the transaction. +// +// Note, if the version of the Ethereum application running on the Ledger wallet is +// too old to sign EIP-155 transactions, but such is requested nonetheless, an error +// will be returned opposed to silently signing in Homestead mode. +func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { + // If the Ethereum app doesn't run, abort + if w.offline() { + return common.Address{}, nil, accounts.ErrWalletClosed + } + // Ensure the wallet is capable of signing the given transaction + if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 { + return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2]) + } + // All infos gathered and metadata checks out, request signing + return w.ledgerSign(path, tx, chainID) +} + +// ledgerVersion retrieves the current version of the Ethereum wallet app running +// on the Ledger wallet. +// +// The version retrieval protocol is defined as follows: +// +// CLA | INS | P1 | P2 | Lc | Le +// ----+-----+----+----+----+--- +// E0 | 06 | 00 | 00 | 00 | 04 +// +// With no input data, and the output data being: +// +// Description | Length +// ---------------------------------------------------+-------- +// Flags 01: arbitrary data signature enabled by user | 1 byte +// Application major version | 1 byte +// Application minor version | 1 byte +// Application patch version | 1 byte +func (w *ledgerDriver) ledgerVersion() ([3]byte, error) { + // Send the request and wait for the response + reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil) + if err != nil { + return [3]byte{}, err + } + if len(reply) != 4 { + return [3]byte{}, errLedgerInvalidVersionReply + } + // Cache the version for future reference + var version [3]byte + copy(version[:], reply[1:]) + return version, nil +} + +// ledgerDerive retrieves the currently active Ethereum address from a Ledger +// wallet at the specified derivation path. +// +// The address derivation protocol is defined as follows: +// +// CLA | INS | P1 | P2 | Lc | Le +// ----+-----+----+----+-----+--- +// E0 | 02 | 00 return address +// 01 display address and confirm before returning +// | 00: do not return the chain code +// | 01: return the chain code +// | var | 00 +// +// Where the input data is: +// +// Description | Length +// -------------------------------------------------+-------- +// Number of BIP 32 derivations to perform (max 10) | 1 byte +// First derivation index (big endian) | 4 bytes +// ... | 4 bytes +// Last derivation index (big endian) | 4 bytes +// +// And the output data is: +// +// Description | Length +// ------------------------+------------------- +// Public Key length | 1 byte +// Uncompressed Public Key | arbitrary +// Ethereum address length | 1 byte +// Ethereum address | 40 bytes hex ascii +// Chain code if requested | 32 bytes +func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, error) { + // Flatten the derivation path into the Ledger request + path := make([]byte, 1+4*len(derivationPath)) + path[0] = byte(len(derivationPath)) + for i, component := range derivationPath { + binary.BigEndian.PutUint32(path[1+4*i:], component) + } + // Send the request and wait for the response + reply, err := w.ledgerExchange(ledgerOpRetrieveAddress, ledgerP1DirectlyFetchAddress, ledgerP2DiscardAddressChainCode, path) + if err != nil { + return common.Address{}, err + } + // Discard the public key, we don't need that for now + if len(reply) < 1 || len(reply) < 1+int(reply[0]) { + return common.Address{}, errors.New("reply lacks public key entry") + } + reply = reply[1+int(reply[0]):] + + // Extract the Ethereum hex address string + if len(reply) < 1 || len(reply) < 1+int(reply[0]) { + return common.Address{}, errors.New("reply lacks address entry") + } + hexstr := reply[1 : 1+int(reply[0])] + + // Decode the hex sting into an Ethereum address and return + var address common.Address + if _, err = hex.Decode(address[:], hexstr); err != nil { + return common.Address{}, err + } + return address, nil +} + +// ledgerSign sends the transaction to the Ledger wallet, and waits for the user +// to confirm or deny the transaction. +// +// The transaction signing protocol is defined as follows: +// +// CLA | INS | P1 | P2 | Lc | Le +// ----+-----+----+----+-----+--- +// E0 | 04 | 00: first transaction data block +// 80: subsequent transaction data block +// | 00 | variable | variable +// +// Where the input for the first transaction block (first 255 bytes) is: +// +// Description | Length +// -------------------------------------------------+---------- +// Number of BIP 32 derivations to perform (max 10) | 1 byte +// First derivation index (big endian) | 4 bytes +// ... | 4 bytes +// Last derivation index (big endian) | 4 bytes +// RLP transaction chunk | arbitrary +// +// And the input for subsequent transaction blocks (first 255 bytes) are: +// +// Description | Length +// ----------------------+---------- +// RLP transaction chunk | arbitrary +// +// And the output data is: +// +// Description | Length +// ------------+--------- +// signature V | 1 byte +// signature R | 32 bytes +// signature S | 32 bytes +func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { + // Flatten the derivation path into the Ledger request + path := make([]byte, 1+4*len(derivationPath)) + path[0] = byte(len(derivationPath)) + for i, component := range derivationPath { + binary.BigEndian.PutUint32(path[1+4*i:], component) + } + // Create the transaction RLP based on whether legacy or EIP155 signing was requested + var ( + txrlp []byte + err error + ) + if chainID == nil { + if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data()}); err != nil { + return common.Address{}, nil, err + } + } else { + if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil { + return common.Address{}, nil, err + } + } + payload := append(path, txrlp...) + + // Send the request and wait for the response + var ( + op = ledgerP1InitTransactionData + reply []byte + ) + for len(payload) > 0 { + // Calculate the size of the next data chunk + chunk := 255 + if chunk > len(payload) { + chunk = len(payload) + } + // Send the chunk over, ensuring it's processed correctly + reply, err = w.ledgerExchange(ledgerOpSignTransaction, op, 0, payload[:chunk]) + if err != nil { + return common.Address{}, nil, err + } + // Shift the payload and ensure subsequent chunks are marked as such + payload = payload[chunk:] + op = ledgerP1ContTransactionData + } + // Extract the Ethereum signature and do a sanity validation + if len(reply) != 65 { + return common.Address{}, nil, errors.New("reply lacks signature") + } + signature := append(reply[1:], reply[0]) + + // Create the correct signer and signature transform based on the chain ID + var signer types.Signer + if chainID == nil { + signer = new(types.HomesteadSigner) + } else { + signer = types.NewEIP155Signer(chainID) + signature[64] -= byte(chainID.Uint64()*2 + 35) + } + signed, err := tx.WithSignature(signer, signature) + if err != nil { + return common.Address{}, nil, err + } + sender, err := types.Sender(signer, signed) + if err != nil { + return common.Address{}, nil, err + } + return sender, signed, nil +} + +// ledgerExchange performs a data exchange with the Ledger wallet, sending it a +// message and retrieving the response. +// +// The common transport header is defined as follows: +// +// Description | Length +// --------------------------------------+---------- +// Communication channel ID (big endian) | 2 bytes +// Command tag | 1 byte +// Packet sequence index (big endian) | 2 bytes +// Payload | arbitrary +// +// The Communication channel ID allows commands multiplexing over the same +// physical link. It is not used for the time being, and should be set to 0101 +// to avoid compatibility issues with implementations ignoring a leading 00 byte. +// +// The Command tag describes the message content. Use TAG_APDU (0x05) for standard +// APDU payloads, or TAG_PING (0x02) for a simple link test. +// +// The Packet sequence index describes the current sequence for fragmented payloads. +// The first fragment index is 0x00. +// +// APDU Command payloads are encoded as follows: +// +// Description | Length +// ----------------------------------- +// APDU length (big endian) | 2 bytes +// APDU CLA | 1 byte +// APDU INS | 1 byte +// APDU P1 | 1 byte +// APDU P2 | 1 byte +// APDU length | 1 byte +// Optional APDU data | arbitrary +func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) { + // Construct the message payload, possibly split into multiple chunks + apdu := make([]byte, 2, 7+len(data)) + + binary.BigEndian.PutUint16(apdu, uint16(5+len(data))) + apdu = append(apdu, []byte{0xe0, byte(opcode), byte(p1), byte(p2), byte(len(data))}...) + apdu = append(apdu, data...) + + // Stream all the chunks to the device + header := []byte{0x01, 0x01, 0x05, 0x00, 0x00} // Channel ID and command tag appended + chunk := make([]byte, 64) + space := len(chunk) - len(header) + + for i := 0; len(apdu) > 0; i++ { + // Construct the new message to stream + chunk = append(chunk[:0], header...) + binary.BigEndian.PutUint16(chunk[3:], uint16(i)) + + if len(apdu) > space { + chunk = append(chunk, apdu[:space]...) + apdu = apdu[space:] + } else { + chunk = append(chunk, apdu...) + apdu = nil + } + // Send over to the device + w.log.Trace("Data chunk sent to the Ledger", "chunk", hexutil.Bytes(chunk)) + if _, err := w.device.Write(chunk); err != nil { + return nil, err + } + } + // Stream the reply back from the wallet in 64 byte chunks + var reply []byte + chunk = chunk[:64] // Yeah, we surely have enough space + for { + // Read the next chunk from the Ledger wallet + if _, err := io.ReadFull(w.device, chunk); err != nil { + return nil, err + } + w.log.Trace("Data chunk received from the Ledger", "chunk", hexutil.Bytes(chunk)) + + // Make sure the transport header matches + if chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != 0x05 { + return nil, errLedgerReplyInvalidHeader + } + // If it's the first chunk, retrieve the total message length + var payload []byte + + if chunk[3] == 0x00 && chunk[4] == 0x00 { + reply = make([]byte, 0, int(binary.BigEndian.Uint16(chunk[5:7]))) + payload = chunk[7:] + } else { + payload = chunk[5:] + } + // Append to the reply and stop when filled up + if left := cap(reply) - len(reply); left > len(payload) { + reply = append(reply, payload...) + } else { + reply = append(reply, payload[:left]...) + break + } + } + return reply[:len(reply)-2], nil +} diff --git a/accounts/usbwallet/trezor.go b/accounts/usbwallet/trezor.go new file mode 100644 index 000000000..31c2a7d8a --- /dev/null +++ b/accounts/usbwallet/trezor.go @@ -0,0 +1,356 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// This file contains the implementation for interacting with the Trezor hardware +// wallets. The wire protocol spec can be found on the SatoshiLabs website: +// https://doc.satoshilabs.com/trezor-tech/api-protobuf.html + +package usbwallet + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/golang/protobuf/proto" +) + +// ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In +// this case, the calling application should display a pinpad and send back the +// encoded passphrase. +var ErrTrezorPINNeeded = errors.New("trezor: pin needed") + +// ErrTrezorPassphraseNeeded is returned if opening the trezor requires a passphrase +var ErrTrezorPassphraseNeeded = errors.New("trezor: passphrase needed") + +// errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange +// if the device replies with a mismatching header. This usually means the device +// is in browser mode. +var errTrezorReplyInvalidHeader = errors.New("trezor: invalid reply header") + +// trezorDriver implements the communication with a Trezor hardware wallet. +type trezorDriver struct { + device io.ReadWriter // USB device connection to communicate through + version [3]uint32 // Current version of the Trezor firmware + label string // Current textual label of the Trezor device + pinwait bool // Flags whether the device is waiting for PIN entry + passphrasewait bool // Flags whether the device is waiting for passphrase entry + failure error // Any failure that would make the device unusable + log log.Logger // Contextual logger to tag the trezor with its id +} + +// newTrezorDriver creates a new instance of a Trezor USB protocol driver. +func newTrezorDriver(logger log.Logger) driver { + return &trezorDriver{ + log: logger, + } +} + +// Status implements accounts.Wallet, always whether the Trezor is opened, closed +// or whether the Ethereum app was not started on it. +func (w *trezorDriver) Status() (string, error) { + if w.failure != nil { + return fmt.Sprintf("Failed: %v", w.failure), w.failure + } + if w.device == nil { + return "Closed", w.failure + } + if w.pinwait { + return fmt.Sprintf("Trezor v%d.%d.%d '%s' waiting for PIN", w.version[0], w.version[1], w.version[2], w.label), w.failure + } + return fmt.Sprintf("Trezor v%d.%d.%d '%s' online", w.version[0], w.version[1], w.version[2], w.label), w.failure +} + +// Open implements usbwallet.driver, attempting to initialize the connection to +// the Trezor hardware wallet. Initializing the Trezor is a two or three phase operation: +// * The first phase is to initialize the connection and read the wallet's +// features. This phase is invoked if the provided passphrase is empty. The +// device will display the pinpad as a result and will return an appropriate +// error to notify the user that a second open phase is needed. +// * The second phase is to unlock access to the Trezor, which is done by the +// user actually providing a passphrase mapping a keyboard keypad to the pin +// number of the user (shuffled according to the pinpad displayed). +// * If needed the device will ask for passphrase which will require calling +// open again with the actual passphrase (3rd phase) +func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error { + w.device, w.failure = device, nil + + // If phase 1 is requested, init the connection and wait for user callback + if passphrase == "" && !w.passphrasewait { + // If we're already waiting for a PIN entry, insta-return + if w.pinwait { + return ErrTrezorPINNeeded + } + // Initialize a connection to the device + features := new(trezor.Features) + if _, err := w.trezorExchange(&trezor.Initialize{}, features); err != nil { + return err + } + w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()} + w.label = features.GetLabel() + + // Do a manual ping, forcing the device to ask for its PIN and Passphrase + askPin := true + askPassphrase := true + res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin, PassphraseProtection: &askPassphrase}, new(trezor.PinMatrixRequest), new(trezor.PassphraseRequest), new(trezor.Success)) + if err != nil { + return err + } + // Only return the PIN request if the device wasn't unlocked until now + switch res { + case 0: + w.pinwait = true + return ErrTrezorPINNeeded + case 1: + w.pinwait = false + w.passphrasewait = true + return ErrTrezorPassphraseNeeded + case 2: + return nil // responded with trezor.Success + } + } + // Phase 2 requested with actual PIN entry + if w.pinwait { + w.pinwait = false + res, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success), new(trezor.PassphraseRequest)) + if err != nil { + w.failure = err + return err + } + if res == 1 { + w.passphrasewait = true + return ErrTrezorPassphraseNeeded + } + } else if w.passphrasewait { + w.passphrasewait = false + if _, err := w.trezorExchange(&trezor.PassphraseAck{Passphrase: &passphrase}, new(trezor.Success)); err != nil { + w.failure = err + return err + } + } + + return nil +} + +// Close implements usbwallet.driver, cleaning up and metadata maintained within +// the Trezor driver. +func (w *trezorDriver) Close() error { + w.version, w.label, w.pinwait = [3]uint32{}, "", false + return nil +} + +// Heartbeat implements usbwallet.driver, performing a sanity check against the +// Trezor to see if it's still online. +func (w *trezorDriver) Heartbeat() error { + if _, err := w.trezorExchange(&trezor.Ping{}, new(trezor.Success)); err != nil { + w.failure = err + return err + } + return nil +} + +// Derive implements usbwallet.driver, sending a derivation request to the Trezor +// and returning the Ethereum address located on that derivation path. +func (w *trezorDriver) Derive(path accounts.DerivationPath) (common.Address, error) { + return w.trezorDerive(path) +} + +// SignTx implements usbwallet.driver, sending the transaction to the Trezor and +// waiting for the user to confirm or deny the transaction. +func (w *trezorDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { + if w.device == nil { + return common.Address{}, nil, accounts.ErrWalletClosed + } + return w.trezorSign(path, tx, chainID) +} + +// trezorDerive sends a derivation request to the Trezor device and returns the +// Ethereum address located on that path. +func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, error) { + address := new(trezor.EthereumAddress) + if _, err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil { + return common.Address{}, err + } + return common.BytesToAddress(address.GetAddress()), nil +} + +// trezorSign sends the transaction to the Trezor wallet, and waits for the user +// to confirm or deny the transaction. +func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { + // Create the transaction initiation message + data := tx.Data() + length := uint32(len(data)) + + request := &trezor.EthereumSignTx{ + AddressN: derivationPath, + Nonce: new(big.Int).SetUint64(tx.Nonce()).Bytes(), + GasPrice: tx.GasPrice().Bytes(), + GasLimit: new(big.Int).SetUint64(tx.Gas()).Bytes(), + Value: tx.Value().Bytes(), + DataLength: &length, + } + if to := tx.To(); to != nil { + request.To = (*to)[:] // Non contract deploy, set recipient explicitly + } + if length > 1024 { // Send the data chunked if that was requested + request.DataInitialChunk, data = data[:1024], data[1024:] + } else { + request.DataInitialChunk, data = data, nil + } + if chainID != nil { // EIP-155 transaction, set chain ID explicitly (only 32 bit is supported!?) + id := uint32(chainID.Int64()) + request.ChainId = &id + } + // Send the initiation message and stream content until a signature is returned + response := new(trezor.EthereumTxRequest) + if _, err := w.trezorExchange(request, response); err != nil { + return common.Address{}, nil, err + } + for response.DataLength != nil && int(*response.DataLength) <= len(data) { + chunk := data[:*response.DataLength] + data = data[*response.DataLength:] + + if _, err := w.trezorExchange(&trezor.EthereumTxAck{DataChunk: chunk}, response); err != nil { + return common.Address{}, nil, err + } + } + // Extract the Ethereum signature and do a sanity validation + if len(response.GetSignatureR()) == 0 || len(response.GetSignatureS()) == 0 || response.GetSignatureV() == 0 { + return common.Address{}, nil, errors.New("reply lacks signature") + } + signature := append(append(response.GetSignatureR(), response.GetSignatureS()...), byte(response.GetSignatureV())) + + // Create the correct signer and signature transform based on the chain ID + var signer types.Signer + if chainID == nil { + signer = new(types.HomesteadSigner) + } else { + signer = types.NewEIP155Signer(chainID) + signature[64] -= byte(chainID.Uint64()*2 + 35) + } + // Inject the final signature into the transaction and sanity check the sender + signed, err := tx.WithSignature(signer, signature) + if err != nil { + return common.Address{}, nil, err + } + sender, err := types.Sender(signer, signed) + if err != nil { + return common.Address{}, nil, err + } + return sender, signed, nil +} + +// trezorExchange performs a data exchange with the Trezor wallet, sending it a +// message and retrieving the response. If multiple responses are possible, the +// method will also return the index of the destination object used. +func (w *trezorDriver) trezorExchange(req proto.Message, results ...proto.Message) (int, error) { + // Construct the original message payload to chunk up + data, err := proto.Marshal(req) + if err != nil { + return 0, err + } + payload := make([]byte, 8+len(data)) + copy(payload, []byte{0x23, 0x23}) + binary.BigEndian.PutUint16(payload[2:], trezor.Type(req)) + binary.BigEndian.PutUint32(payload[4:], uint32(len(data))) + copy(payload[8:], data) + + // Stream all the chunks to the device + chunk := make([]byte, 64) + chunk[0] = 0x3f // Report ID magic number + + for len(payload) > 0 { + // Construct the new message to stream, padding with zeroes if needed + if len(payload) > 63 { + copy(chunk[1:], payload[:63]) + payload = payload[63:] + } else { + copy(chunk[1:], payload) + copy(chunk[1+len(payload):], make([]byte, 63-len(payload))) + payload = nil + } + // Send over to the device + w.log.Trace("Data chunk sent to the Trezor", "chunk", hexutil.Bytes(chunk)) + if _, err := w.device.Write(chunk); err != nil { + return 0, err + } + } + // Stream the reply back from the wallet in 64 byte chunks + var ( + kind uint16 + reply []byte + ) + for { + // Read the next chunk from the Trezor wallet + if _, err := io.ReadFull(w.device, chunk); err != nil { + return 0, err + } + w.log.Trace("Data chunk received from the Trezor", "chunk", hexutil.Bytes(chunk)) + + // Make sure the transport header matches + if chunk[0] != 0x3f || (len(reply) == 0 && (chunk[1] != 0x23 || chunk[2] != 0x23)) { + return 0, errTrezorReplyInvalidHeader + } + // If it's the first chunk, retrieve the reply message type and total message length + var payload []byte + + if len(reply) == 0 { + kind = binary.BigEndian.Uint16(chunk[3:5]) + reply = make([]byte, 0, int(binary.BigEndian.Uint32(chunk[5:9]))) + payload = chunk[9:] + } else { + payload = chunk[1:] + } + // Append to the reply and stop when filled up + if left := cap(reply) - len(reply); left > len(payload) { + reply = append(reply, payload...) + } else { + reply = append(reply, payload[:left]...) + break + } + } + // Try to parse the reply into the requested reply message + if kind == uint16(trezor.MessageType_MessageType_Failure) { + // Trezor returned a failure, extract and return the message + failure := new(trezor.Failure) + if err := proto.Unmarshal(reply, failure); err != nil { + return 0, err + } + return 0, errors.New("trezor: " + failure.GetMessage()) + } + if kind == uint16(trezor.MessageType_MessageType_ButtonRequest) { + // Trezor is waiting for user confirmation, ack and wait for the next message + return w.trezorExchange(&trezor.ButtonAck{}, results...) + } + for i, res := range results { + if trezor.Type(res) == kind { + return i, proto.Unmarshal(reply, res) + } + } + expected := make([]string, len(results)) + for i, res := range results { + expected[i] = trezor.Name(trezor.Type(res)) + } + return 0, fmt.Errorf("trezor: expected reply types %s, got %s", expected, trezor.Name(kind)) +} diff --git a/accounts/usbwallet/trezor/messages.pb.go b/accounts/usbwallet/trezor/messages.pb.go new file mode 100644 index 000000000..15bb6fb73 --- /dev/null +++ b/accounts/usbwallet/trezor/messages.pb.go @@ -0,0 +1,3081 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: messages.proto + +package trezor + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// * +// Mapping between Trezor wire identifier (uint) and a protobuf message +type MessageType int32 + +const ( + MessageType_MessageType_Initialize MessageType = 0 + MessageType_MessageType_Ping MessageType = 1 + MessageType_MessageType_Success MessageType = 2 + MessageType_MessageType_Failure MessageType = 3 + MessageType_MessageType_ChangePin MessageType = 4 + MessageType_MessageType_WipeDevice MessageType = 5 + MessageType_MessageType_FirmwareErase MessageType = 6 + MessageType_MessageType_FirmwareUpload MessageType = 7 + MessageType_MessageType_FirmwareRequest MessageType = 8 + MessageType_MessageType_GetEntropy MessageType = 9 + MessageType_MessageType_Entropy MessageType = 10 + MessageType_MessageType_GetPublicKey MessageType = 11 + MessageType_MessageType_PublicKey MessageType = 12 + MessageType_MessageType_LoadDevice MessageType = 13 + MessageType_MessageType_ResetDevice MessageType = 14 + MessageType_MessageType_SignTx MessageType = 15 + MessageType_MessageType_SimpleSignTx MessageType = 16 + MessageType_MessageType_Features MessageType = 17 + MessageType_MessageType_PinMatrixRequest MessageType = 18 + MessageType_MessageType_PinMatrixAck MessageType = 19 + MessageType_MessageType_Cancel MessageType = 20 + MessageType_MessageType_TxRequest MessageType = 21 + MessageType_MessageType_TxAck MessageType = 22 + MessageType_MessageType_CipherKeyValue MessageType = 23 + MessageType_MessageType_ClearSession MessageType = 24 + MessageType_MessageType_ApplySettings MessageType = 25 + MessageType_MessageType_ButtonRequest MessageType = 26 + MessageType_MessageType_ButtonAck MessageType = 27 + MessageType_MessageType_ApplyFlags MessageType = 28 + MessageType_MessageType_GetAddress MessageType = 29 + MessageType_MessageType_Address MessageType = 30 + MessageType_MessageType_SelfTest MessageType = 32 + MessageType_MessageType_BackupDevice MessageType = 34 + MessageType_MessageType_EntropyRequest MessageType = 35 + MessageType_MessageType_EntropyAck MessageType = 36 + MessageType_MessageType_SignMessage MessageType = 38 + MessageType_MessageType_VerifyMessage MessageType = 39 + MessageType_MessageType_MessageSignature MessageType = 40 + MessageType_MessageType_PassphraseRequest MessageType = 41 + MessageType_MessageType_PassphraseAck MessageType = 42 + MessageType_MessageType_EstimateTxSize MessageType = 43 + MessageType_MessageType_TxSize MessageType = 44 + MessageType_MessageType_RecoveryDevice MessageType = 45 + MessageType_MessageType_WordRequest MessageType = 46 + MessageType_MessageType_WordAck MessageType = 47 + MessageType_MessageType_CipheredKeyValue MessageType = 48 + MessageType_MessageType_EncryptMessage MessageType = 49 + MessageType_MessageType_EncryptedMessage MessageType = 50 + MessageType_MessageType_DecryptMessage MessageType = 51 + MessageType_MessageType_DecryptedMessage MessageType = 52 + MessageType_MessageType_SignIdentity MessageType = 53 + MessageType_MessageType_SignedIdentity MessageType = 54 + MessageType_MessageType_GetFeatures MessageType = 55 + MessageType_MessageType_EthereumGetAddress MessageType = 56 + MessageType_MessageType_EthereumAddress MessageType = 57 + MessageType_MessageType_EthereumSignTx MessageType = 58 + MessageType_MessageType_EthereumTxRequest MessageType = 59 + MessageType_MessageType_EthereumTxAck MessageType = 60 + MessageType_MessageType_GetECDHSessionKey MessageType = 61 + MessageType_MessageType_ECDHSessionKey MessageType = 62 + MessageType_MessageType_SetU2FCounter MessageType = 63 + MessageType_MessageType_EthereumSignMessage MessageType = 64 + MessageType_MessageType_EthereumVerifyMessage MessageType = 65 + MessageType_MessageType_EthereumMessageSignature MessageType = 66 + MessageType_MessageType_DebugLinkDecision MessageType = 100 + MessageType_MessageType_DebugLinkGetState MessageType = 101 + MessageType_MessageType_DebugLinkState MessageType = 102 + MessageType_MessageType_DebugLinkStop MessageType = 103 + MessageType_MessageType_DebugLinkLog MessageType = 104 + MessageType_MessageType_DebugLinkMemoryRead MessageType = 110 + MessageType_MessageType_DebugLinkMemory MessageType = 111 + MessageType_MessageType_DebugLinkMemoryWrite MessageType = 112 + MessageType_MessageType_DebugLinkFlashErase MessageType = 113 +) + +var MessageType_name = map[int32]string{ + 0: "MessageType_Initialize", + 1: "MessageType_Ping", + 2: "MessageType_Success", + 3: "MessageType_Failure", + 4: "MessageType_ChangePin", + 5: "MessageType_WipeDevice", + 6: "MessageType_FirmwareErase", + 7: "MessageType_FirmwareUpload", + 8: "MessageType_FirmwareRequest", + 9: "MessageType_GetEntropy", + 10: "MessageType_Entropy", + 11: "MessageType_GetPublicKey", + 12: "MessageType_PublicKey", + 13: "MessageType_LoadDevice", + 14: "MessageType_ResetDevice", + 15: "MessageType_SignTx", + 16: "MessageType_SimpleSignTx", + 17: "MessageType_Features", + 18: "MessageType_PinMatrixRequest", + 19: "MessageType_PinMatrixAck", + 20: "MessageType_Cancel", + 21: "MessageType_TxRequest", + 22: "MessageType_TxAck", + 23: "MessageType_CipherKeyValue", + 24: "MessageType_ClearSession", + 25: "MessageType_ApplySettings", + 26: "MessageType_ButtonRequest", + 27: "MessageType_ButtonAck", + 28: "MessageType_ApplyFlags", + 29: "MessageType_GetAddress", + 30: "MessageType_Address", + 32: "MessageType_SelfTest", + 34: "MessageType_BackupDevice", + 35: "MessageType_EntropyRequest", + 36: "MessageType_EntropyAck", + 38: "MessageType_SignMessage", + 39: "MessageType_VerifyMessage", + 40: "MessageType_MessageSignature", + 41: "MessageType_PassphraseRequest", + 42: "MessageType_PassphraseAck", + 43: "MessageType_EstimateTxSize", + 44: "MessageType_TxSize", + 45: "MessageType_RecoveryDevice", + 46: "MessageType_WordRequest", + 47: "MessageType_WordAck", + 48: "MessageType_CipheredKeyValue", + 49: "MessageType_EncryptMessage", + 50: "MessageType_EncryptedMessage", + 51: "MessageType_DecryptMessage", + 52: "MessageType_DecryptedMessage", + 53: "MessageType_SignIdentity", + 54: "MessageType_SignedIdentity", + 55: "MessageType_GetFeatures", + 56: "MessageType_EthereumGetAddress", + 57: "MessageType_EthereumAddress", + 58: "MessageType_EthereumSignTx", + 59: "MessageType_EthereumTxRequest", + 60: "MessageType_EthereumTxAck", + 61: "MessageType_GetECDHSessionKey", + 62: "MessageType_ECDHSessionKey", + 63: "MessageType_SetU2FCounter", + 64: "MessageType_EthereumSignMessage", + 65: "MessageType_EthereumVerifyMessage", + 66: "MessageType_EthereumMessageSignature", + 100: "MessageType_DebugLinkDecision", + 101: "MessageType_DebugLinkGetState", + 102: "MessageType_DebugLinkState", + 103: "MessageType_DebugLinkStop", + 104: "MessageType_DebugLinkLog", + 110: "MessageType_DebugLinkMemoryRead", + 111: "MessageType_DebugLinkMemory", + 112: "MessageType_DebugLinkMemoryWrite", + 113: "MessageType_DebugLinkFlashErase", +} +var MessageType_value = map[string]int32{ + "MessageType_Initialize": 0, + "MessageType_Ping": 1, + "MessageType_Success": 2, + "MessageType_Failure": 3, + "MessageType_ChangePin": 4, + "MessageType_WipeDevice": 5, + "MessageType_FirmwareErase": 6, + "MessageType_FirmwareUpload": 7, + "MessageType_FirmwareRequest": 8, + "MessageType_GetEntropy": 9, + "MessageType_Entropy": 10, + "MessageType_GetPublicKey": 11, + "MessageType_PublicKey": 12, + "MessageType_LoadDevice": 13, + "MessageType_ResetDevice": 14, + "MessageType_SignTx": 15, + "MessageType_SimpleSignTx": 16, + "MessageType_Features": 17, + "MessageType_PinMatrixRequest": 18, + "MessageType_PinMatrixAck": 19, + "MessageType_Cancel": 20, + "MessageType_TxRequest": 21, + "MessageType_TxAck": 22, + "MessageType_CipherKeyValue": 23, + "MessageType_ClearSession": 24, + "MessageType_ApplySettings": 25, + "MessageType_ButtonRequest": 26, + "MessageType_ButtonAck": 27, + "MessageType_ApplyFlags": 28, + "MessageType_GetAddress": 29, + "MessageType_Address": 30, + "MessageType_SelfTest": 32, + "MessageType_BackupDevice": 34, + "MessageType_EntropyRequest": 35, + "MessageType_EntropyAck": 36, + "MessageType_SignMessage": 38, + "MessageType_VerifyMessage": 39, + "MessageType_MessageSignature": 40, + "MessageType_PassphraseRequest": 41, + "MessageType_PassphraseAck": 42, + "MessageType_EstimateTxSize": 43, + "MessageType_TxSize": 44, + "MessageType_RecoveryDevice": 45, + "MessageType_WordRequest": 46, + "MessageType_WordAck": 47, + "MessageType_CipheredKeyValue": 48, + "MessageType_EncryptMessage": 49, + "MessageType_EncryptedMessage": 50, + "MessageType_DecryptMessage": 51, + "MessageType_DecryptedMessage": 52, + "MessageType_SignIdentity": 53, + "MessageType_SignedIdentity": 54, + "MessageType_GetFeatures": 55, + "MessageType_EthereumGetAddress": 56, + "MessageType_EthereumAddress": 57, + "MessageType_EthereumSignTx": 58, + "MessageType_EthereumTxRequest": 59, + "MessageType_EthereumTxAck": 60, + "MessageType_GetECDHSessionKey": 61, + "MessageType_ECDHSessionKey": 62, + "MessageType_SetU2FCounter": 63, + "MessageType_EthereumSignMessage": 64, + "MessageType_EthereumVerifyMessage": 65, + "MessageType_EthereumMessageSignature": 66, + "MessageType_DebugLinkDecision": 100, + "MessageType_DebugLinkGetState": 101, + "MessageType_DebugLinkState": 102, + "MessageType_DebugLinkStop": 103, + "MessageType_DebugLinkLog": 104, + "MessageType_DebugLinkMemoryRead": 110, + "MessageType_DebugLinkMemory": 111, + "MessageType_DebugLinkMemoryWrite": 112, + "MessageType_DebugLinkFlashErase": 113, +} + +func (x MessageType) Enum() *MessageType { + p := new(MessageType) + *p = x + return p +} +func (x MessageType) String() string { + return proto.EnumName(MessageType_name, int32(x)) +} +func (x *MessageType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(MessageType_value, data, "MessageType") + if err != nil { + return err + } + *x = MessageType(value) + return nil +} +func (MessageType) EnumDescriptor() ([]byte, []int) { return fileDescriptor1, []int{0} } + +// * +// Request: Reset device to default state and ask for device details +// @next Features +type Initialize struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *Initialize) Reset() { *m = Initialize{} } +func (m *Initialize) String() string { return proto.CompactTextString(m) } +func (*Initialize) ProtoMessage() {} +func (*Initialize) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} } + +// * +// Request: Ask for device details (no device reset) +// @next Features +type GetFeatures struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *GetFeatures) Reset() { *m = GetFeatures{} } +func (m *GetFeatures) String() string { return proto.CompactTextString(m) } +func (*GetFeatures) ProtoMessage() {} +func (*GetFeatures) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{1} } + +// * +// Response: Reports various information about the device +// @prev Initialize +// @prev GetFeatures +type Features struct { + Vendor *string `protobuf:"bytes,1,opt,name=vendor" json:"vendor,omitempty"` + MajorVersion *uint32 `protobuf:"varint,2,opt,name=major_version,json=majorVersion" json:"major_version,omitempty"` + MinorVersion *uint32 `protobuf:"varint,3,opt,name=minor_version,json=minorVersion" json:"minor_version,omitempty"` + PatchVersion *uint32 `protobuf:"varint,4,opt,name=patch_version,json=patchVersion" json:"patch_version,omitempty"` + BootloaderMode *bool `protobuf:"varint,5,opt,name=bootloader_mode,json=bootloaderMode" json:"bootloader_mode,omitempty"` + DeviceId *string `protobuf:"bytes,6,opt,name=device_id,json=deviceId" json:"device_id,omitempty"` + PinProtection *bool `protobuf:"varint,7,opt,name=pin_protection,json=pinProtection" json:"pin_protection,omitempty"` + PassphraseProtection *bool `protobuf:"varint,8,opt,name=passphrase_protection,json=passphraseProtection" json:"passphrase_protection,omitempty"` + Language *string `protobuf:"bytes,9,opt,name=language" json:"language,omitempty"` + Label *string `protobuf:"bytes,10,opt,name=label" json:"label,omitempty"` + Coins []*CoinType `protobuf:"bytes,11,rep,name=coins" json:"coins,omitempty"` + Initialized *bool `protobuf:"varint,12,opt,name=initialized" json:"initialized,omitempty"` + Revision []byte `protobuf:"bytes,13,opt,name=revision" json:"revision,omitempty"` + BootloaderHash []byte `protobuf:"bytes,14,opt,name=bootloader_hash,json=bootloaderHash" json:"bootloader_hash,omitempty"` + Imported *bool `protobuf:"varint,15,opt,name=imported" json:"imported,omitempty"` + PinCached *bool `protobuf:"varint,16,opt,name=pin_cached,json=pinCached" json:"pin_cached,omitempty"` + PassphraseCached *bool `protobuf:"varint,17,opt,name=passphrase_cached,json=passphraseCached" json:"passphrase_cached,omitempty"` + FirmwarePresent *bool `protobuf:"varint,18,opt,name=firmware_present,json=firmwarePresent" json:"firmware_present,omitempty"` + NeedsBackup *bool `protobuf:"varint,19,opt,name=needs_backup,json=needsBackup" json:"needs_backup,omitempty"` + Flags *uint32 `protobuf:"varint,20,opt,name=flags" json:"flags,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Features) Reset() { *m = Features{} } +func (m *Features) String() string { return proto.CompactTextString(m) } +func (*Features) ProtoMessage() {} +func (*Features) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{2} } + +func (m *Features) GetVendor() string { + if m != nil && m.Vendor != nil { + return *m.Vendor + } + return "" +} + +func (m *Features) GetMajorVersion() uint32 { + if m != nil && m.MajorVersion != nil { + return *m.MajorVersion + } + return 0 +} + +func (m *Features) GetMinorVersion() uint32 { + if m != nil && m.MinorVersion != nil { + return *m.MinorVersion + } + return 0 +} + +func (m *Features) GetPatchVersion() uint32 { + if m != nil && m.PatchVersion != nil { + return *m.PatchVersion + } + return 0 +} + +func (m *Features) GetBootloaderMode() bool { + if m != nil && m.BootloaderMode != nil { + return *m.BootloaderMode + } + return false +} + +func (m *Features) GetDeviceId() string { + if m != nil && m.DeviceId != nil { + return *m.DeviceId + } + return "" +} + +func (m *Features) GetPinProtection() bool { + if m != nil && m.PinProtection != nil { + return *m.PinProtection + } + return false +} + +func (m *Features) GetPassphraseProtection() bool { + if m != nil && m.PassphraseProtection != nil { + return *m.PassphraseProtection + } + return false +} + +func (m *Features) GetLanguage() string { + if m != nil && m.Language != nil { + return *m.Language + } + return "" +} + +func (m *Features) GetLabel() string { + if m != nil && m.Label != nil { + return *m.Label + } + return "" +} + +func (m *Features) GetCoins() []*CoinType { + if m != nil { + return m.Coins + } + return nil +} + +func (m *Features) GetInitialized() bool { + if m != nil && m.Initialized != nil { + return *m.Initialized + } + return false +} + +func (m *Features) GetRevision() []byte { + if m != nil { + return m.Revision + } + return nil +} + +func (m *Features) GetBootloaderHash() []byte { + if m != nil { + return m.BootloaderHash + } + return nil +} + +func (m *Features) GetImported() bool { + if m != nil && m.Imported != nil { + return *m.Imported + } + return false +} + +func (m *Features) GetPinCached() bool { + if m != nil && m.PinCached != nil { + return *m.PinCached + } + return false +} + +func (m *Features) GetPassphraseCached() bool { + if m != nil && m.PassphraseCached != nil { + return *m.PassphraseCached + } + return false +} + +func (m *Features) GetFirmwarePresent() bool { + if m != nil && m.FirmwarePresent != nil { + return *m.FirmwarePresent + } + return false +} + +func (m *Features) GetNeedsBackup() bool { + if m != nil && m.NeedsBackup != nil { + return *m.NeedsBackup + } + return false +} + +func (m *Features) GetFlags() uint32 { + if m != nil && m.Flags != nil { + return *m.Flags + } + return 0 +} + +// * +// Request: clear session (removes cached PIN, passphrase, etc). +// @next Success +type ClearSession struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *ClearSession) Reset() { *m = ClearSession{} } +func (m *ClearSession) String() string { return proto.CompactTextString(m) } +func (*ClearSession) ProtoMessage() {} +func (*ClearSession) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{3} } + +// * +// Request: change language and/or label of the device +// @next Success +// @next Failure +// @next ButtonRequest +// @next PinMatrixRequest +type ApplySettings struct { + Language *string `protobuf:"bytes,1,opt,name=language" json:"language,omitempty"` + Label *string `protobuf:"bytes,2,opt,name=label" json:"label,omitempty"` + UsePassphrase *bool `protobuf:"varint,3,opt,name=use_passphrase,json=usePassphrase" json:"use_passphrase,omitempty"` + Homescreen []byte `protobuf:"bytes,4,opt,name=homescreen" json:"homescreen,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *ApplySettings) Reset() { *m = ApplySettings{} } +func (m *ApplySettings) String() string { return proto.CompactTextString(m) } +func (*ApplySettings) ProtoMessage() {} +func (*ApplySettings) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{4} } + +func (m *ApplySettings) GetLanguage() string { + if m != nil && m.Language != nil { + return *m.Language + } + return "" +} + +func (m *ApplySettings) GetLabel() string { + if m != nil && m.Label != nil { + return *m.Label + } + return "" +} + +func (m *ApplySettings) GetUsePassphrase() bool { + if m != nil && m.UsePassphrase != nil { + return *m.UsePassphrase + } + return false +} + +func (m *ApplySettings) GetHomescreen() []byte { + if m != nil { + return m.Homescreen + } + return nil +} + +// * +// Request: set flags of the device +// @next Success +// @next Failure +type ApplyFlags struct { + Flags *uint32 `protobuf:"varint,1,opt,name=flags" json:"flags,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *ApplyFlags) Reset() { *m = ApplyFlags{} } +func (m *ApplyFlags) String() string { return proto.CompactTextString(m) } +func (*ApplyFlags) ProtoMessage() {} +func (*ApplyFlags) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{5} } + +func (m *ApplyFlags) GetFlags() uint32 { + if m != nil && m.Flags != nil { + return *m.Flags + } + return 0 +} + +// * +// Request: Starts workflow for setting/changing/removing the PIN +// @next ButtonRequest +// @next PinMatrixRequest +type ChangePin struct { + Remove *bool `protobuf:"varint,1,opt,name=remove" json:"remove,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *ChangePin) Reset() { *m = ChangePin{} } +func (m *ChangePin) String() string { return proto.CompactTextString(m) } +func (*ChangePin) ProtoMessage() {} +func (*ChangePin) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{6} } + +func (m *ChangePin) GetRemove() bool { + if m != nil && m.Remove != nil { + return *m.Remove + } + return false +} + +// * +// Request: Test if the device is alive, device sends back the message in Success response +// @next Success +type Ping struct { + Message *string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"` + ButtonProtection *bool `protobuf:"varint,2,opt,name=button_protection,json=buttonProtection" json:"button_protection,omitempty"` + PinProtection *bool `protobuf:"varint,3,opt,name=pin_protection,json=pinProtection" json:"pin_protection,omitempty"` + PassphraseProtection *bool `protobuf:"varint,4,opt,name=passphrase_protection,json=passphraseProtection" json:"passphrase_protection,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Ping) Reset() { *m = Ping{} } +func (m *Ping) String() string { return proto.CompactTextString(m) } +func (*Ping) ProtoMessage() {} +func (*Ping) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{7} } + +func (m *Ping) GetMessage() string { + if m != nil && m.Message != nil { + return *m.Message + } + return "" +} + +func (m *Ping) GetButtonProtection() bool { + if m != nil && m.ButtonProtection != nil { + return *m.ButtonProtection + } + return false +} + +func (m *Ping) GetPinProtection() bool { + if m != nil && m.PinProtection != nil { + return *m.PinProtection + } + return false +} + +func (m *Ping) GetPassphraseProtection() bool { + if m != nil && m.PassphraseProtection != nil { + return *m.PassphraseProtection + } + return false +} + +// * +// Response: Success of the previous request +type Success struct { + Message *string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Success) Reset() { *m = Success{} } +func (m *Success) String() string { return proto.CompactTextString(m) } +func (*Success) ProtoMessage() {} +func (*Success) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{8} } + +func (m *Success) GetMessage() string { + if m != nil && m.Message != nil { + return *m.Message + } + return "" +} + +// * +// Response: Failure of the previous request +type Failure struct { + Code *FailureType `protobuf:"varint,1,opt,name=code,enum=FailureType" json:"code,omitempty"` + Message *string `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Failure) Reset() { *m = Failure{} } +func (m *Failure) String() string { return proto.CompactTextString(m) } +func (*Failure) ProtoMessage() {} +func (*Failure) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{9} } + +func (m *Failure) GetCode() FailureType { + if m != nil && m.Code != nil { + return *m.Code + } + return FailureType_Failure_UnexpectedMessage +} + +func (m *Failure) GetMessage() string { + if m != nil && m.Message != nil { + return *m.Message + } + return "" +} + +// * +// Response: Device is waiting for HW button press. +// @next ButtonAck +// @next Cancel +type ButtonRequest struct { + Code *ButtonRequestType `protobuf:"varint,1,opt,name=code,enum=ButtonRequestType" json:"code,omitempty"` + Data *string `protobuf:"bytes,2,opt,name=data" json:"data,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *ButtonRequest) Reset() { *m = ButtonRequest{} } +func (m *ButtonRequest) String() string { return proto.CompactTextString(m) } +func (*ButtonRequest) ProtoMessage() {} +func (*ButtonRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{10} } + +func (m *ButtonRequest) GetCode() ButtonRequestType { + if m != nil && m.Code != nil { + return *m.Code + } + return ButtonRequestType_ButtonRequest_Other +} + +func (m *ButtonRequest) GetData() string { + if m != nil && m.Data != nil { + return *m.Data + } + return "" +} + +// * +// Request: Computer agrees to wait for HW button press +// @prev ButtonRequest +type ButtonAck struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *ButtonAck) Reset() { *m = ButtonAck{} } +func (m *ButtonAck) String() string { return proto.CompactTextString(m) } +func (*ButtonAck) ProtoMessage() {} +func (*ButtonAck) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{11} } + +// * +// Response: Device is asking computer to show PIN matrix and awaits PIN encoded using this matrix scheme +// @next PinMatrixAck +// @next Cancel +type PinMatrixRequest struct { + Type *PinMatrixRequestType `protobuf:"varint,1,opt,name=type,enum=PinMatrixRequestType" json:"type,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *PinMatrixRequest) Reset() { *m = PinMatrixRequest{} } +func (m *PinMatrixRequest) String() string { return proto.CompactTextString(m) } +func (*PinMatrixRequest) ProtoMessage() {} +func (*PinMatrixRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{12} } + +func (m *PinMatrixRequest) GetType() PinMatrixRequestType { + if m != nil && m.Type != nil { + return *m.Type + } + return PinMatrixRequestType_PinMatrixRequestType_Current +} + +// * +// Request: Computer responds with encoded PIN +// @prev PinMatrixRequest +type PinMatrixAck struct { + Pin *string `protobuf:"bytes,1,req,name=pin" json:"pin,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *PinMatrixAck) Reset() { *m = PinMatrixAck{} } +func (m *PinMatrixAck) String() string { return proto.CompactTextString(m) } +func (*PinMatrixAck) ProtoMessage() {} +func (*PinMatrixAck) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{13} } + +func (m *PinMatrixAck) GetPin() string { + if m != nil && m.Pin != nil { + return *m.Pin + } + return "" +} + +// * +// Request: Abort last operation that required user interaction +// @prev ButtonRequest +// @prev PinMatrixRequest +// @prev PassphraseRequest +type Cancel struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *Cancel) Reset() { *m = Cancel{} } +func (m *Cancel) String() string { return proto.CompactTextString(m) } +func (*Cancel) ProtoMessage() {} +func (*Cancel) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{14} } + +// * +// Response: Device awaits encryption passphrase +// @next PassphraseAck +// @next Cancel +type PassphraseRequest struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *PassphraseRequest) Reset() { *m = PassphraseRequest{} } +func (m *PassphraseRequest) String() string { return proto.CompactTextString(m) } +func (*PassphraseRequest) ProtoMessage() {} +func (*PassphraseRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{15} } + +// * +// Request: Send passphrase back +// @prev PassphraseRequest +type PassphraseAck struct { + Passphrase *string `protobuf:"bytes,1,req,name=passphrase" json:"passphrase,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *PassphraseAck) Reset() { *m = PassphraseAck{} } +func (m *PassphraseAck) String() string { return proto.CompactTextString(m) } +func (*PassphraseAck) ProtoMessage() {} +func (*PassphraseAck) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{16} } + +func (m *PassphraseAck) GetPassphrase() string { + if m != nil && m.Passphrase != nil { + return *m.Passphrase + } + return "" +} + +// * +// Request: Request a sample of random data generated by hardware RNG. May be used for testing. +// @next ButtonRequest +// @next Entropy +// @next Failure +type GetEntropy struct { + Size *uint32 `protobuf:"varint,1,req,name=size" json:"size,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GetEntropy) Reset() { *m = GetEntropy{} } +func (m *GetEntropy) String() string { return proto.CompactTextString(m) } +func (*GetEntropy) ProtoMessage() {} +func (*GetEntropy) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{17} } + +func (m *GetEntropy) GetSize() uint32 { + if m != nil && m.Size != nil { + return *m.Size + } + return 0 +} + +// * +// Response: Reply with random data generated by internal RNG +// @prev GetEntropy +type Entropy struct { + Entropy []byte `protobuf:"bytes,1,req,name=entropy" json:"entropy,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Entropy) Reset() { *m = Entropy{} } +func (m *Entropy) String() string { return proto.CompactTextString(m) } +func (*Entropy) ProtoMessage() {} +func (*Entropy) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{18} } + +func (m *Entropy) GetEntropy() []byte { + if m != nil { + return m.Entropy + } + return nil +} + +// * +// Request: Ask device for public key corresponding to address_n path +// @next PassphraseRequest +// @next PublicKey +// @next Failure +type GetPublicKey struct { + AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` + EcdsaCurveName *string `protobuf:"bytes,2,opt,name=ecdsa_curve_name,json=ecdsaCurveName" json:"ecdsa_curve_name,omitempty"` + ShowDisplay *bool `protobuf:"varint,3,opt,name=show_display,json=showDisplay" json:"show_display,omitempty"` + CoinName *string `protobuf:"bytes,4,opt,name=coin_name,json=coinName,def=Bitcoin" json:"coin_name,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GetPublicKey) Reset() { *m = GetPublicKey{} } +func (m *GetPublicKey) String() string { return proto.CompactTextString(m) } +func (*GetPublicKey) ProtoMessage() {} +func (*GetPublicKey) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{19} } + +const Default_GetPublicKey_CoinName string = "Bitcoin" + +func (m *GetPublicKey) GetAddressN() []uint32 { + if m != nil { + return m.AddressN + } + return nil +} + +func (m *GetPublicKey) GetEcdsaCurveName() string { + if m != nil && m.EcdsaCurveName != nil { + return *m.EcdsaCurveName + } + return "" +} + +func (m *GetPublicKey) GetShowDisplay() bool { + if m != nil && m.ShowDisplay != nil { + return *m.ShowDisplay + } + return false +} + +func (m *GetPublicKey) GetCoinName() string { + if m != nil && m.CoinName != nil { + return *m.CoinName + } + return Default_GetPublicKey_CoinName +} + +// * +// Response: Contains public key derived from device private seed +// @prev GetPublicKey +type PublicKey struct { + Node *HDNodeType `protobuf:"bytes,1,req,name=node" json:"node,omitempty"` + Xpub *string `protobuf:"bytes,2,opt,name=xpub" json:"xpub,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *PublicKey) Reset() { *m = PublicKey{} } +func (m *PublicKey) String() string { return proto.CompactTextString(m) } +func (*PublicKey) ProtoMessage() {} +func (*PublicKey) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{20} } + +func (m *PublicKey) GetNode() *HDNodeType { + if m != nil { + return m.Node + } + return nil +} + +func (m *PublicKey) GetXpub() string { + if m != nil && m.Xpub != nil { + return *m.Xpub + } + return "" +} + +// * +// Request: Ask device for address corresponding to address_n path +// @next PassphraseRequest +// @next Address +// @next Failure +type GetAddress struct { + AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` + CoinName *string `protobuf:"bytes,2,opt,name=coin_name,json=coinName,def=Bitcoin" json:"coin_name,omitempty"` + ShowDisplay *bool `protobuf:"varint,3,opt,name=show_display,json=showDisplay" json:"show_display,omitempty"` + Multisig *MultisigRedeemScriptType `protobuf:"bytes,4,opt,name=multisig" json:"multisig,omitempty"` + ScriptType *InputScriptType `protobuf:"varint,5,opt,name=script_type,json=scriptType,enum=InputScriptType,def=0" json:"script_type,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GetAddress) Reset() { *m = GetAddress{} } +func (m *GetAddress) String() string { return proto.CompactTextString(m) } +func (*GetAddress) ProtoMessage() {} +func (*GetAddress) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{21} } + +const Default_GetAddress_CoinName string = "Bitcoin" +const Default_GetAddress_ScriptType InputScriptType = InputScriptType_SPENDADDRESS + +func (m *GetAddress) GetAddressN() []uint32 { + if m != nil { + return m.AddressN + } + return nil +} + +func (m *GetAddress) GetCoinName() string { + if m != nil && m.CoinName != nil { + return *m.CoinName + } + return Default_GetAddress_CoinName +} + +func (m *GetAddress) GetShowDisplay() bool { + if m != nil && m.ShowDisplay != nil { + return *m.ShowDisplay + } + return false +} + +func (m *GetAddress) GetMultisig() *MultisigRedeemScriptType { + if m != nil { + return m.Multisig + } + return nil +} + +func (m *GetAddress) GetScriptType() InputScriptType { + if m != nil && m.ScriptType != nil { + return *m.ScriptType + } + return Default_GetAddress_ScriptType +} + +// * +// Request: Ask device for Ethereum address corresponding to address_n path +// @next PassphraseRequest +// @next EthereumAddress +// @next Failure +type EthereumGetAddress struct { + AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` + ShowDisplay *bool `protobuf:"varint,2,opt,name=show_display,json=showDisplay" json:"show_display,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EthereumGetAddress) Reset() { *m = EthereumGetAddress{} } +func (m *EthereumGetAddress) String() string { return proto.CompactTextString(m) } +func (*EthereumGetAddress) ProtoMessage() {} +func (*EthereumGetAddress) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{22} } + +func (m *EthereumGetAddress) GetAddressN() []uint32 { + if m != nil { + return m.AddressN + } + return nil +} + +func (m *EthereumGetAddress) GetShowDisplay() bool { + if m != nil && m.ShowDisplay != nil { + return *m.ShowDisplay + } + return false +} + +// * +// Response: Contains address derived from device private seed +// @prev GetAddress +type Address struct { + Address *string `protobuf:"bytes,1,req,name=address" json:"address,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Address) Reset() { *m = Address{} } +func (m *Address) String() string { return proto.CompactTextString(m) } +func (*Address) ProtoMessage() {} +func (*Address) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{23} } + +func (m *Address) GetAddress() string { + if m != nil && m.Address != nil { + return *m.Address + } + return "" +} + +// * +// Response: Contains an Ethereum address derived from device private seed +// @prev EthereumGetAddress +type EthereumAddress struct { + Address []byte `protobuf:"bytes,1,req,name=address" json:"address,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EthereumAddress) Reset() { *m = EthereumAddress{} } +func (m *EthereumAddress) String() string { return proto.CompactTextString(m) } +func (*EthereumAddress) ProtoMessage() {} +func (*EthereumAddress) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{24} } + +func (m *EthereumAddress) GetAddress() []byte { + if m != nil { + return m.Address + } + return nil +} + +// * +// Request: Request device to wipe all sensitive data and settings +// @next ButtonRequest +type WipeDevice struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *WipeDevice) Reset() { *m = WipeDevice{} } +func (m *WipeDevice) String() string { return proto.CompactTextString(m) } +func (*WipeDevice) ProtoMessage() {} +func (*WipeDevice) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{25} } + +// * +// Request: Load seed and related internal settings from the computer +// @next ButtonRequest +// @next Success +// @next Failure +type LoadDevice struct { + Mnemonic *string `protobuf:"bytes,1,opt,name=mnemonic" json:"mnemonic,omitempty"` + Node *HDNodeType `protobuf:"bytes,2,opt,name=node" json:"node,omitempty"` + Pin *string `protobuf:"bytes,3,opt,name=pin" json:"pin,omitempty"` + PassphraseProtection *bool `protobuf:"varint,4,opt,name=passphrase_protection,json=passphraseProtection" json:"passphrase_protection,omitempty"` + Language *string `protobuf:"bytes,5,opt,name=language,def=english" json:"language,omitempty"` + Label *string `protobuf:"bytes,6,opt,name=label" json:"label,omitempty"` + SkipChecksum *bool `protobuf:"varint,7,opt,name=skip_checksum,json=skipChecksum" json:"skip_checksum,omitempty"` + U2FCounter *uint32 `protobuf:"varint,8,opt,name=u2f_counter,json=u2fCounter" json:"u2f_counter,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *LoadDevice) Reset() { *m = LoadDevice{} } +func (m *LoadDevice) String() string { return proto.CompactTextString(m) } +func (*LoadDevice) ProtoMessage() {} +func (*LoadDevice) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{26} } + +const Default_LoadDevice_Language string = "english" + +func (m *LoadDevice) GetMnemonic() string { + if m != nil && m.Mnemonic != nil { + return *m.Mnemonic + } + return "" +} + +func (m *LoadDevice) GetNode() *HDNodeType { + if m != nil { + return m.Node + } + return nil +} + +func (m *LoadDevice) GetPin() string { + if m != nil && m.Pin != nil { + return *m.Pin + } + return "" +} + +func (m *LoadDevice) GetPassphraseProtection() bool { + if m != nil && m.PassphraseProtection != nil { + return *m.PassphraseProtection + } + return false +} + +func (m *LoadDevice) GetLanguage() string { + if m != nil && m.Language != nil { + return *m.Language + } + return Default_LoadDevice_Language +} + +func (m *LoadDevice) GetLabel() string { + if m != nil && m.Label != nil { + return *m.Label + } + return "" +} + +func (m *LoadDevice) GetSkipChecksum() bool { + if m != nil && m.SkipChecksum != nil { + return *m.SkipChecksum + } + return false +} + +func (m *LoadDevice) GetU2FCounter() uint32 { + if m != nil && m.U2FCounter != nil { + return *m.U2FCounter + } + return 0 +} + +// * +// Request: Ask device to do initialization involving user interaction +// @next EntropyRequest +// @next Failure +type ResetDevice struct { + DisplayRandom *bool `protobuf:"varint,1,opt,name=display_random,json=displayRandom" json:"display_random,omitempty"` + Strength *uint32 `protobuf:"varint,2,opt,name=strength,def=256" json:"strength,omitempty"` + PassphraseProtection *bool `protobuf:"varint,3,opt,name=passphrase_protection,json=passphraseProtection" json:"passphrase_protection,omitempty"` + PinProtection *bool `protobuf:"varint,4,opt,name=pin_protection,json=pinProtection" json:"pin_protection,omitempty"` + Language *string `protobuf:"bytes,5,opt,name=language,def=english" json:"language,omitempty"` + Label *string `protobuf:"bytes,6,opt,name=label" json:"label,omitempty"` + U2FCounter *uint32 `protobuf:"varint,7,opt,name=u2f_counter,json=u2fCounter" json:"u2f_counter,omitempty"` + SkipBackup *bool `protobuf:"varint,8,opt,name=skip_backup,json=skipBackup" json:"skip_backup,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *ResetDevice) Reset() { *m = ResetDevice{} } +func (m *ResetDevice) String() string { return proto.CompactTextString(m) } +func (*ResetDevice) ProtoMessage() {} +func (*ResetDevice) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{27} } + +const Default_ResetDevice_Strength uint32 = 256 +const Default_ResetDevice_Language string = "english" + +func (m *ResetDevice) GetDisplayRandom() bool { + if m != nil && m.DisplayRandom != nil { + return *m.DisplayRandom + } + return false +} + +func (m *ResetDevice) GetStrength() uint32 { + if m != nil && m.Strength != nil { + return *m.Strength + } + return Default_ResetDevice_Strength +} + +func (m *ResetDevice) GetPassphraseProtection() bool { + if m != nil && m.PassphraseProtection != nil { + return *m.PassphraseProtection + } + return false +} + +func (m *ResetDevice) GetPinProtection() bool { + if m != nil && m.PinProtection != nil { + return *m.PinProtection + } + return false +} + +func (m *ResetDevice) GetLanguage() string { + if m != nil && m.Language != nil { + return *m.Language + } + return Default_ResetDevice_Language +} + +func (m *ResetDevice) GetLabel() string { + if m != nil && m.Label != nil { + return *m.Label + } + return "" +} + +func (m *ResetDevice) GetU2FCounter() uint32 { + if m != nil && m.U2FCounter != nil { + return *m.U2FCounter + } + return 0 +} + +func (m *ResetDevice) GetSkipBackup() bool { + if m != nil && m.SkipBackup != nil { + return *m.SkipBackup + } + return false +} + +// * +// Request: Perform backup of the device seed if not backed up using ResetDevice +// @next ButtonRequest +type BackupDevice struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *BackupDevice) Reset() { *m = BackupDevice{} } +func (m *BackupDevice) String() string { return proto.CompactTextString(m) } +func (*BackupDevice) ProtoMessage() {} +func (*BackupDevice) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{28} } + +// * +// Response: Ask for additional entropy from host computer +// @prev ResetDevice +// @next EntropyAck +type EntropyRequest struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *EntropyRequest) Reset() { *m = EntropyRequest{} } +func (m *EntropyRequest) String() string { return proto.CompactTextString(m) } +func (*EntropyRequest) ProtoMessage() {} +func (*EntropyRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{29} } + +// * +// Request: Provide additional entropy for seed generation function +// @prev EntropyRequest +// @next ButtonRequest +type EntropyAck struct { + Entropy []byte `protobuf:"bytes,1,opt,name=entropy" json:"entropy,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EntropyAck) Reset() { *m = EntropyAck{} } +func (m *EntropyAck) String() string { return proto.CompactTextString(m) } +func (*EntropyAck) ProtoMessage() {} +func (*EntropyAck) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{30} } + +func (m *EntropyAck) GetEntropy() []byte { + if m != nil { + return m.Entropy + } + return nil +} + +// * +// Request: Start recovery workflow asking user for specific words of mnemonic +// Used to recovery device safely even on untrusted computer. +// @next WordRequest +type RecoveryDevice struct { + WordCount *uint32 `protobuf:"varint,1,opt,name=word_count,json=wordCount" json:"word_count,omitempty"` + PassphraseProtection *bool `protobuf:"varint,2,opt,name=passphrase_protection,json=passphraseProtection" json:"passphrase_protection,omitempty"` + PinProtection *bool `protobuf:"varint,3,opt,name=pin_protection,json=pinProtection" json:"pin_protection,omitempty"` + Language *string `protobuf:"bytes,4,opt,name=language,def=english" json:"language,omitempty"` + Label *string `protobuf:"bytes,5,opt,name=label" json:"label,omitempty"` + EnforceWordlist *bool `protobuf:"varint,6,opt,name=enforce_wordlist,json=enforceWordlist" json:"enforce_wordlist,omitempty"` + // 7 reserved for unused recovery method + Type *uint32 `protobuf:"varint,8,opt,name=type" json:"type,omitempty"` + U2FCounter *uint32 `protobuf:"varint,9,opt,name=u2f_counter,json=u2fCounter" json:"u2f_counter,omitempty"` + DryRun *bool `protobuf:"varint,10,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *RecoveryDevice) Reset() { *m = RecoveryDevice{} } +func (m *RecoveryDevice) String() string { return proto.CompactTextString(m) } +func (*RecoveryDevice) ProtoMessage() {} +func (*RecoveryDevice) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{31} } + +const Default_RecoveryDevice_Language string = "english" + +func (m *RecoveryDevice) GetWordCount() uint32 { + if m != nil && m.WordCount != nil { + return *m.WordCount + } + return 0 +} + +func (m *RecoveryDevice) GetPassphraseProtection() bool { + if m != nil && m.PassphraseProtection != nil { + return *m.PassphraseProtection + } + return false +} + +func (m *RecoveryDevice) GetPinProtection() bool { + if m != nil && m.PinProtection != nil { + return *m.PinProtection + } + return false +} + +func (m *RecoveryDevice) GetLanguage() string { + if m != nil && m.Language != nil { + return *m.Language + } + return Default_RecoveryDevice_Language +} + +func (m *RecoveryDevice) GetLabel() string { + if m != nil && m.Label != nil { + return *m.Label + } + return "" +} + +func (m *RecoveryDevice) GetEnforceWordlist() bool { + if m != nil && m.EnforceWordlist != nil { + return *m.EnforceWordlist + } + return false +} + +func (m *RecoveryDevice) GetType() uint32 { + if m != nil && m.Type != nil { + return *m.Type + } + return 0 +} + +func (m *RecoveryDevice) GetU2FCounter() uint32 { + if m != nil && m.U2FCounter != nil { + return *m.U2FCounter + } + return 0 +} + +func (m *RecoveryDevice) GetDryRun() bool { + if m != nil && m.DryRun != nil { + return *m.DryRun + } + return false +} + +// * +// Response: Device is waiting for user to enter word of the mnemonic +// Its position is shown only on device's internal display. +// @prev RecoveryDevice +// @prev WordAck +type WordRequest struct { + Type *WordRequestType `protobuf:"varint,1,opt,name=type,enum=WordRequestType" json:"type,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *WordRequest) Reset() { *m = WordRequest{} } +func (m *WordRequest) String() string { return proto.CompactTextString(m) } +func (*WordRequest) ProtoMessage() {} +func (*WordRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{32} } + +func (m *WordRequest) GetType() WordRequestType { + if m != nil && m.Type != nil { + return *m.Type + } + return WordRequestType_WordRequestType_Plain +} + +// * +// Request: Computer replies with word from the mnemonic +// @prev WordRequest +// @next WordRequest +// @next Success +// @next Failure +type WordAck struct { + Word *string `protobuf:"bytes,1,req,name=word" json:"word,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *WordAck) Reset() { *m = WordAck{} } +func (m *WordAck) String() string { return proto.CompactTextString(m) } +func (*WordAck) ProtoMessage() {} +func (*WordAck) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{33} } + +func (m *WordAck) GetWord() string { + if m != nil && m.Word != nil { + return *m.Word + } + return "" +} + +// * +// Request: Ask device to sign message +// @next MessageSignature +// @next Failure +type SignMessage struct { + AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` + Message []byte `protobuf:"bytes,2,req,name=message" json:"message,omitempty"` + CoinName *string `protobuf:"bytes,3,opt,name=coin_name,json=coinName,def=Bitcoin" json:"coin_name,omitempty"` + ScriptType *InputScriptType `protobuf:"varint,4,opt,name=script_type,json=scriptType,enum=InputScriptType,def=0" json:"script_type,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *SignMessage) Reset() { *m = SignMessage{} } +func (m *SignMessage) String() string { return proto.CompactTextString(m) } +func (*SignMessage) ProtoMessage() {} +func (*SignMessage) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{34} } + +const Default_SignMessage_CoinName string = "Bitcoin" +const Default_SignMessage_ScriptType InputScriptType = InputScriptType_SPENDADDRESS + +func (m *SignMessage) GetAddressN() []uint32 { + if m != nil { + return m.AddressN + } + return nil +} + +func (m *SignMessage) GetMessage() []byte { + if m != nil { + return m.Message + } + return nil +} + +func (m *SignMessage) GetCoinName() string { + if m != nil && m.CoinName != nil { + return *m.CoinName + } + return Default_SignMessage_CoinName +} + +func (m *SignMessage) GetScriptType() InputScriptType { + if m != nil && m.ScriptType != nil { + return *m.ScriptType + } + return Default_SignMessage_ScriptType +} + +// * +// Request: Ask device to verify message +// @next Success +// @next Failure +type VerifyMessage struct { + Address *string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` + Signature []byte `protobuf:"bytes,2,opt,name=signature" json:"signature,omitempty"` + Message []byte `protobuf:"bytes,3,opt,name=message" json:"message,omitempty"` + CoinName *string `protobuf:"bytes,4,opt,name=coin_name,json=coinName,def=Bitcoin" json:"coin_name,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *VerifyMessage) Reset() { *m = VerifyMessage{} } +func (m *VerifyMessage) String() string { return proto.CompactTextString(m) } +func (*VerifyMessage) ProtoMessage() {} +func (*VerifyMessage) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{35} } + +const Default_VerifyMessage_CoinName string = "Bitcoin" + +func (m *VerifyMessage) GetAddress() string { + if m != nil && m.Address != nil { + return *m.Address + } + return "" +} + +func (m *VerifyMessage) GetSignature() []byte { + if m != nil { + return m.Signature + } + return nil +} + +func (m *VerifyMessage) GetMessage() []byte { + if m != nil { + return m.Message + } + return nil +} + +func (m *VerifyMessage) GetCoinName() string { + if m != nil && m.CoinName != nil { + return *m.CoinName + } + return Default_VerifyMessage_CoinName +} + +// * +// Response: Signed message +// @prev SignMessage +type MessageSignature struct { + Address *string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` + Signature []byte `protobuf:"bytes,2,opt,name=signature" json:"signature,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MessageSignature) Reset() { *m = MessageSignature{} } +func (m *MessageSignature) String() string { return proto.CompactTextString(m) } +func (*MessageSignature) ProtoMessage() {} +func (*MessageSignature) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{36} } + +func (m *MessageSignature) GetAddress() string { + if m != nil && m.Address != nil { + return *m.Address + } + return "" +} + +func (m *MessageSignature) GetSignature() []byte { + if m != nil { + return m.Signature + } + return nil +} + +// * +// Request: Ask device to encrypt message +// @next EncryptedMessage +// @next Failure +type EncryptMessage struct { + Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey" json:"pubkey,omitempty"` + Message []byte `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + DisplayOnly *bool `protobuf:"varint,3,opt,name=display_only,json=displayOnly" json:"display_only,omitempty"` + AddressN []uint32 `protobuf:"varint,4,rep,name=address_n,json=addressN" json:"address_n,omitempty"` + CoinName *string `protobuf:"bytes,5,opt,name=coin_name,json=coinName,def=Bitcoin" json:"coin_name,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EncryptMessage) Reset() { *m = EncryptMessage{} } +func (m *EncryptMessage) String() string { return proto.CompactTextString(m) } +func (*EncryptMessage) ProtoMessage() {} +func (*EncryptMessage) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{37} } + +const Default_EncryptMessage_CoinName string = "Bitcoin" + +func (m *EncryptMessage) GetPubkey() []byte { + if m != nil { + return m.Pubkey + } + return nil +} + +func (m *EncryptMessage) GetMessage() []byte { + if m != nil { + return m.Message + } + return nil +} + +func (m *EncryptMessage) GetDisplayOnly() bool { + if m != nil && m.DisplayOnly != nil { + return *m.DisplayOnly + } + return false +} + +func (m *EncryptMessage) GetAddressN() []uint32 { + if m != nil { + return m.AddressN + } + return nil +} + +func (m *EncryptMessage) GetCoinName() string { + if m != nil && m.CoinName != nil { + return *m.CoinName + } + return Default_EncryptMessage_CoinName +} + +// * +// Response: Encrypted message +// @prev EncryptMessage +type EncryptedMessage struct { + Nonce []byte `protobuf:"bytes,1,opt,name=nonce" json:"nonce,omitempty"` + Message []byte `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + Hmac []byte `protobuf:"bytes,3,opt,name=hmac" json:"hmac,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EncryptedMessage) Reset() { *m = EncryptedMessage{} } +func (m *EncryptedMessage) String() string { return proto.CompactTextString(m) } +func (*EncryptedMessage) ProtoMessage() {} +func (*EncryptedMessage) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{38} } + +func (m *EncryptedMessage) GetNonce() []byte { + if m != nil { + return m.Nonce + } + return nil +} + +func (m *EncryptedMessage) GetMessage() []byte { + if m != nil { + return m.Message + } + return nil +} + +func (m *EncryptedMessage) GetHmac() []byte { + if m != nil { + return m.Hmac + } + return nil +} + +// * +// Request: Ask device to decrypt message +// @next Success +// @next Failure +type DecryptMessage struct { + AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` + Nonce []byte `protobuf:"bytes,2,opt,name=nonce" json:"nonce,omitempty"` + Message []byte `protobuf:"bytes,3,opt,name=message" json:"message,omitempty"` + Hmac []byte `protobuf:"bytes,4,opt,name=hmac" json:"hmac,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DecryptMessage) Reset() { *m = DecryptMessage{} } +func (m *DecryptMessage) String() string { return proto.CompactTextString(m) } +func (*DecryptMessage) ProtoMessage() {} +func (*DecryptMessage) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{39} } + +func (m *DecryptMessage) GetAddressN() []uint32 { + if m != nil { + return m.AddressN + } + return nil +} + +func (m *DecryptMessage) GetNonce() []byte { + if m != nil { + return m.Nonce + } + return nil +} + +func (m *DecryptMessage) GetMessage() []byte { + if m != nil { + return m.Message + } + return nil +} + +func (m *DecryptMessage) GetHmac() []byte { + if m != nil { + return m.Hmac + } + return nil +} + +// * +// Response: Decrypted message +// @prev DecryptedMessage +type DecryptedMessage struct { + Message []byte `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"` + Address *string `protobuf:"bytes,2,opt,name=address" json:"address,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DecryptedMessage) Reset() { *m = DecryptedMessage{} } +func (m *DecryptedMessage) String() string { return proto.CompactTextString(m) } +func (*DecryptedMessage) ProtoMessage() {} +func (*DecryptedMessage) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{40} } + +func (m *DecryptedMessage) GetMessage() []byte { + if m != nil { + return m.Message + } + return nil +} + +func (m *DecryptedMessage) GetAddress() string { + if m != nil && m.Address != nil { + return *m.Address + } + return "" +} + +// * +// Request: Ask device to encrypt or decrypt value of given key +// @next CipheredKeyValue +// @next Failure +type CipherKeyValue struct { + AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` + Key *string `protobuf:"bytes,2,opt,name=key" json:"key,omitempty"` + Value []byte `protobuf:"bytes,3,opt,name=value" json:"value,omitempty"` + Encrypt *bool `protobuf:"varint,4,opt,name=encrypt" json:"encrypt,omitempty"` + AskOnEncrypt *bool `protobuf:"varint,5,opt,name=ask_on_encrypt,json=askOnEncrypt" json:"ask_on_encrypt,omitempty"` + AskOnDecrypt *bool `protobuf:"varint,6,opt,name=ask_on_decrypt,json=askOnDecrypt" json:"ask_on_decrypt,omitempty"` + Iv []byte `protobuf:"bytes,7,opt,name=iv" json:"iv,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *CipherKeyValue) Reset() { *m = CipherKeyValue{} } +func (m *CipherKeyValue) String() string { return proto.CompactTextString(m) } +func (*CipherKeyValue) ProtoMessage() {} +func (*CipherKeyValue) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{41} } + +func (m *CipherKeyValue) GetAddressN() []uint32 { + if m != nil { + return m.AddressN + } + return nil +} + +func (m *CipherKeyValue) GetKey() string { + if m != nil && m.Key != nil { + return *m.Key + } + return "" +} + +func (m *CipherKeyValue) GetValue() []byte { + if m != nil { + return m.Value + } + return nil +} + +func (m *CipherKeyValue) GetEncrypt() bool { + if m != nil && m.Encrypt != nil { + return *m.Encrypt + } + return false +} + +func (m *CipherKeyValue) GetAskOnEncrypt() bool { + if m != nil && m.AskOnEncrypt != nil { + return *m.AskOnEncrypt + } + return false +} + +func (m *CipherKeyValue) GetAskOnDecrypt() bool { + if m != nil && m.AskOnDecrypt != nil { + return *m.AskOnDecrypt + } + return false +} + +func (m *CipherKeyValue) GetIv() []byte { + if m != nil { + return m.Iv + } + return nil +} + +// * +// Response: Return ciphered/deciphered value +// @prev CipherKeyValue +type CipheredKeyValue struct { + Value []byte `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *CipheredKeyValue) Reset() { *m = CipheredKeyValue{} } +func (m *CipheredKeyValue) String() string { return proto.CompactTextString(m) } +func (*CipheredKeyValue) ProtoMessage() {} +func (*CipheredKeyValue) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{42} } + +func (m *CipheredKeyValue) GetValue() []byte { + if m != nil { + return m.Value + } + return nil +} + +// * +// Request: Estimated size of the transaction +// This behaves exactly like SignTx, which means that it can ask using TxRequest +// This call is non-blocking (except possible PassphraseRequest to unlock the seed) +// @next TxSize +// @next Failure +type EstimateTxSize struct { + OutputsCount *uint32 `protobuf:"varint,1,req,name=outputs_count,json=outputsCount" json:"outputs_count,omitempty"` + InputsCount *uint32 `protobuf:"varint,2,req,name=inputs_count,json=inputsCount" json:"inputs_count,omitempty"` + CoinName *string `protobuf:"bytes,3,opt,name=coin_name,json=coinName,def=Bitcoin" json:"coin_name,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EstimateTxSize) Reset() { *m = EstimateTxSize{} } +func (m *EstimateTxSize) String() string { return proto.CompactTextString(m) } +func (*EstimateTxSize) ProtoMessage() {} +func (*EstimateTxSize) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{43} } + +const Default_EstimateTxSize_CoinName string = "Bitcoin" + +func (m *EstimateTxSize) GetOutputsCount() uint32 { + if m != nil && m.OutputsCount != nil { + return *m.OutputsCount + } + return 0 +} + +func (m *EstimateTxSize) GetInputsCount() uint32 { + if m != nil && m.InputsCount != nil { + return *m.InputsCount + } + return 0 +} + +func (m *EstimateTxSize) GetCoinName() string { + if m != nil && m.CoinName != nil { + return *m.CoinName + } + return Default_EstimateTxSize_CoinName +} + +// * +// Response: Estimated size of the transaction +// @prev EstimateTxSize +type TxSize struct { + TxSize *uint32 `protobuf:"varint,1,opt,name=tx_size,json=txSize" json:"tx_size,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *TxSize) Reset() { *m = TxSize{} } +func (m *TxSize) String() string { return proto.CompactTextString(m) } +func (*TxSize) ProtoMessage() {} +func (*TxSize) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{44} } + +func (m *TxSize) GetTxSize() uint32 { + if m != nil && m.TxSize != nil { + return *m.TxSize + } + return 0 +} + +// * +// Request: Ask device to sign transaction +// @next PassphraseRequest +// @next PinMatrixRequest +// @next TxRequest +// @next Failure +type SignTx struct { + OutputsCount *uint32 `protobuf:"varint,1,req,name=outputs_count,json=outputsCount" json:"outputs_count,omitempty"` + InputsCount *uint32 `protobuf:"varint,2,req,name=inputs_count,json=inputsCount" json:"inputs_count,omitempty"` + CoinName *string `protobuf:"bytes,3,opt,name=coin_name,json=coinName,def=Bitcoin" json:"coin_name,omitempty"` + Version *uint32 `protobuf:"varint,4,opt,name=version,def=1" json:"version,omitempty"` + LockTime *uint32 `protobuf:"varint,5,opt,name=lock_time,json=lockTime,def=0" json:"lock_time,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *SignTx) Reset() { *m = SignTx{} } +func (m *SignTx) String() string { return proto.CompactTextString(m) } +func (*SignTx) ProtoMessage() {} +func (*SignTx) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{45} } + +const Default_SignTx_CoinName string = "Bitcoin" +const Default_SignTx_Version uint32 = 1 +const Default_SignTx_LockTime uint32 = 0 + +func (m *SignTx) GetOutputsCount() uint32 { + if m != nil && m.OutputsCount != nil { + return *m.OutputsCount + } + return 0 +} + +func (m *SignTx) GetInputsCount() uint32 { + if m != nil && m.InputsCount != nil { + return *m.InputsCount + } + return 0 +} + +func (m *SignTx) GetCoinName() string { + if m != nil && m.CoinName != nil { + return *m.CoinName + } + return Default_SignTx_CoinName +} + +func (m *SignTx) GetVersion() uint32 { + if m != nil && m.Version != nil { + return *m.Version + } + return Default_SignTx_Version +} + +func (m *SignTx) GetLockTime() uint32 { + if m != nil && m.LockTime != nil { + return *m.LockTime + } + return Default_SignTx_LockTime +} + +// * +// Request: Simplified transaction signing +// This method doesn't support streaming, so there are hardware limits in number of inputs and outputs. +// In case of success, the result is returned using TxRequest message. +// @next PassphraseRequest +// @next PinMatrixRequest +// @next TxRequest +// @next Failure +type SimpleSignTx struct { + Inputs []*TxInputType `protobuf:"bytes,1,rep,name=inputs" json:"inputs,omitempty"` + Outputs []*TxOutputType `protobuf:"bytes,2,rep,name=outputs" json:"outputs,omitempty"` + Transactions []*TransactionType `protobuf:"bytes,3,rep,name=transactions" json:"transactions,omitempty"` + CoinName *string `protobuf:"bytes,4,opt,name=coin_name,json=coinName,def=Bitcoin" json:"coin_name,omitempty"` + Version *uint32 `protobuf:"varint,5,opt,name=version,def=1" json:"version,omitempty"` + LockTime *uint32 `protobuf:"varint,6,opt,name=lock_time,json=lockTime,def=0" json:"lock_time,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *SimpleSignTx) Reset() { *m = SimpleSignTx{} } +func (m *SimpleSignTx) String() string { return proto.CompactTextString(m) } +func (*SimpleSignTx) ProtoMessage() {} +func (*SimpleSignTx) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{46} } + +const Default_SimpleSignTx_CoinName string = "Bitcoin" +const Default_SimpleSignTx_Version uint32 = 1 +const Default_SimpleSignTx_LockTime uint32 = 0 + +func (m *SimpleSignTx) GetInputs() []*TxInputType { + if m != nil { + return m.Inputs + } + return nil +} + +func (m *SimpleSignTx) GetOutputs() []*TxOutputType { + if m != nil { + return m.Outputs + } + return nil +} + +func (m *SimpleSignTx) GetTransactions() []*TransactionType { + if m != nil { + return m.Transactions + } + return nil +} + +func (m *SimpleSignTx) GetCoinName() string { + if m != nil && m.CoinName != nil { + return *m.CoinName + } + return Default_SimpleSignTx_CoinName +} + +func (m *SimpleSignTx) GetVersion() uint32 { + if m != nil && m.Version != nil { + return *m.Version + } + return Default_SimpleSignTx_Version +} + +func (m *SimpleSignTx) GetLockTime() uint32 { + if m != nil && m.LockTime != nil { + return *m.LockTime + } + return Default_SimpleSignTx_LockTime +} + +// * +// Response: Device asks for information for signing transaction or returns the last result +// If request_index is set, device awaits TxAck message (with fields filled in according to request_type) +// If signature_index is set, 'signature' contains signed input of signature_index's input +// @prev SignTx +// @prev SimpleSignTx +// @prev TxAck +type TxRequest struct { + RequestType *RequestType `protobuf:"varint,1,opt,name=request_type,json=requestType,enum=RequestType" json:"request_type,omitempty"` + Details *TxRequestDetailsType `protobuf:"bytes,2,opt,name=details" json:"details,omitempty"` + Serialized *TxRequestSerializedType `protobuf:"bytes,3,opt,name=serialized" json:"serialized,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *TxRequest) Reset() { *m = TxRequest{} } +func (m *TxRequest) String() string { return proto.CompactTextString(m) } +func (*TxRequest) ProtoMessage() {} +func (*TxRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{47} } + +func (m *TxRequest) GetRequestType() RequestType { + if m != nil && m.RequestType != nil { + return *m.RequestType + } + return RequestType_TXINPUT +} + +func (m *TxRequest) GetDetails() *TxRequestDetailsType { + if m != nil { + return m.Details + } + return nil +} + +func (m *TxRequest) GetSerialized() *TxRequestSerializedType { + if m != nil { + return m.Serialized + } + return nil +} + +// * +// Request: Reported transaction data +// @prev TxRequest +// @next TxRequest +type TxAck struct { + Tx *TransactionType `protobuf:"bytes,1,opt,name=tx" json:"tx,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *TxAck) Reset() { *m = TxAck{} } +func (m *TxAck) String() string { return proto.CompactTextString(m) } +func (*TxAck) ProtoMessage() {} +func (*TxAck) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{48} } + +func (m *TxAck) GetTx() *TransactionType { + if m != nil { + return m.Tx + } + return nil +} + +// * +// Request: Ask device to sign transaction +// All fields are optional from the protocol's point of view. Each field defaults to value `0` if missing. +// Note: the first at most 1024 bytes of data MUST be transmitted as part of this message. +// @next PassphraseRequest +// @next PinMatrixRequest +// @next EthereumTxRequest +// @next Failure +type EthereumSignTx struct { + AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` + Nonce []byte `protobuf:"bytes,2,opt,name=nonce" json:"nonce,omitempty"` + GasPrice []byte `protobuf:"bytes,3,opt,name=gas_price,json=gasPrice" json:"gas_price,omitempty"` + GasLimit []byte `protobuf:"bytes,4,opt,name=gas_limit,json=gasLimit" json:"gas_limit,omitempty"` + To []byte `protobuf:"bytes,5,opt,name=to" json:"to,omitempty"` + Value []byte `protobuf:"bytes,6,opt,name=value" json:"value,omitempty"` + DataInitialChunk []byte `protobuf:"bytes,7,opt,name=data_initial_chunk,json=dataInitialChunk" json:"data_initial_chunk,omitempty"` + DataLength *uint32 `protobuf:"varint,8,opt,name=data_length,json=dataLength" json:"data_length,omitempty"` + ChainId *uint32 `protobuf:"varint,9,opt,name=chain_id,json=chainId" json:"chain_id,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EthereumSignTx) Reset() { *m = EthereumSignTx{} } +func (m *EthereumSignTx) String() string { return proto.CompactTextString(m) } +func (*EthereumSignTx) ProtoMessage() {} +func (*EthereumSignTx) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{49} } + +func (m *EthereumSignTx) GetAddressN() []uint32 { + if m != nil { + return m.AddressN + } + return nil +} + +func (m *EthereumSignTx) GetNonce() []byte { + if m != nil { + return m.Nonce + } + return nil +} + +func (m *EthereumSignTx) GetGasPrice() []byte { + if m != nil { + return m.GasPrice + } + return nil +} + +func (m *EthereumSignTx) GetGasLimit() []byte { + if m != nil { + return m.GasLimit + } + return nil +} + +func (m *EthereumSignTx) GetTo() []byte { + if m != nil { + return m.To + } + return nil +} + +func (m *EthereumSignTx) GetValue() []byte { + if m != nil { + return m.Value + } + return nil +} + +func (m *EthereumSignTx) GetDataInitialChunk() []byte { + if m != nil { + return m.DataInitialChunk + } + return nil +} + +func (m *EthereumSignTx) GetDataLength() uint32 { + if m != nil && m.DataLength != nil { + return *m.DataLength + } + return 0 +} + +func (m *EthereumSignTx) GetChainId() uint32 { + if m != nil && m.ChainId != nil { + return *m.ChainId + } + return 0 +} + +// * +// Response: Device asks for more data from transaction payload, or returns the signature. +// If data_length is set, device awaits that many more bytes of payload. +// Otherwise, the signature_* fields contain the computed transaction signature. All three fields will be present. +// @prev EthereumSignTx +// @next EthereumTxAck +type EthereumTxRequest struct { + DataLength *uint32 `protobuf:"varint,1,opt,name=data_length,json=dataLength" json:"data_length,omitempty"` + SignatureV *uint32 `protobuf:"varint,2,opt,name=signature_v,json=signatureV" json:"signature_v,omitempty"` + SignatureR []byte `protobuf:"bytes,3,opt,name=signature_r,json=signatureR" json:"signature_r,omitempty"` + SignatureS []byte `protobuf:"bytes,4,opt,name=signature_s,json=signatureS" json:"signature_s,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EthereumTxRequest) Reset() { *m = EthereumTxRequest{} } +func (m *EthereumTxRequest) String() string { return proto.CompactTextString(m) } +func (*EthereumTxRequest) ProtoMessage() {} +func (*EthereumTxRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{50} } + +func (m *EthereumTxRequest) GetDataLength() uint32 { + if m != nil && m.DataLength != nil { + return *m.DataLength + } + return 0 +} + +func (m *EthereumTxRequest) GetSignatureV() uint32 { + if m != nil && m.SignatureV != nil { + return *m.SignatureV + } + return 0 +} + +func (m *EthereumTxRequest) GetSignatureR() []byte { + if m != nil { + return m.SignatureR + } + return nil +} + +func (m *EthereumTxRequest) GetSignatureS() []byte { + if m != nil { + return m.SignatureS + } + return nil +} + +// * +// Request: Transaction payload data. +// @prev EthereumTxRequest +// @next EthereumTxRequest +type EthereumTxAck struct { + DataChunk []byte `protobuf:"bytes,1,opt,name=data_chunk,json=dataChunk" json:"data_chunk,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EthereumTxAck) Reset() { *m = EthereumTxAck{} } +func (m *EthereumTxAck) String() string { return proto.CompactTextString(m) } +func (*EthereumTxAck) ProtoMessage() {} +func (*EthereumTxAck) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{51} } + +func (m *EthereumTxAck) GetDataChunk() []byte { + if m != nil { + return m.DataChunk + } + return nil +} + +// * +// Request: Ask device to sign message +// @next EthereumMessageSignature +// @next Failure +type EthereumSignMessage struct { + AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` + Message []byte `protobuf:"bytes,2,req,name=message" json:"message,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EthereumSignMessage) Reset() { *m = EthereumSignMessage{} } +func (m *EthereumSignMessage) String() string { return proto.CompactTextString(m) } +func (*EthereumSignMessage) ProtoMessage() {} +func (*EthereumSignMessage) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{52} } + +func (m *EthereumSignMessage) GetAddressN() []uint32 { + if m != nil { + return m.AddressN + } + return nil +} + +func (m *EthereumSignMessage) GetMessage() []byte { + if m != nil { + return m.Message + } + return nil +} + +// * +// Request: Ask device to verify message +// @next Success +// @next Failure +type EthereumVerifyMessage struct { + Address []byte `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` + Signature []byte `protobuf:"bytes,2,opt,name=signature" json:"signature,omitempty"` + Message []byte `protobuf:"bytes,3,opt,name=message" json:"message,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EthereumVerifyMessage) Reset() { *m = EthereumVerifyMessage{} } +func (m *EthereumVerifyMessage) String() string { return proto.CompactTextString(m) } +func (*EthereumVerifyMessage) ProtoMessage() {} +func (*EthereumVerifyMessage) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{53} } + +func (m *EthereumVerifyMessage) GetAddress() []byte { + if m != nil { + return m.Address + } + return nil +} + +func (m *EthereumVerifyMessage) GetSignature() []byte { + if m != nil { + return m.Signature + } + return nil +} + +func (m *EthereumVerifyMessage) GetMessage() []byte { + if m != nil { + return m.Message + } + return nil +} + +// * +// Response: Signed message +// @prev EthereumSignMessage +type EthereumMessageSignature struct { + Address []byte `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` + Signature []byte `protobuf:"bytes,2,opt,name=signature" json:"signature,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *EthereumMessageSignature) Reset() { *m = EthereumMessageSignature{} } +func (m *EthereumMessageSignature) String() string { return proto.CompactTextString(m) } +func (*EthereumMessageSignature) ProtoMessage() {} +func (*EthereumMessageSignature) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{54} } + +func (m *EthereumMessageSignature) GetAddress() []byte { + if m != nil { + return m.Address + } + return nil +} + +func (m *EthereumMessageSignature) GetSignature() []byte { + if m != nil { + return m.Signature + } + return nil +} + +// * +// Request: Ask device to sign identity +// @next SignedIdentity +// @next Failure +type SignIdentity struct { + Identity *IdentityType `protobuf:"bytes,1,opt,name=identity" json:"identity,omitempty"` + ChallengeHidden []byte `protobuf:"bytes,2,opt,name=challenge_hidden,json=challengeHidden" json:"challenge_hidden,omitempty"` + ChallengeVisual *string `protobuf:"bytes,3,opt,name=challenge_visual,json=challengeVisual" json:"challenge_visual,omitempty"` + EcdsaCurveName *string `protobuf:"bytes,4,opt,name=ecdsa_curve_name,json=ecdsaCurveName" json:"ecdsa_curve_name,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *SignIdentity) Reset() { *m = SignIdentity{} } +func (m *SignIdentity) String() string { return proto.CompactTextString(m) } +func (*SignIdentity) ProtoMessage() {} +func (*SignIdentity) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{55} } + +func (m *SignIdentity) GetIdentity() *IdentityType { + if m != nil { + return m.Identity + } + return nil +} + +func (m *SignIdentity) GetChallengeHidden() []byte { + if m != nil { + return m.ChallengeHidden + } + return nil +} + +func (m *SignIdentity) GetChallengeVisual() string { + if m != nil && m.ChallengeVisual != nil { + return *m.ChallengeVisual + } + return "" +} + +func (m *SignIdentity) GetEcdsaCurveName() string { + if m != nil && m.EcdsaCurveName != nil { + return *m.EcdsaCurveName + } + return "" +} + +// * +// Response: Device provides signed identity +// @prev SignIdentity +type SignedIdentity struct { + Address *string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` + PublicKey []byte `protobuf:"bytes,2,opt,name=public_key,json=publicKey" json:"public_key,omitempty"` + Signature []byte `protobuf:"bytes,3,opt,name=signature" json:"signature,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *SignedIdentity) Reset() { *m = SignedIdentity{} } +func (m *SignedIdentity) String() string { return proto.CompactTextString(m) } +func (*SignedIdentity) ProtoMessage() {} +func (*SignedIdentity) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{56} } + +func (m *SignedIdentity) GetAddress() string { + if m != nil && m.Address != nil { + return *m.Address + } + return "" +} + +func (m *SignedIdentity) GetPublicKey() []byte { + if m != nil { + return m.PublicKey + } + return nil +} + +func (m *SignedIdentity) GetSignature() []byte { + if m != nil { + return m.Signature + } + return nil +} + +// * +// Request: Ask device to generate ECDH session key +// @next ECDHSessionKey +// @next Failure +type GetECDHSessionKey struct { + Identity *IdentityType `protobuf:"bytes,1,opt,name=identity" json:"identity,omitempty"` + PeerPublicKey []byte `protobuf:"bytes,2,opt,name=peer_public_key,json=peerPublicKey" json:"peer_public_key,omitempty"` + EcdsaCurveName *string `protobuf:"bytes,3,opt,name=ecdsa_curve_name,json=ecdsaCurveName" json:"ecdsa_curve_name,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GetECDHSessionKey) Reset() { *m = GetECDHSessionKey{} } +func (m *GetECDHSessionKey) String() string { return proto.CompactTextString(m) } +func (*GetECDHSessionKey) ProtoMessage() {} +func (*GetECDHSessionKey) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{57} } + +func (m *GetECDHSessionKey) GetIdentity() *IdentityType { + if m != nil { + return m.Identity + } + return nil +} + +func (m *GetECDHSessionKey) GetPeerPublicKey() []byte { + if m != nil { + return m.PeerPublicKey + } + return nil +} + +func (m *GetECDHSessionKey) GetEcdsaCurveName() string { + if m != nil && m.EcdsaCurveName != nil { + return *m.EcdsaCurveName + } + return "" +} + +// * +// Response: Device provides ECDH session key +// @prev GetECDHSessionKey +type ECDHSessionKey struct { + SessionKey []byte `protobuf:"bytes,1,opt,name=session_key,json=sessionKey" json:"session_key,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *ECDHSessionKey) Reset() { *m = ECDHSessionKey{} } +func (m *ECDHSessionKey) String() string { return proto.CompactTextString(m) } +func (*ECDHSessionKey) ProtoMessage() {} +func (*ECDHSessionKey) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{58} } + +func (m *ECDHSessionKey) GetSessionKey() []byte { + if m != nil { + return m.SessionKey + } + return nil +} + +// * +// Request: Set U2F counter +// @next Success +type SetU2FCounter struct { + U2FCounter *uint32 `protobuf:"varint,1,opt,name=u2f_counter,json=u2fCounter" json:"u2f_counter,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *SetU2FCounter) Reset() { *m = SetU2FCounter{} } +func (m *SetU2FCounter) String() string { return proto.CompactTextString(m) } +func (*SetU2FCounter) ProtoMessage() {} +func (*SetU2FCounter) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{59} } + +func (m *SetU2FCounter) GetU2FCounter() uint32 { + if m != nil && m.U2FCounter != nil { + return *m.U2FCounter + } + return 0 +} + +// * +// Request: Ask device to erase its firmware (so it can be replaced via FirmwareUpload) +// @next Success +// @next FirmwareRequest +// @next Failure +type FirmwareErase struct { + Length *uint32 `protobuf:"varint,1,opt,name=length" json:"length,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *FirmwareErase) Reset() { *m = FirmwareErase{} } +func (m *FirmwareErase) String() string { return proto.CompactTextString(m) } +func (*FirmwareErase) ProtoMessage() {} +func (*FirmwareErase) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{60} } + +func (m *FirmwareErase) GetLength() uint32 { + if m != nil && m.Length != nil { + return *m.Length + } + return 0 +} + +// * +// Response: Ask for firmware chunk +// @next FirmwareUpload +type FirmwareRequest struct { + Offset *uint32 `protobuf:"varint,1,opt,name=offset" json:"offset,omitempty"` + Length *uint32 `protobuf:"varint,2,opt,name=length" json:"length,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *FirmwareRequest) Reset() { *m = FirmwareRequest{} } +func (m *FirmwareRequest) String() string { return proto.CompactTextString(m) } +func (*FirmwareRequest) ProtoMessage() {} +func (*FirmwareRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{61} } + +func (m *FirmwareRequest) GetOffset() uint32 { + if m != nil && m.Offset != nil { + return *m.Offset + } + return 0 +} + +func (m *FirmwareRequest) GetLength() uint32 { + if m != nil && m.Length != nil { + return *m.Length + } + return 0 +} + +// * +// Request: Send firmware in binary form to the device +// @next Success +// @next Failure +type FirmwareUpload struct { + Payload []byte `protobuf:"bytes,1,req,name=payload" json:"payload,omitempty"` + Hash []byte `protobuf:"bytes,2,opt,name=hash" json:"hash,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *FirmwareUpload) Reset() { *m = FirmwareUpload{} } +func (m *FirmwareUpload) String() string { return proto.CompactTextString(m) } +func (*FirmwareUpload) ProtoMessage() {} +func (*FirmwareUpload) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{62} } + +func (m *FirmwareUpload) GetPayload() []byte { + if m != nil { + return m.Payload + } + return nil +} + +func (m *FirmwareUpload) GetHash() []byte { + if m != nil { + return m.Hash + } + return nil +} + +// * +// Request: Perform a device self-test +// @next Success +// @next Failure +type SelfTest struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload" json:"payload,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *SelfTest) Reset() { *m = SelfTest{} } +func (m *SelfTest) String() string { return proto.CompactTextString(m) } +func (*SelfTest) ProtoMessage() {} +func (*SelfTest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{63} } + +func (m *SelfTest) GetPayload() []byte { + if m != nil { + return m.Payload + } + return nil +} + +// * +// Request: "Press" the button on the device +// @next Success +type DebugLinkDecision struct { + YesNo *bool `protobuf:"varint,1,req,name=yes_no,json=yesNo" json:"yes_no,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DebugLinkDecision) Reset() { *m = DebugLinkDecision{} } +func (m *DebugLinkDecision) String() string { return proto.CompactTextString(m) } +func (*DebugLinkDecision) ProtoMessage() {} +func (*DebugLinkDecision) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{64} } + +func (m *DebugLinkDecision) GetYesNo() bool { + if m != nil && m.YesNo != nil { + return *m.YesNo + } + return false +} + +// * +// Request: Computer asks for device state +// @next DebugLinkState +type DebugLinkGetState struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *DebugLinkGetState) Reset() { *m = DebugLinkGetState{} } +func (m *DebugLinkGetState) String() string { return proto.CompactTextString(m) } +func (*DebugLinkGetState) ProtoMessage() {} +func (*DebugLinkGetState) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{65} } + +// * +// Response: Device current state +// @prev DebugLinkGetState +type DebugLinkState struct { + Layout []byte `protobuf:"bytes,1,opt,name=layout" json:"layout,omitempty"` + Pin *string `protobuf:"bytes,2,opt,name=pin" json:"pin,omitempty"` + Matrix *string `protobuf:"bytes,3,opt,name=matrix" json:"matrix,omitempty"` + Mnemonic *string `protobuf:"bytes,4,opt,name=mnemonic" json:"mnemonic,omitempty"` + Node *HDNodeType `protobuf:"bytes,5,opt,name=node" json:"node,omitempty"` + PassphraseProtection *bool `protobuf:"varint,6,opt,name=passphrase_protection,json=passphraseProtection" json:"passphrase_protection,omitempty"` + ResetWord *string `protobuf:"bytes,7,opt,name=reset_word,json=resetWord" json:"reset_word,omitempty"` + ResetEntropy []byte `protobuf:"bytes,8,opt,name=reset_entropy,json=resetEntropy" json:"reset_entropy,omitempty"` + RecoveryFakeWord *string `protobuf:"bytes,9,opt,name=recovery_fake_word,json=recoveryFakeWord" json:"recovery_fake_word,omitempty"` + RecoveryWordPos *uint32 `protobuf:"varint,10,opt,name=recovery_word_pos,json=recoveryWordPos" json:"recovery_word_pos,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DebugLinkState) Reset() { *m = DebugLinkState{} } +func (m *DebugLinkState) String() string { return proto.CompactTextString(m) } +func (*DebugLinkState) ProtoMessage() {} +func (*DebugLinkState) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{66} } + +func (m *DebugLinkState) GetLayout() []byte { + if m != nil { + return m.Layout + } + return nil +} + +func (m *DebugLinkState) GetPin() string { + if m != nil && m.Pin != nil { + return *m.Pin + } + return "" +} + +func (m *DebugLinkState) GetMatrix() string { + if m != nil && m.Matrix != nil { + return *m.Matrix + } + return "" +} + +func (m *DebugLinkState) GetMnemonic() string { + if m != nil && m.Mnemonic != nil { + return *m.Mnemonic + } + return "" +} + +func (m *DebugLinkState) GetNode() *HDNodeType { + if m != nil { + return m.Node + } + return nil +} + +func (m *DebugLinkState) GetPassphraseProtection() bool { + if m != nil && m.PassphraseProtection != nil { + return *m.PassphraseProtection + } + return false +} + +func (m *DebugLinkState) GetResetWord() string { + if m != nil && m.ResetWord != nil { + return *m.ResetWord + } + return "" +} + +func (m *DebugLinkState) GetResetEntropy() []byte { + if m != nil { + return m.ResetEntropy + } + return nil +} + +func (m *DebugLinkState) GetRecoveryFakeWord() string { + if m != nil && m.RecoveryFakeWord != nil { + return *m.RecoveryFakeWord + } + return "" +} + +func (m *DebugLinkState) GetRecoveryWordPos() uint32 { + if m != nil && m.RecoveryWordPos != nil { + return *m.RecoveryWordPos + } + return 0 +} + +// * +// Request: Ask device to restart +type DebugLinkStop struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *DebugLinkStop) Reset() { *m = DebugLinkStop{} } +func (m *DebugLinkStop) String() string { return proto.CompactTextString(m) } +func (*DebugLinkStop) ProtoMessage() {} +func (*DebugLinkStop) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{67} } + +// * +// Response: Device wants host to log event +type DebugLinkLog struct { + Level *uint32 `protobuf:"varint,1,opt,name=level" json:"level,omitempty"` + Bucket *string `protobuf:"bytes,2,opt,name=bucket" json:"bucket,omitempty"` + Text *string `protobuf:"bytes,3,opt,name=text" json:"text,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DebugLinkLog) Reset() { *m = DebugLinkLog{} } +func (m *DebugLinkLog) String() string { return proto.CompactTextString(m) } +func (*DebugLinkLog) ProtoMessage() {} +func (*DebugLinkLog) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{68} } + +func (m *DebugLinkLog) GetLevel() uint32 { + if m != nil && m.Level != nil { + return *m.Level + } + return 0 +} + +func (m *DebugLinkLog) GetBucket() string { + if m != nil && m.Bucket != nil { + return *m.Bucket + } + return "" +} + +func (m *DebugLinkLog) GetText() string { + if m != nil && m.Text != nil { + return *m.Text + } + return "" +} + +// * +// Request: Read memory from device +// @next DebugLinkMemory +type DebugLinkMemoryRead struct { + Address *uint32 `protobuf:"varint,1,opt,name=address" json:"address,omitempty"` + Length *uint32 `protobuf:"varint,2,opt,name=length" json:"length,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DebugLinkMemoryRead) Reset() { *m = DebugLinkMemoryRead{} } +func (m *DebugLinkMemoryRead) String() string { return proto.CompactTextString(m) } +func (*DebugLinkMemoryRead) ProtoMessage() {} +func (*DebugLinkMemoryRead) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{69} } + +func (m *DebugLinkMemoryRead) GetAddress() uint32 { + if m != nil && m.Address != nil { + return *m.Address + } + return 0 +} + +func (m *DebugLinkMemoryRead) GetLength() uint32 { + if m != nil && m.Length != nil { + return *m.Length + } + return 0 +} + +// * +// Response: Device sends memory back +// @prev DebugLinkMemoryRead +type DebugLinkMemory struct { + Memory []byte `protobuf:"bytes,1,opt,name=memory" json:"memory,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DebugLinkMemory) Reset() { *m = DebugLinkMemory{} } +func (m *DebugLinkMemory) String() string { return proto.CompactTextString(m) } +func (*DebugLinkMemory) ProtoMessage() {} +func (*DebugLinkMemory) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{70} } + +func (m *DebugLinkMemory) GetMemory() []byte { + if m != nil { + return m.Memory + } + return nil +} + +// * +// Request: Write memory to device. +// WARNING: Writing to the wrong location can irreparably break the device. +type DebugLinkMemoryWrite struct { + Address *uint32 `protobuf:"varint,1,opt,name=address" json:"address,omitempty"` + Memory []byte `protobuf:"bytes,2,opt,name=memory" json:"memory,omitempty"` + Flash *bool `protobuf:"varint,3,opt,name=flash" json:"flash,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DebugLinkMemoryWrite) Reset() { *m = DebugLinkMemoryWrite{} } +func (m *DebugLinkMemoryWrite) String() string { return proto.CompactTextString(m) } +func (*DebugLinkMemoryWrite) ProtoMessage() {} +func (*DebugLinkMemoryWrite) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{71} } + +func (m *DebugLinkMemoryWrite) GetAddress() uint32 { + if m != nil && m.Address != nil { + return *m.Address + } + return 0 +} + +func (m *DebugLinkMemoryWrite) GetMemory() []byte { + if m != nil { + return m.Memory + } + return nil +} + +func (m *DebugLinkMemoryWrite) GetFlash() bool { + if m != nil && m.Flash != nil { + return *m.Flash + } + return false +} + +// * +// Request: Erase block of flash on device +// WARNING: Writing to the wrong location can irreparably break the device. +type DebugLinkFlashErase struct { + Sector *uint32 `protobuf:"varint,1,opt,name=sector" json:"sector,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DebugLinkFlashErase) Reset() { *m = DebugLinkFlashErase{} } +func (m *DebugLinkFlashErase) String() string { return proto.CompactTextString(m) } +func (*DebugLinkFlashErase) ProtoMessage() {} +func (*DebugLinkFlashErase) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{72} } + +func (m *DebugLinkFlashErase) GetSector() uint32 { + if m != nil && m.Sector != nil { + return *m.Sector + } + return 0 +} + +func init() { + proto.RegisterType((*Initialize)(nil), "Initialize") + proto.RegisterType((*GetFeatures)(nil), "GetFeatures") + proto.RegisterType((*Features)(nil), "Features") + proto.RegisterType((*ClearSession)(nil), "ClearSession") + proto.RegisterType((*ApplySettings)(nil), "ApplySettings") + proto.RegisterType((*ApplyFlags)(nil), "ApplyFlags") + proto.RegisterType((*ChangePin)(nil), "ChangePin") + proto.RegisterType((*Ping)(nil), "Ping") + proto.RegisterType((*Success)(nil), "Success") + proto.RegisterType((*Failure)(nil), "Failure") + proto.RegisterType((*ButtonRequest)(nil), "ButtonRequest") + proto.RegisterType((*ButtonAck)(nil), "ButtonAck") + proto.RegisterType((*PinMatrixRequest)(nil), "PinMatrixRequest") + proto.RegisterType((*PinMatrixAck)(nil), "PinMatrixAck") + proto.RegisterType((*Cancel)(nil), "Cancel") + proto.RegisterType((*PassphraseRequest)(nil), "PassphraseRequest") + proto.RegisterType((*PassphraseAck)(nil), "PassphraseAck") + proto.RegisterType((*GetEntropy)(nil), "GetEntropy") + proto.RegisterType((*Entropy)(nil), "Entropy") + proto.RegisterType((*GetPublicKey)(nil), "GetPublicKey") + proto.RegisterType((*PublicKey)(nil), "PublicKey") + proto.RegisterType((*GetAddress)(nil), "GetAddress") + proto.RegisterType((*EthereumGetAddress)(nil), "EthereumGetAddress") + proto.RegisterType((*Address)(nil), "Address") + proto.RegisterType((*EthereumAddress)(nil), "EthereumAddress") + proto.RegisterType((*WipeDevice)(nil), "WipeDevice") + proto.RegisterType((*LoadDevice)(nil), "LoadDevice") + proto.RegisterType((*ResetDevice)(nil), "ResetDevice") + proto.RegisterType((*BackupDevice)(nil), "BackupDevice") + proto.RegisterType((*EntropyRequest)(nil), "EntropyRequest") + proto.RegisterType((*EntropyAck)(nil), "EntropyAck") + proto.RegisterType((*RecoveryDevice)(nil), "RecoveryDevice") + proto.RegisterType((*WordRequest)(nil), "WordRequest") + proto.RegisterType((*WordAck)(nil), "WordAck") + proto.RegisterType((*SignMessage)(nil), "SignMessage") + proto.RegisterType((*VerifyMessage)(nil), "VerifyMessage") + proto.RegisterType((*MessageSignature)(nil), "MessageSignature") + proto.RegisterType((*EncryptMessage)(nil), "EncryptMessage") + proto.RegisterType((*EncryptedMessage)(nil), "EncryptedMessage") + proto.RegisterType((*DecryptMessage)(nil), "DecryptMessage") + proto.RegisterType((*DecryptedMessage)(nil), "DecryptedMessage") + proto.RegisterType((*CipherKeyValue)(nil), "CipherKeyValue") + proto.RegisterType((*CipheredKeyValue)(nil), "CipheredKeyValue") + proto.RegisterType((*EstimateTxSize)(nil), "EstimateTxSize") + proto.RegisterType((*TxSize)(nil), "TxSize") + proto.RegisterType((*SignTx)(nil), "SignTx") + proto.RegisterType((*SimpleSignTx)(nil), "SimpleSignTx") + proto.RegisterType((*TxRequest)(nil), "TxRequest") + proto.RegisterType((*TxAck)(nil), "TxAck") + proto.RegisterType((*EthereumSignTx)(nil), "EthereumSignTx") + proto.RegisterType((*EthereumTxRequest)(nil), "EthereumTxRequest") + proto.RegisterType((*EthereumTxAck)(nil), "EthereumTxAck") + proto.RegisterType((*EthereumSignMessage)(nil), "EthereumSignMessage") + proto.RegisterType((*EthereumVerifyMessage)(nil), "EthereumVerifyMessage") + proto.RegisterType((*EthereumMessageSignature)(nil), "EthereumMessageSignature") + proto.RegisterType((*SignIdentity)(nil), "SignIdentity") + proto.RegisterType((*SignedIdentity)(nil), "SignedIdentity") + proto.RegisterType((*GetECDHSessionKey)(nil), "GetECDHSessionKey") + proto.RegisterType((*ECDHSessionKey)(nil), "ECDHSessionKey") + proto.RegisterType((*SetU2FCounter)(nil), "SetU2FCounter") + proto.RegisterType((*FirmwareErase)(nil), "FirmwareErase") + proto.RegisterType((*FirmwareRequest)(nil), "FirmwareRequest") + proto.RegisterType((*FirmwareUpload)(nil), "FirmwareUpload") + proto.RegisterType((*SelfTest)(nil), "SelfTest") + proto.RegisterType((*DebugLinkDecision)(nil), "DebugLinkDecision") + proto.RegisterType((*DebugLinkGetState)(nil), "DebugLinkGetState") + proto.RegisterType((*DebugLinkState)(nil), "DebugLinkState") + proto.RegisterType((*DebugLinkStop)(nil), "DebugLinkStop") + proto.RegisterType((*DebugLinkLog)(nil), "DebugLinkLog") + proto.RegisterType((*DebugLinkMemoryRead)(nil), "DebugLinkMemoryRead") + proto.RegisterType((*DebugLinkMemory)(nil), "DebugLinkMemory") + proto.RegisterType((*DebugLinkMemoryWrite)(nil), "DebugLinkMemoryWrite") + proto.RegisterType((*DebugLinkFlashErase)(nil), "DebugLinkFlashErase") + proto.RegisterEnum("MessageType", MessageType_name, MessageType_value) +} + +func init() { proto.RegisterFile("messages.proto", fileDescriptor1) } + +var fileDescriptor1 = []byte{ + // 3424 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5a, 0xcb, 0x6f, 0xdc, 0x46, + 0x9a, 0x5f, 0x76, 0xb7, 0xfa, 0xf1, 0x35, 0xbb, 0x55, 0xa2, 0x2d, 0xbb, 0x2d, 0x5b, 0xb6, 0x4c, + 0xc9, 0xb6, 0x64, 0x27, 0xed, 0x44, 0x79, 0x6c, 0xd6, 0xbb, 0x79, 0xc8, 0x7a, 0xd8, 0xde, 0xd8, + 0x8e, 0xc0, 0x56, 0x9c, 0xdb, 0x12, 0x14, 0x59, 0xea, 0xae, 0x55, 0x37, 0xc9, 0xf0, 0xa1, 0xa8, + 0x7d, 0xd8, 0xeb, 0xee, 0x65, 0x81, 0xec, 0x69, 0x73, 0x1a, 0xe4, 0x36, 0x19, 0x04, 0x18, 0x0c, + 0x30, 0x18, 0x60, 0x72, 0x9a, 0x3f, 0x60, 0xfe, 0x8b, 0x39, 0xce, 0x1f, 0x30, 0xe7, 0x41, 0x3d, + 0x48, 0x16, 0x29, 0xb6, 0x6c, 0x27, 0xc0, 0x5c, 0x04, 0xd6, 0x57, 0xbf, 0xfe, 0xea, 0x7b, 0xd5, + 0x57, 0x5f, 0x7d, 0x25, 0xe8, 0x4e, 0x70, 0x18, 0x5a, 0x43, 0x1c, 0xf6, 0xfd, 0xc0, 0x8b, 0xbc, + 0xa5, 0x76, 0x34, 0xf5, 0x93, 0x81, 0xae, 0x02, 0x3c, 0x71, 0x49, 0x44, 0xac, 0x31, 0x79, 0x89, + 0xf5, 0x0e, 0xb4, 0x1f, 0xe1, 0x68, 0x0f, 0x5b, 0x51, 0x1c, 0xe0, 0x50, 0xff, 0x69, 0x0e, 0x9a, + 0xc9, 0x40, 0xbb, 0x04, 0xf5, 0x13, 0xec, 0x3a, 0x5e, 0xd0, 0x53, 0x56, 0x94, 0xf5, 0x96, 0x21, + 0x46, 0xda, 0x2a, 0x74, 0x26, 0xd6, 0x7f, 0x7a, 0x81, 0x79, 0x82, 0x83, 0x90, 0x78, 0x6e, 0xaf, + 0xb2, 0xa2, 0xac, 0x77, 0x0c, 0x95, 0x11, 0x5f, 0x70, 0x1a, 0x03, 0x11, 0x57, 0x02, 0x55, 0x05, + 0x88, 0x12, 0x25, 0x90, 0x6f, 0x45, 0xf6, 0x28, 0x05, 0xd5, 0x38, 0x88, 0x11, 0x13, 0xd0, 0x1d, + 0x98, 0x3f, 0xf4, 0xbc, 0x68, 0xec, 0x59, 0x0e, 0x0e, 0xcc, 0x89, 0xe7, 0xe0, 0xde, 0xdc, 0x8a, + 0xb2, 0xde, 0x34, 0xba, 0x19, 0xf9, 0x99, 0xe7, 0x60, 0xed, 0x2a, 0xb4, 0x1c, 0x7c, 0x42, 0x6c, + 0x6c, 0x12, 0xa7, 0x57, 0x67, 0x22, 0x37, 0x39, 0xe1, 0x89, 0xa3, 0xdd, 0x82, 0xae, 0x4f, 0x5c, + 0x93, 0xda, 0x00, 0xdb, 0x11, 0x5d, 0xab, 0xc1, 0x98, 0x74, 0x7c, 0xe2, 0xee, 0xa7, 0x44, 0xed, + 0x3d, 0x58, 0xf4, 0xad, 0x30, 0xf4, 0x47, 0x81, 0x15, 0x62, 0x19, 0xdd, 0x64, 0xe8, 0x8b, 0xd9, + 0xa4, 0xf4, 0xa3, 0x25, 0x68, 0x8e, 0x2d, 0x77, 0x18, 0x5b, 0x43, 0xdc, 0x6b, 0xf1, 0x75, 0x93, + 0xb1, 0x76, 0x11, 0xe6, 0xc6, 0xd6, 0x21, 0x1e, 0xf7, 0x80, 0x4d, 0xf0, 0x81, 0x76, 0x03, 0xe6, + 0x6c, 0x8f, 0xb8, 0x61, 0xaf, 0xbd, 0x52, 0x5d, 0x6f, 0x6f, 0xb6, 0xfa, 0xdb, 0x1e, 0x71, 0x0f, + 0xa6, 0x3e, 0x36, 0x38, 0x5d, 0x5b, 0x81, 0x36, 0x49, 0xbd, 0xe4, 0xf4, 0x54, 0xb6, 0xba, 0x4c, + 0xa2, 0x8b, 0x06, 0xf8, 0x84, 0x30, 0xb3, 0x75, 0x56, 0x94, 0x75, 0xd5, 0x48, 0xc7, 0x05, 0x93, + 0x8d, 0xac, 0x70, 0xd4, 0xeb, 0x32, 0x88, 0x64, 0xb2, 0xc7, 0x56, 0x38, 0xa2, 0x4c, 0xc8, 0xc4, + 0xf7, 0x82, 0x08, 0x3b, 0xbd, 0x79, 0xb6, 0x46, 0x3a, 0xd6, 0x96, 0x01, 0xa8, 0xc5, 0x6c, 0xcb, + 0x1e, 0x61, 0xa7, 0x87, 0xd8, 0x6c, 0xcb, 0x27, 0xee, 0x36, 0x23, 0x68, 0xf7, 0x60, 0x41, 0xb2, + 0x94, 0x40, 0x2d, 0x30, 0x14, 0xca, 0x26, 0x04, 0x78, 0x03, 0xd0, 0x11, 0x09, 0x26, 0xdf, 0x58, + 0x01, 0x35, 0x2a, 0x0e, 0xb1, 0x1b, 0xf5, 0x34, 0x86, 0x9d, 0x4f, 0xe8, 0xfb, 0x9c, 0xac, 0xdd, + 0x04, 0xd5, 0xc5, 0xd8, 0x09, 0xcd, 0x43, 0xcb, 0x3e, 0x8e, 0xfd, 0xde, 0x05, 0xae, 0x3a, 0xa3, + 0x3d, 0x64, 0x24, 0x6a, 0xd3, 0xa3, 0xb1, 0x35, 0x0c, 0x7b, 0x17, 0x59, 0xb8, 0xf0, 0x81, 0xde, + 0x05, 0x75, 0x7b, 0x8c, 0xad, 0x60, 0x80, 0x43, 0x6a, 0x04, 0xfd, 0x7f, 0x14, 0xe8, 0x6c, 0xf9, + 0xfe, 0x78, 0x3a, 0xc0, 0x51, 0x44, 0xdc, 0x61, 0x98, 0xf3, 0x93, 0x32, 0xcb, 0x4f, 0x15, 0xd9, + 0x4f, 0xb7, 0xa0, 0x1b, 0xd3, 0x38, 0x48, 0xf5, 0x61, 0x61, 0xdc, 0x34, 0x3a, 0x71, 0x88, 0xf7, + 0x53, 0xa2, 0x76, 0x1d, 0x60, 0xe4, 0x4d, 0x70, 0x68, 0x07, 0x18, 0xf3, 0x20, 0x56, 0x0d, 0x89, + 0xa2, 0xeb, 0x00, 0x4c, 0x92, 0x3d, 0x2a, 0x68, 0x26, 0xbe, 0x22, 0x8b, 0xbf, 0x0a, 0xad, 0xed, + 0x91, 0xe5, 0x0e, 0xf1, 0x3e, 0x71, 0xe9, 0xd6, 0x0b, 0xf0, 0xc4, 0x3b, 0xe1, 0x72, 0x36, 0x0d, + 0x31, 0xd2, 0x7f, 0xa3, 0x40, 0x6d, 0x9f, 0xb8, 0x43, 0xad, 0x07, 0x0d, 0xb1, 0xc9, 0x85, 0x26, + 0xc9, 0x90, 0xfa, 0xe5, 0x30, 0x8e, 0x22, 0x2f, 0x17, 0xeb, 0x15, 0xee, 0x17, 0x3e, 0x21, 0x45, + 0xee, 0xd9, 0x5d, 0x51, 0x7d, 0xa3, 0x5d, 0x51, 0x9b, 0xbd, 0x2b, 0xf4, 0x55, 0x68, 0x0c, 0x62, + 0xdb, 0xc6, 0x61, 0x38, 0x5b, 0x5a, 0x7d, 0x17, 0x1a, 0x7b, 0x16, 0x19, 0xc7, 0x01, 0xd6, 0x56, + 0xa0, 0x66, 0xd3, 0xcd, 0x4d, 0x11, 0xdd, 0x4d, 0xb5, 0x2f, 0xe8, 0x6c, 0x57, 0xb0, 0x19, 0x99, + 0x4d, 0x25, 0xcf, 0xe6, 0x73, 0xe8, 0x3c, 0x64, 0xba, 0x19, 0xf8, 0xeb, 0x18, 0x87, 0x91, 0x76, + 0x3b, 0xc7, 0x4c, 0xeb, 0xe7, 0x66, 0x25, 0x96, 0x1a, 0xd4, 0x1c, 0x2b, 0xb2, 0x04, 0x3f, 0xf6, + 0xad, 0xb7, 0xa1, 0xc5, 0xe1, 0x5b, 0xf6, 0xb1, 0xfe, 0x31, 0xa0, 0x7d, 0xe2, 0x3e, 0xb3, 0xa2, + 0x80, 0x9c, 0x26, 0xcc, 0x37, 0xa0, 0x46, 0x33, 0xaa, 0x60, 0xbe, 0xd8, 0x2f, 0x02, 0x38, 0x7f, + 0x0a, 0xd1, 0x57, 0x40, 0x4d, 0x67, 0xb7, 0xec, 0x63, 0x0d, 0x41, 0xd5, 0x27, 0x6e, 0x4f, 0x59, + 0xa9, 0xac, 0xb7, 0x0c, 0xfa, 0xa9, 0x37, 0xa1, 0xbe, 0x6d, 0xb9, 0x36, 0x1e, 0xeb, 0x17, 0x60, + 0x21, 0x8b, 0x29, 0xc1, 0x4a, 0xbf, 0x0f, 0x9d, 0x8c, 0x48, 0x39, 0x5c, 0x07, 0x90, 0xc2, 0x91, + 0x33, 0x92, 0x28, 0xfa, 0x0a, 0xc0, 0x23, 0x1c, 0xed, 0xba, 0x51, 0xe0, 0xf9, 0x53, 0xaa, 0x5f, + 0x48, 0x5e, 0x72, 0x5c, 0xc7, 0x60, 0xdf, 0xd4, 0x31, 0xc9, 0x74, 0x0f, 0x1a, 0x98, 0x7f, 0x32, + 0x84, 0x6a, 0x24, 0x43, 0xfd, 0x57, 0x0a, 0xa8, 0x8f, 0x70, 0xb4, 0x1f, 0x1f, 0x8e, 0x89, 0xfd, + 0x39, 0x9e, 0xd2, 0xec, 0x6a, 0x39, 0x4e, 0x80, 0xc3, 0xd0, 0xa4, 0xf2, 0x57, 0xd7, 0x3b, 0x46, + 0x53, 0x10, 0x9e, 0x6b, 0xeb, 0x80, 0xb0, 0xed, 0x84, 0x96, 0x69, 0xc7, 0xc1, 0x09, 0x36, 0x5d, + 0x6b, 0x92, 0xb8, 0xa8, 0xcb, 0xe8, 0xdb, 0x94, 0xfc, 0xdc, 0x9a, 0x60, 0xba, 0xbd, 0xc3, 0x91, + 0xf7, 0x8d, 0xe9, 0x90, 0xd0, 0x1f, 0x5b, 0x53, 0x11, 0x6f, 0x6d, 0x4a, 0xdb, 0xe1, 0x24, 0x6d, + 0x0d, 0x5a, 0x34, 0x09, 0x72, 0x2e, 0x34, 0xc2, 0x5a, 0x0f, 0x1a, 0x0f, 0x49, 0x44, 0x69, 0x46, + 0x93, 0xfe, 0xa5, 0x8c, 0xf4, 0xcf, 0xa0, 0x95, 0x09, 0x77, 0x03, 0x6a, 0x2e, 0x77, 0x77, 0x65, + 0xbd, 0xbd, 0xd9, 0xee, 0x3f, 0xde, 0x79, 0xee, 0x39, 0x22, 0x74, 0x5c, 0xe1, 0xe7, 0x53, 0x3f, + 0x3e, 0x4c, 0xfc, 0x4c, 0xbf, 0xf5, 0xbf, 0x2a, 0xcc, 0x54, 0x5b, 0x5c, 0x89, 0xf3, 0x15, 0xcc, + 0xc9, 0x54, 0x99, 0x21, 0xd3, 0xeb, 0x28, 0xf7, 0x01, 0x34, 0x27, 0xf1, 0x38, 0x22, 0x21, 0x19, + 0x32, 0xdd, 0xda, 0x9b, 0x57, 0xfa, 0xcf, 0x04, 0xc1, 0xc0, 0x0e, 0xc6, 0x93, 0x81, 0x1d, 0x10, + 0x9f, 0xc7, 0x50, 0x0a, 0xd5, 0x3e, 0x85, 0x76, 0xc8, 0xe8, 0x26, 0x8b, 0xbc, 0x39, 0x16, 0x79, + 0xa8, 0xff, 0xc4, 0xf5, 0xe3, 0x28, 0xfb, 0xc1, 0x03, 0x75, 0xb0, 0xbf, 0xfb, 0x7c, 0x67, 0x6b, + 0x67, 0xc7, 0xd8, 0x1d, 0x0c, 0x0c, 0x08, 0xd3, 0x19, 0xfd, 0x00, 0xb4, 0xdd, 0x68, 0x84, 0x03, + 0x1c, 0x4f, 0x5e, 0x57, 0xe7, 0xa2, 0x36, 0x95, 0x33, 0xda, 0xd0, 0x50, 0x4a, 0x58, 0xf5, 0xa0, + 0x21, 0x7e, 0x29, 0x82, 0x32, 0x19, 0xea, 0xf7, 0x60, 0x3e, 0x59, 0x7a, 0x06, 0x58, 0xcd, 0xc0, + 0x2a, 0xc0, 0x57, 0xc4, 0xc7, 0x3b, 0xec, 0xdc, 0xd6, 0xff, 0xaf, 0x02, 0xf0, 0xd4, 0xb3, 0x1c, + 0x3e, 0xa4, 0x09, 0x7c, 0xe2, 0xe2, 0x89, 0xe7, 0x12, 0x3b, 0x49, 0xe0, 0xc9, 0x38, 0x0d, 0x81, + 0x0a, 0x33, 0x6a, 0x49, 0x08, 0x88, 0xad, 0x57, 0x65, 0xbf, 0xa3, 0x9f, 0x3f, 0x2b, 0xad, 0x69, + 0xab, 0xd2, 0x21, 0x32, 0xc7, 0x03, 0x01, 0xbb, 0xc3, 0x31, 0x09, 0x47, 0x65, 0xa7, 0x49, 0x5d, + 0x3e, 0x4d, 0x56, 0xa1, 0x13, 0x1e, 0x13, 0xdf, 0xb4, 0x47, 0xd8, 0x3e, 0x0e, 0xe3, 0x89, 0x28, + 0x41, 0x54, 0x4a, 0xdc, 0x16, 0x34, 0xed, 0x06, 0xb4, 0xe3, 0xcd, 0x23, 0xd3, 0xf6, 0x62, 0x37, + 0xc2, 0x01, 0xab, 0x3b, 0x3a, 0x06, 0xc4, 0x9b, 0x47, 0xdb, 0x9c, 0xa2, 0xff, 0xb6, 0x02, 0x6d, + 0x03, 0x87, 0x38, 0x12, 0x46, 0xb9, 0x05, 0x5d, 0xe1, 0x21, 0x33, 0xb0, 0x5c, 0xc7, 0x9b, 0x88, + 0x33, 0xa3, 0x23, 0xa8, 0x06, 0x23, 0x6a, 0x37, 0xa0, 0x19, 0x46, 0x01, 0x76, 0x87, 0xd1, 0x88, + 0x17, 0x6c, 0x0f, 0xaa, 0x9b, 0x1f, 0x7c, 0x68, 0xa4, 0xc4, 0xd9, 0xd6, 0xa8, 0x9e, 0x63, 0x8d, + 0xb3, 0x07, 0x48, 0xad, 0xec, 0x00, 0xf9, 0x05, 0x46, 0x2b, 0xd8, 0xa3, 0x51, 0xb4, 0x07, 0x05, + 0x30, 0xab, 0x8a, 0x7a, 0x81, 0x17, 0x6a, 0x40, 0x49, 0xbc, 0x5c, 0xa0, 0x85, 0x01, 0xff, 0x12, + 0x41, 0x85, 0xa0, 0x2b, 0xf2, 0x5f, 0x92, 0x64, 0x6f, 0x03, 0x08, 0x0a, 0xcd, 0xb0, 0xb9, 0xa4, + 0xa8, 0xc8, 0x49, 0xf1, 0x4f, 0x15, 0xe8, 0x1a, 0xd8, 0xf6, 0x4e, 0x70, 0x30, 0x15, 0xd6, 0x5f, + 0x06, 0xf8, 0xc6, 0x0b, 0x1c, 0x2e, 0x9f, 0x38, 0xd1, 0x5b, 0x94, 0xc2, 0xc4, 0x9b, 0x6d, 0xd4, + 0xca, 0x1b, 0x19, 0xb5, 0xfa, 0x2a, 0xa3, 0xd6, 0x5e, 0x69, 0xd4, 0x39, 0xd9, 0xa8, 0x1b, 0x80, + 0xb0, 0x7b, 0xe4, 0x05, 0x36, 0x36, 0xa9, 0xac, 0x63, 0x12, 0x46, 0xcc, 0xea, 0x4d, 0x63, 0x5e, + 0xd0, 0xbf, 0x12, 0x64, 0x9a, 0x39, 0x59, 0xca, 0xe1, 0x81, 0xc8, 0xbe, 0x8b, 0x3e, 0x69, 0x9d, + 0xf1, 0xc9, 0x65, 0x68, 0x38, 0xc1, 0xd4, 0x0c, 0x62, 0x97, 0xd5, 0xbd, 0x4d, 0xa3, 0xee, 0x04, + 0x53, 0x23, 0x76, 0xf5, 0xf7, 0xa0, 0x4d, 0x39, 0x27, 0x27, 0xe9, 0x5a, 0xee, 0x24, 0x45, 0x7d, + 0x69, 0x4e, 0x3a, 0x44, 0x97, 0xa1, 0x41, 0x27, 0xa8, 0x6f, 0x34, 0xa8, 0x51, 0x81, 0x45, 0x8a, + 0x61, 0xdf, 0xfa, 0x8f, 0x0a, 0xb4, 0x07, 0x64, 0xe8, 0x3e, 0x13, 0x15, 0xd0, 0xb9, 0x49, 0x2d, + 0x57, 0x43, 0xb0, 0xcc, 0x93, 0x14, 0x4e, 0xb9, 0x14, 0x5f, 0x9d, 0x95, 0xe2, 0x0b, 0x89, 0xb8, + 0xf6, 0xc6, 0x89, 0xf8, 0xbf, 0x15, 0xe8, 0xbc, 0xc0, 0x01, 0x39, 0x9a, 0x26, 0xf2, 0xe6, 0x92, + 0xa1, 0x22, 0x65, 0x4e, 0xed, 0x1a, 0xb4, 0x42, 0x32, 0x74, 0xd9, 0x7d, 0x8c, 0x45, 0x8c, 0x6a, + 0x64, 0x04, 0x59, 0x95, 0x2a, 0x8f, 0xd3, 0x52, 0x55, 0x66, 0x9e, 0xa0, 0xff, 0x0e, 0x48, 0x88, + 0x30, 0x90, 0x79, 0xfe, 0x1c, 0x59, 0xf4, 0x1f, 0x14, 0xba, 0xa9, 0xec, 0x60, 0xea, 0x47, 0x89, + 0x5a, 0x97, 0xa0, 0xee, 0xc7, 0x87, 0xc7, 0x38, 0xd9, 0x45, 0x62, 0x54, 0xac, 0xe2, 0x24, 0xb1, + 0x6f, 0x82, 0x9a, 0x64, 0x32, 0xcf, 0x1d, 0xa7, 0xc7, 0xa7, 0xa0, 0x7d, 0xe1, 0x8e, 0x0b, 0x55, + 0x48, 0xed, 0xbc, 0x43, 0x7a, 0x6e, 0x96, 0xda, 0x2f, 0x00, 0x09, 0x49, 0xb1, 0x93, 0xc8, 0x7a, + 0x11, 0xe6, 0x5c, 0xcf, 0xb5, 0xb1, 0x10, 0x95, 0x0f, 0xce, 0x91, 0x54, 0x83, 0xda, 0x68, 0x62, + 0xd9, 0xc2, 0xee, 0xec, 0x5b, 0xff, 0x1a, 0xba, 0x3b, 0x38, 0x67, 0x81, 0x73, 0x03, 0x31, 0x5d, + 0xb2, 0x32, 0x63, 0xc9, 0x6a, 0xf9, 0x92, 0x35, 0x69, 0xc9, 0x3d, 0x40, 0x62, 0xc9, 0x4c, 0x95, + 0x42, 0xad, 0x2d, 0x71, 0x90, 0x7c, 0x5b, 0xc9, 0xf9, 0x56, 0xff, 0xb3, 0x02, 0xdd, 0x6d, 0xe2, + 0x8f, 0x70, 0xf0, 0x39, 0x9e, 0xbe, 0xb0, 0xc6, 0xf1, 0x2b, 0x64, 0x47, 0x50, 0xa5, 0x7e, 0xe5, + 0x5c, 0xe8, 0x27, 0xd5, 0xe6, 0x84, 0xfe, 0x4e, 0x48, 0xcd, 0x07, 0x3c, 0x93, 0x32, 0xf9, 0xc4, + 0xb1, 0x90, 0x0c, 0xb5, 0x35, 0xe8, 0x5a, 0xe1, 0xb1, 0xe9, 0xb9, 0x66, 0x02, 0xe0, 0x77, 0x7a, + 0xd5, 0x0a, 0x8f, 0xbf, 0x70, 0x77, 0xcf, 0xa0, 0x1c, 0xae, 0xa6, 0x48, 0x52, 0x1c, 0x25, 0x54, + 0xd7, 0xba, 0x50, 0x21, 0x27, 0xec, 0x60, 0x50, 0x8d, 0x0a, 0x39, 0xd1, 0xd7, 0x01, 0x71, 0x65, + 0xb0, 0x93, 0xaa, 0x93, 0xca, 0xa7, 0x48, 0xf2, 0xe9, 0xff, 0x05, 0xdd, 0xdd, 0x30, 0x22, 0x13, + 0x2b, 0xc2, 0x07, 0xa7, 0x03, 0xf2, 0x12, 0xd3, 0x23, 0xda, 0x8b, 0x23, 0x3f, 0x8e, 0xc2, 0x34, + 0xa3, 0xd3, 0xc2, 0x59, 0x15, 0x44, 0x9e, 0xd4, 0x6f, 0x82, 0x4a, 0x5c, 0x09, 0x53, 0x61, 0x98, + 0x36, 0xa7, 0x71, 0xc8, 0x6b, 0x25, 0x13, 0xfd, 0x26, 0xd4, 0xc5, 0xba, 0x97, 0xa1, 0x11, 0x9d, + 0x9a, 0xa2, 0x54, 0xa7, 0xd9, 0xb4, 0x1e, 0xb1, 0x09, 0xfd, 0xf7, 0x0a, 0xd4, 0xe9, 0xf6, 0x3c, + 0x38, 0xfd, 0xc7, 0xca, 0xa6, 0x5d, 0x85, 0x46, 0xae, 0x2b, 0xf3, 0x40, 0x79, 0xd7, 0x48, 0x28, + 0xda, 0x75, 0x68, 0x8d, 0x3d, 0xfb, 0xd8, 0x8c, 0x88, 0xd8, 0x69, 0x9d, 0x07, 0xca, 0x3b, 0x46, + 0x93, 0xd2, 0x0e, 0xc8, 0x04, 0xeb, 0x7f, 0x53, 0x40, 0x1d, 0x90, 0x89, 0x3f, 0xc6, 0x42, 0xf6, + 0x35, 0xa8, 0x73, 0x11, 0x58, 0x2c, 0xb5, 0x37, 0xd5, 0xfe, 0xc1, 0x29, 0xcb, 0x99, 0x2c, 0xcd, + 0x8b, 0x39, 0xed, 0x0e, 0x34, 0x84, 0x32, 0xbd, 0x0a, 0x83, 0x75, 0xfa, 0x07, 0xa7, 0x5f, 0x30, + 0x0a, 0xc3, 0x25, 0xb3, 0xda, 0xfb, 0xa0, 0x46, 0x81, 0xe5, 0x86, 0x16, 0x3b, 0x09, 0xc3, 0x5e, + 0x95, 0xa1, 0x51, 0xff, 0x20, 0x23, 0xb2, 0x1f, 0xe4, 0x50, 0xaf, 0x97, 0x16, 0x65, 0xc5, 0xe7, + 0xce, 0x57, 0xbc, 0x7e, 0x56, 0xf1, 0x5f, 0x2b, 0xd0, 0x3a, 0x48, 0x2f, 0x8a, 0xf7, 0x41, 0x0d, + 0xf8, 0xa7, 0x29, 0x1d, 0x73, 0x6a, 0x5f, 0x3e, 0xe2, 0xda, 0x41, 0x36, 0xd0, 0xee, 0x43, 0xc3, + 0xc1, 0x91, 0x45, 0xc6, 0xa1, 0xa8, 0x63, 0x17, 0xfb, 0x29, 0xb7, 0x1d, 0x3e, 0xc1, 0x0d, 0x21, + 0x50, 0xda, 0x47, 0x00, 0x21, 0x0e, 0x92, 0x36, 0x51, 0x95, 0xfd, 0xa6, 0x97, 0xfd, 0x66, 0x90, + 0xce, 0xb1, 0x9f, 0x49, 0x58, 0x7d, 0x03, 0xe6, 0x0e, 0xd8, 0x95, 0x74, 0x05, 0x2a, 0xd1, 0x29, + 0x13, 0xad, 0xcc, 0x82, 0x95, 0xe8, 0x54, 0xff, 0xdf, 0x0a, 0x74, 0x93, 0x0a, 0x5e, 0xf8, 0xf3, + 0x67, 0xa4, 0xb6, 0xab, 0xd0, 0x1a, 0x5a, 0xa1, 0xe9, 0x07, 0xc4, 0x4e, 0xd2, 0x44, 0x73, 0x68, + 0x85, 0xfb, 0x74, 0x9c, 0x4c, 0x8e, 0xc9, 0x84, 0x44, 0x22, 0xc5, 0xd1, 0xc9, 0xa7, 0x74, 0x4c, + 0x37, 0x78, 0xe4, 0x31, 0x67, 0xa8, 0x46, 0x25, 0xf2, 0xb2, 0xcd, 0x5c, 0x97, 0x93, 0xcd, 0x5b, + 0xa0, 0xd1, 0xeb, 0xbb, 0x29, 0x9a, 0x64, 0xa6, 0x3d, 0x8a, 0xdd, 0x63, 0x91, 0x16, 0x10, 0x9d, + 0x11, 0x6d, 0xcf, 0x6d, 0x4a, 0xa7, 0x25, 0x0c, 0x43, 0x8f, 0x79, 0x45, 0x2c, 0xca, 0x6c, 0x4a, + 0x7a, 0xca, 0xcb, 0xe1, 0x2b, 0xd0, 0xb4, 0x47, 0x16, 0x71, 0x4d, 0xe2, 0x88, 0x02, 0xa7, 0xc1, + 0xc6, 0x4f, 0x1c, 0xfd, 0xff, 0x15, 0x58, 0x48, 0xec, 0x91, 0x39, 0xbb, 0xc0, 0x51, 0x39, 0xc3, + 0x91, 0x16, 0xaa, 0xc9, 0x81, 0x69, 0x9e, 0x88, 0xae, 0x29, 0xa4, 0xa4, 0x17, 0x79, 0x40, 0x20, + 0x6c, 0x94, 0x01, 0x8c, 0x3c, 0x20, 0x4c, 0x1a, 0x4d, 0x29, 0x69, 0xa0, 0xf7, 0xa1, 0x93, 0x09, + 0x46, 0x9d, 0xbb, 0x0c, 0x4c, 0x02, 0x61, 0x0c, 0x9e, 0xfc, 0x5a, 0x94, 0xc2, 0xac, 0xa0, 0x3f, + 0x85, 0x0b, 0xb2, 0x63, 0x7f, 0x59, 0x05, 0xa5, 0x13, 0x58, 0x4c, 0xb8, 0x9d, 0x5b, 0xe1, 0xa8, + 0xbf, 0xb8, 0xc2, 0xd1, 0x0d, 0xe8, 0x25, 0x4b, 0xbd, 0xaa, 0x86, 0x79, 0xdd, 0xd5, 0xf4, 0x9f, + 0x58, 0xd2, 0x1a, 0xba, 0x4f, 0x1c, 0xec, 0x46, 0x24, 0x9a, 0x6a, 0x1b, 0xd0, 0x24, 0xe2, 0x5b, + 0xec, 0x8f, 0x4e, 0x3f, 0x99, 0xe4, 0xf7, 0x73, 0x92, 0x41, 0x91, 0x3d, 0xb2, 0xc6, 0xd4, 0xf7, + 0xd8, 0x1c, 0x11, 0xc7, 0xc1, 0xae, 0x58, 0x60, 0x3e, 0xa5, 0x3f, 0x66, 0xe4, 0x3c, 0xf4, 0x84, + 0x84, 0xb1, 0x35, 0x16, 0x97, 0xd2, 0x0c, 0xfa, 0x82, 0x91, 0x4b, 0xdb, 0x2a, 0xb5, 0xb2, 0xb6, + 0x8a, 0x3e, 0x84, 0x2e, 0x15, 0x1d, 0x3b, 0xa9, 0xf0, 0xb3, 0x2b, 0xb9, 0x65, 0x00, 0x9f, 0x75, + 0x4e, 0xcc, 0xe4, 0x10, 0x57, 0x8d, 0x96, 0x9f, 0xf6, 0x52, 0x72, 0x46, 0xaa, 0x16, 0x8d, 0xf4, + 0xad, 0x02, 0x0b, 0x8f, 0x70, 0xb4, 0xbb, 0xbd, 0xf3, 0x58, 0x34, 0x5a, 0xe9, 0x6f, 0xde, 0xc0, + 0x52, 0xb7, 0x61, 0xde, 0xc7, 0x38, 0x30, 0xcf, 0x88, 0xd0, 0xa1, 0xe4, 0xac, 0xa5, 0x53, 0xa6, + 0x7b, 0xb5, 0x54, 0xf7, 0x77, 0xa1, 0x5b, 0x10, 0x87, 0xee, 0x13, 0x3e, 0x32, 0xb3, 0xfa, 0x13, + 0xc2, 0x14, 0xa0, 0xbf, 0x03, 0x9d, 0x01, 0x8e, 0xbe, 0xdc, 0xdc, 0x93, 0x2e, 0x91, 0xf2, 0x8d, + 0x46, 0x39, 0x73, 0xeb, 0xbe, 0x03, 0x9d, 0x3d, 0xd1, 0xa9, 0xde, 0x65, 0x3d, 0xdf, 0x4b, 0x50, + 0xcf, 0xed, 0x74, 0x31, 0xd2, 0xb7, 0x60, 0x3e, 0x01, 0x26, 0x99, 0xe1, 0x12, 0xd4, 0xbd, 0xa3, + 0xa3, 0x10, 0x27, 0xf7, 0x43, 0x31, 0x92, 0x58, 0x54, 0x72, 0x2c, 0x3e, 0x81, 0x6e, 0xc2, 0xe2, + 0x4b, 0x7f, 0xec, 0x59, 0x0e, 0x75, 0xa6, 0x6f, 0x4d, 0xe9, 0x67, 0xd2, 0x2f, 0x11, 0x43, 0x56, + 0x16, 0x5a, 0xe1, 0x48, 0xd8, 0x90, 0x7d, 0xeb, 0x6b, 0xd0, 0x1c, 0xe0, 0xf1, 0xd1, 0x01, 0x5d, + 0x3b, 0xf7, 0x4b, 0x45, 0xfa, 0xa5, 0x7e, 0x17, 0x16, 0x76, 0xf0, 0x61, 0x3c, 0x7c, 0x4a, 0xdc, + 0xe3, 0x1d, 0x6c, 0xf3, 0x97, 0x83, 0x45, 0xa8, 0x4f, 0x71, 0x68, 0xba, 0x1e, 0x5b, 0xa7, 0x69, + 0xcc, 0x4d, 0x71, 0xf8, 0xdc, 0xd3, 0x2f, 0x48, 0xd8, 0x47, 0x38, 0x1a, 0x44, 0x56, 0x84, 0xf5, + 0xbf, 0x54, 0x68, 0xc5, 0x2b, 0xa8, 0x8c, 0xc4, 0x34, 0xb2, 0xa6, 0x5e, 0x1c, 0x25, 0x35, 0x3f, + 0x1f, 0x25, 0xbd, 0x97, 0x4a, 0xd6, 0x7b, 0xb9, 0x04, 0xf5, 0x09, 0xeb, 0x8a, 0x0a, 0xa7, 0x8a, + 0x51, 0xae, 0xc5, 0x53, 0x9b, 0xd1, 0xe2, 0x99, 0x9b, 0xd5, 0xe2, 0x99, 0x79, 0xdb, 0xae, 0x9f, + 0x73, 0xdb, 0x5e, 0x06, 0x08, 0x70, 0x88, 0x23, 0x76, 0x13, 0x66, 0xe7, 0x45, 0xcb, 0x68, 0x31, + 0x0a, 0xbd, 0x74, 0xd2, 0xaa, 0x8b, 0x4f, 0x27, 0x3d, 0x81, 0x26, 0xd3, 0x4c, 0x65, 0xc4, 0xa4, + 0x8f, 0xfa, 0x16, 0x68, 0x81, 0xe8, 0x0b, 0x98, 0x47, 0xd6, 0x31, 0xbf, 0x55, 0x8b, 0xb7, 0x20, + 0x94, 0xcc, 0xec, 0x59, 0xc7, 0xec, 0x5a, 0xad, 0xdd, 0x85, 0x85, 0x14, 0xcd, 0x9a, 0x07, 0xbe, + 0x17, 0xb2, 0x7b, 0x72, 0xc7, 0x98, 0x4f, 0x26, 0x28, 0x70, 0xdf, 0x0b, 0xf5, 0x79, 0xe8, 0x48, + 0x36, 0xf6, 0x7c, 0x7d, 0x1f, 0xd4, 0x94, 0xf0, 0xd4, 0x1b, 0xb2, 0x0b, 0x3e, 0x3e, 0xc1, 0xe3, + 0xe4, 0x35, 0x81, 0x0d, 0xa8, 0x79, 0x0f, 0x63, 0xfb, 0x18, 0x47, 0xc2, 0xe6, 0x62, 0xc4, 0x6e, + 0xf3, 0xf8, 0x34, 0x12, 0x46, 0x67, 0xdf, 0xfa, 0x23, 0xb8, 0x90, 0x72, 0x7c, 0x86, 0x27, 0x5e, + 0x30, 0x35, 0x30, 0x8f, 0x39, 0x39, 0x81, 0x74, 0xb2, 0x04, 0x32, 0x2b, 0x6e, 0x37, 0x60, 0xbe, + 0xc0, 0x88, 0xb9, 0x99, 0x7d, 0x25, 0x01, 0xc1, 0x47, 0xfa, 0x7f, 0xc0, 0xc5, 0x02, 0xf4, 0xab, + 0x80, 0x44, 0xf8, 0xfc, 0x45, 0x05, 0xa7, 0x8a, 0xcc, 0x49, 0xbc, 0xa6, 0x84, 0x23, 0x71, 0x5b, + 0xe4, 0x03, 0xfd, 0x6d, 0x49, 0xa7, 0x3d, 0x4a, 0x49, 0x37, 0x6d, 0x88, 0xed, 0xc8, 0x4b, 0x76, + 0xb8, 0x18, 0xdd, 0xfd, 0x71, 0x11, 0xda, 0xe2, 0x1c, 0x61, 0x75, 0xd8, 0x0a, 0x5c, 0x92, 0x86, + 0x66, 0xf6, 0x60, 0x8a, 0xfe, 0x69, 0xa9, 0xf6, 0xed, 0x1f, 0x7a, 0x8a, 0xb6, 0x94, 0x5e, 0x9e, + 0x19, 0x62, 0x9f, 0xb8, 0x43, 0xa4, 0x88, 0xb9, 0x65, 0xb8, 0x20, 0xcf, 0x89, 0x57, 0x10, 0x54, + 0x59, 0xaa, 0x7d, 0x57, 0x32, 0x2d, 0xde, 0x39, 0x50, 0x55, 0x4c, 0xdf, 0x80, 0x45, 0x79, 0x3a, + 0x7d, 0x14, 0x42, 0x35, 0xc1, 0xbe, 0x20, 0x5c, 0xd6, 0x2e, 0x45, 0x73, 0x02, 0x71, 0x07, 0xae, + 0xe4, 0x56, 0x90, 0x13, 0x17, 0xaa, 0x2f, 0x35, 0x29, 0xe8, 0x8f, 0x14, 0xb8, 0x0e, 0x4b, 0x65, + 0x40, 0x9e, 0x75, 0x50, 0x43, 0x42, 0x6e, 0xc0, 0xd5, 0x32, 0xa4, 0x48, 0x71, 0xa8, 0xb9, 0xd4, + 0xfc, 0x2e, 0x81, 0x16, 0xe4, 0xcb, 0x5e, 0x23, 0x50, 0xab, 0xdc, 0x40, 0xc9, 0x34, 0x08, 0x0b, + 0xe8, 0xd0, 0x2b, 0x30, 0x48, 0x8f, 0x05, 0xd4, 0x16, 0x2c, 0x0a, 0x56, 0xca, 0x00, 0xaa, 0x60, + 0x52, 0x90, 0x22, 0xeb, 0x22, 0xa3, 0x8e, 0x60, 0x71, 0x13, 0x2e, 0xcb, 0x08, 0xa9, 0xa7, 0x8a, + 0xba, 0x02, 0x72, 0x0d, 0xb4, 0x9c, 0x27, 0x59, 0xf1, 0x8b, 0xe6, 0xc5, 0xec, 0x5a, 0x5e, 0x4e, + 0xf9, 0xc2, 0x83, 0xd0, 0x52, 0x9d, 0x62, 0x9a, 0x8a, 0x76, 0x1d, 0x2e, 0xe6, 0x2c, 0x27, 0x9e, + 0xd7, 0xd1, 0x82, 0x10, 0xf4, 0x36, 0x5c, 0x2b, 0x44, 0x52, 0xee, 0x31, 0x09, 0x69, 0x29, 0xae, + 0x57, 0x8a, 0xdb, 0xb2, 0x8f, 0xd1, 0x05, 0xee, 0xa9, 0xdf, 0x95, 0xc8, 0xcc, 0x1f, 0x97, 0xd0, + 0xc5, 0x72, 0xbb, 0xa5, 0xe5, 0x2b, 0x5a, 0x14, 0xcb, 0x5c, 0x85, 0x85, 0x3c, 0x80, 0xf2, 0xbf, + 0x94, 0x6a, 0x9c, 0x8b, 0x97, 0x7c, 0xcf, 0x00, 0x5d, 0x16, 0xa8, 0x82, 0xff, 0xe4, 0x57, 0x59, + 0xd4, 0x13, 0x98, 0xd5, 0x7c, 0x88, 0xe6, 0x1e, 0x6a, 0xd1, 0x95, 0x72, 0x50, 0xee, 0x11, 0x0f, + 0x2d, 0x09, 0x81, 0x57, 0xf3, 0x1a, 0xa5, 0x4f, 0x77, 0xe8, 0xaa, 0x64, 0x94, 0x42, 0x34, 0x64, + 0xaf, 0xb1, 0xe8, 0x5a, 0xf9, 0xae, 0xca, 0x1e, 0x49, 0xd0, 0x72, 0x79, 0xd4, 0x26, 0xd3, 0xd7, + 0xd3, 0xa8, 0xcd, 0xf9, 0x39, 0x39, 0x81, 0xd1, 0x8a, 0xb4, 0x8b, 0x0a, 0x96, 0x91, 0xdb, 0xd2, + 0x48, 0x2f, 0xb7, 0x71, 0xbe, 0x55, 0x8d, 0x56, 0xcb, 0xc3, 0x3b, 0x6b, 0x5f, 0xa3, 0xb5, 0xf2, + 0xf0, 0x96, 0xea, 0x7b, 0x74, 0xbb, 0xdc, 0xbe, 0xb9, 0xa2, 0x1d, 0xdd, 0x11, 0xa0, 0x42, 0x7c, + 0x16, 0xcb, 0x6d, 0xb4, 0x2e, 0x24, 0xba, 0x03, 0xcb, 0xb9, 0xf8, 0x2c, 0x3e, 0x65, 0xa2, 0x8d, + 0x14, 0x78, 0xa5, 0x1c, 0x48, 0xa5, 0xbf, 0x2b, 0x39, 0xed, 0x76, 0xc1, 0x12, 0xb9, 0x56, 0x0d, + 0xba, 0x27, 0xed, 0x30, 0x2d, 0x1f, 0xb2, 0x6c, 0xfe, 0xad, 0xa5, 0xfa, 0x77, 0x7c, 0xbe, 0x60, + 0xd1, 0x7c, 0x07, 0x1f, 0xbd, 0x5d, 0x6e, 0x2f, 0xa9, 0x15, 0x8d, 0xfa, 0xe5, 0x99, 0x5b, 0x34, + 0xa5, 0xd1, 0xfd, 0x72, 0x4b, 0x15, 0x9b, 0x50, 0xe8, 0x9d, 0x74, 0x27, 0x17, 0x3c, 0x2c, 0x77, + 0x0d, 0xd1, 0xbb, 0xa9, 0x5e, 0xeb, 0x79, 0x7e, 0xc5, 0xae, 0x25, 0xda, 0x4c, 0x35, 0x2c, 0x70, + 0xcc, 0xf7, 0x21, 0xd1, 0x7b, 0xb3, 0x38, 0x16, 0x9b, 0x87, 0xe8, 0xfd, 0x94, 0xa3, 0x5e, 0xcc, + 0x6d, 0xd9, 0xbd, 0x08, 0x7d, 0x50, 0x1e, 0xa9, 0xf9, 0x0b, 0x08, 0xfa, 0x50, 0x68, 0x5b, 0xb0, + 0xab, 0xf4, 0xef, 0x46, 0xe8, 0x9f, 0x05, 0xa3, 0x75, 0xb8, 0x9e, 0x53, 0xf4, 0xcc, 0x43, 0x25, + 0xfa, 0x48, 0x20, 0x6f, 0xe5, 0x8f, 0xa1, 0xc2, 0xbb, 0x22, 0xfa, 0x17, 0xb1, 0x66, 0x71, 0x0f, + 0xe5, 0x9a, 0x17, 0xe8, 0x41, 0x7a, 0x4c, 0x2e, 0x97, 0xa1, 0xb2, 0x9c, 0xf8, 0xaf, 0x69, 0x8a, + 0xb9, 0x52, 0x0e, 0xa4, 0xde, 0xff, 0xb7, 0x72, 0x6e, 0x67, 0x2e, 0x49, 0xe8, 0xe3, 0x19, 0x1b, + 0x3c, 0x8f, 0xfa, 0xa4, 0x7c, 0xcd, 0xdc, 0x75, 0x05, 0x7d, 0x2a, 0x58, 0x6d, 0xc0, 0x8d, 0x59, + 0x7a, 0x26, 0x2e, 0xfd, 0x4c, 0x40, 0xef, 0xc1, 0xcd, 0x32, 0x68, 0x7e, 0xcf, 0x6f, 0x09, 0x70, + 0x1f, 0xd6, 0xca, 0xc0, 0x67, 0xf6, 0xfe, 0x43, 0x21, 0xec, 0xbd, 0xbc, 0xee, 0x67, 0xee, 0x15, + 0xc8, 0x59, 0x6a, 0x7e, 0x9f, 0x6c, 0xeb, 0x3b, 0x33, 0xc0, 0xc9, 0xc5, 0x02, 0xe1, 0xa5, 0xda, + 0xf7, 0x25, 0x86, 0xca, 0xdf, 0x35, 0xd0, 0xd1, 0x52, 0xed, 0x87, 0x12, 0x43, 0xe5, 0xaa, 0x65, + 0x34, 0x14, 0xac, 0x0a, 0xe1, 0x2c, 0x57, 0xd0, 0x68, 0x24, 0x18, 0x15, 0x8c, 0x59, 0x52, 0x13, + 0x23, 0x57, 0xb0, 0x2b, 0x84, 0x61, 0x01, 0x8a, 0x3c, 0xc1, 0xf1, 0x2e, 0xac, 0x9c, 0x03, 0x63, + 0x15, 0x2f, 0xf2, 0x05, 0xcb, 0x59, 0xab, 0x67, 0xd5, 0x2b, 0xfa, 0x9a, 0x43, 0x1f, 0xbe, 0x0f, + 0xab, 0xb6, 0x37, 0xe9, 0x87, 0x56, 0xe4, 0x85, 0x23, 0x32, 0xb6, 0x0e, 0xc3, 0x7e, 0x14, 0xe0, + 0x97, 0x5e, 0xd0, 0x1f, 0x93, 0x43, 0xfe, 0x6f, 0x7e, 0x87, 0xf1, 0xd1, 0xc3, 0xce, 0x01, 0x23, + 0x0a, 0xae, 0x7f, 0x0f, 0x00, 0x00, 0xff, 0xff, 0x2a, 0xe4, 0xc0, 0x85, 0x16, 0x28, 0x00, 0x00, +} diff --git a/accounts/usbwallet/trezor/messages.proto b/accounts/usbwallet/trezor/messages.proto new file mode 100644 index 000000000..8cb9c8cc2 --- /dev/null +++ b/accounts/usbwallet/trezor/messages.proto @@ -0,0 +1,905 @@ +// This file originates from the SatoshiLabs Trezor `common` repository at: +// https://github.com/trezor/trezor-common/blob/master/protob/messages.proto +// dated 28.07.2017, commit dd8ec3231fb5f7992360aff9bdfe30bb58130f4b. + +syntax = "proto2"; + +/** + * Messages for TREZOR communication + */ + +// Sugar for easier handling in Java +option java_package = "com.satoshilabs.trezor.lib.protobuf"; +option java_outer_classname = "TrezorMessage"; + +import "types.proto"; + +/** + * Mapping between Trezor wire identifier (uint) and a protobuf message + */ +enum MessageType { + MessageType_Initialize = 0 [(wire_in) = true]; + MessageType_Ping = 1 [(wire_in) = true]; + MessageType_Success = 2 [(wire_out) = true]; + MessageType_Failure = 3 [(wire_out) = true]; + MessageType_ChangePin = 4 [(wire_in) = true]; + MessageType_WipeDevice = 5 [(wire_in) = true]; + MessageType_FirmwareErase = 6 [(wire_in) = true, (wire_bootloader) = true]; + MessageType_FirmwareUpload = 7 [(wire_in) = true, (wire_bootloader) = true]; + MessageType_FirmwareRequest = 8 [(wire_out) = true, (wire_bootloader) = true]; + MessageType_GetEntropy = 9 [(wire_in) = true]; + MessageType_Entropy = 10 [(wire_out) = true]; + MessageType_GetPublicKey = 11 [(wire_in) = true]; + MessageType_PublicKey = 12 [(wire_out) = true]; + MessageType_LoadDevice = 13 [(wire_in) = true]; + MessageType_ResetDevice = 14 [(wire_in) = true]; + MessageType_SignTx = 15 [(wire_in) = true]; + MessageType_SimpleSignTx = 16 [(wire_in) = true, deprecated = true]; + MessageType_Features = 17 [(wire_out) = true]; + MessageType_PinMatrixRequest = 18 [(wire_out) = true]; + MessageType_PinMatrixAck = 19 [(wire_in) = true, (wire_tiny) = true]; + MessageType_Cancel = 20 [(wire_in) = true]; + MessageType_TxRequest = 21 [(wire_out) = true]; + MessageType_TxAck = 22 [(wire_in) = true]; + MessageType_CipherKeyValue = 23 [(wire_in) = true]; + MessageType_ClearSession = 24 [(wire_in) = true]; + MessageType_ApplySettings = 25 [(wire_in) = true]; + MessageType_ButtonRequest = 26 [(wire_out) = true]; + MessageType_ButtonAck = 27 [(wire_in) = true, (wire_tiny) = true]; + MessageType_ApplyFlags = 28 [(wire_in) = true]; + MessageType_GetAddress = 29 [(wire_in) = true]; + MessageType_Address = 30 [(wire_out) = true]; + MessageType_SelfTest = 32 [(wire_in) = true, (wire_bootloader) = true]; + MessageType_BackupDevice = 34 [(wire_in) = true]; + MessageType_EntropyRequest = 35 [(wire_out) = true]; + MessageType_EntropyAck = 36 [(wire_in) = true]; + MessageType_SignMessage = 38 [(wire_in) = true]; + MessageType_VerifyMessage = 39 [(wire_in) = true]; + MessageType_MessageSignature = 40 [(wire_out) = true]; + MessageType_PassphraseRequest = 41 [(wire_out) = true]; + MessageType_PassphraseAck = 42 [(wire_in) = true, (wire_tiny) = true]; + MessageType_EstimateTxSize = 43 [(wire_in) = true, deprecated = true]; + MessageType_TxSize = 44 [(wire_out) = true, deprecated = true]; + MessageType_RecoveryDevice = 45 [(wire_in) = true]; + MessageType_WordRequest = 46 [(wire_out) = true]; + MessageType_WordAck = 47 [(wire_in) = true]; + MessageType_CipheredKeyValue = 48 [(wire_out) = true]; + MessageType_EncryptMessage = 49 [(wire_in) = true, deprecated = true]; + MessageType_EncryptedMessage = 50 [(wire_out) = true, deprecated = true]; + MessageType_DecryptMessage = 51 [(wire_in) = true, deprecated = true]; + MessageType_DecryptedMessage = 52 [(wire_out) = true, deprecated = true]; + MessageType_SignIdentity = 53 [(wire_in) = true]; + MessageType_SignedIdentity = 54 [(wire_out) = true]; + MessageType_GetFeatures = 55 [(wire_in) = true]; + MessageType_EthereumGetAddress = 56 [(wire_in) = true]; + MessageType_EthereumAddress = 57 [(wire_out) = true]; + MessageType_EthereumSignTx = 58 [(wire_in) = true]; + MessageType_EthereumTxRequest = 59 [(wire_out) = true]; + MessageType_EthereumTxAck = 60 [(wire_in) = true]; + MessageType_GetECDHSessionKey = 61 [(wire_in) = true]; + MessageType_ECDHSessionKey = 62 [(wire_out) = true]; + MessageType_SetU2FCounter = 63 [(wire_in) = true]; + MessageType_EthereumSignMessage = 64 [(wire_in) = true]; + MessageType_EthereumVerifyMessage = 65 [(wire_in) = true]; + MessageType_EthereumMessageSignature = 66 [(wire_out) = true]; + MessageType_DebugLinkDecision = 100 [(wire_debug_in) = true, (wire_tiny) = true]; + MessageType_DebugLinkGetState = 101 [(wire_debug_in) = true]; + MessageType_DebugLinkState = 102 [(wire_debug_out) = true]; + MessageType_DebugLinkStop = 103 [(wire_debug_in) = true]; + MessageType_DebugLinkLog = 104 [(wire_debug_out) = true]; + MessageType_DebugLinkMemoryRead = 110 [(wire_debug_in) = true]; + MessageType_DebugLinkMemory = 111 [(wire_debug_out) = true]; + MessageType_DebugLinkMemoryWrite = 112 [(wire_debug_in) = true]; + MessageType_DebugLinkFlashErase = 113 [(wire_debug_in) = true]; +} + +//////////////////// +// Basic messages // +//////////////////// + +/** + * Request: Reset device to default state and ask for device details + * @next Features + */ +message Initialize { +} + +/** + * Request: Ask for device details (no device reset) + * @next Features + */ +message GetFeatures { +} + +/** + * Response: Reports various information about the device + * @prev Initialize + * @prev GetFeatures + */ +message Features { + optional string vendor = 1; // name of the manufacturer, e.g. "bitcointrezor.com" + optional uint32 major_version = 2; // major version of the device, e.g. 1 + optional uint32 minor_version = 3; // minor version of the device, e.g. 0 + optional uint32 patch_version = 4; // patch version of the device, e.g. 0 + optional bool bootloader_mode = 5; // is device in bootloader mode? + optional string device_id = 6; // device's unique identifier + optional bool pin_protection = 7; // is device protected by PIN? + optional bool passphrase_protection = 8; // is node/mnemonic encrypted using passphrase? + optional string language = 9; // device language + optional string label = 10; // device description label + repeated CoinType coins = 11; // supported coins + optional bool initialized = 12; // does device contain seed? + optional bytes revision = 13; // SCM revision of firmware + optional bytes bootloader_hash = 14; // hash of the bootloader + optional bool imported = 15; // was storage imported from an external source? + optional bool pin_cached = 16; // is PIN already cached in session? + optional bool passphrase_cached = 17; // is passphrase already cached in session? + optional bool firmware_present = 18; // is valid firmware loaded? + optional bool needs_backup = 19; // does storage need backup? (equals to Storage.needs_backup) + optional uint32 flags = 20; // device flags (equals to Storage.flags) +} + +/** + * Request: clear session (removes cached PIN, passphrase, etc). + * @next Success + */ +message ClearSession { +} + +/** + * Request: change language and/or label of the device + * @next Success + * @next Failure + * @next ButtonRequest + * @next PinMatrixRequest + */ +message ApplySettings { + optional string language = 1; + optional string label = 2; + optional bool use_passphrase = 3; + optional bytes homescreen = 4; +} + +/** + * Request: set flags of the device + * @next Success + * @next Failure + */ +message ApplyFlags { + optional uint32 flags = 1; // bitmask, can only set bits, not unset +} + +/** + * Request: Starts workflow for setting/changing/removing the PIN + * @next ButtonRequest + * @next PinMatrixRequest + */ +message ChangePin { + optional bool remove = 1; // is PIN removal requested? +} + +/** + * Request: Test if the device is alive, device sends back the message in Success response + * @next Success + */ +message Ping { + optional string message = 1; // message to send back in Success message + optional bool button_protection = 2; // ask for button press + optional bool pin_protection = 3; // ask for PIN if set in device + optional bool passphrase_protection = 4; // ask for passphrase if set in device +} + +/** + * Response: Success of the previous request + */ +message Success { + optional string message = 1; // human readable description of action or request-specific payload +} + +/** + * Response: Failure of the previous request + */ +message Failure { + optional FailureType code = 1; // computer-readable definition of the error state + optional string message = 2; // human-readable message of the error state +} + +/** + * Response: Device is waiting for HW button press. + * @next ButtonAck + * @next Cancel + */ +message ButtonRequest { + optional ButtonRequestType code = 1; + optional string data = 2; +} + +/** + * Request: Computer agrees to wait for HW button press + * @prev ButtonRequest + */ +message ButtonAck { +} + +/** + * Response: Device is asking computer to show PIN matrix and awaits PIN encoded using this matrix scheme + * @next PinMatrixAck + * @next Cancel + */ +message PinMatrixRequest { + optional PinMatrixRequestType type = 1; +} + +/** + * Request: Computer responds with encoded PIN + * @prev PinMatrixRequest + */ +message PinMatrixAck { + required string pin = 1; // matrix encoded PIN entered by user +} + +/** + * Request: Abort last operation that required user interaction + * @prev ButtonRequest + * @prev PinMatrixRequest + * @prev PassphraseRequest + */ +message Cancel { +} + +/** + * Response: Device awaits encryption passphrase + * @next PassphraseAck + * @next Cancel + */ +message PassphraseRequest { +} + +/** + * Request: Send passphrase back + * @prev PassphraseRequest + */ +message PassphraseAck { + required string passphrase = 1; +} + +/** + * Request: Request a sample of random data generated by hardware RNG. May be used for testing. + * @next ButtonRequest + * @next Entropy + * @next Failure + */ +message GetEntropy { + required uint32 size = 1; // size of requested entropy +} + +/** + * Response: Reply with random data generated by internal RNG + * @prev GetEntropy + */ +message Entropy { + required bytes entropy = 1; // stream of random generated bytes +} + +/** + * Request: Ask device for public key corresponding to address_n path + * @next PassphraseRequest + * @next PublicKey + * @next Failure + */ +message GetPublicKey { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional string ecdsa_curve_name = 2; // ECDSA curve name to use + optional bool show_display = 3; // optionally show on display before sending the result + optional string coin_name = 4 [default='Bitcoin']; +} + +/** + * Response: Contains public key derived from device private seed + * @prev GetPublicKey + */ +message PublicKey { + required HDNodeType node = 1; // BIP32 public node + optional string xpub = 2; // serialized form of public node +} + +/** + * Request: Ask device for address corresponding to address_n path + * @next PassphraseRequest + * @next Address + * @next Failure + */ +message GetAddress { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional string coin_name = 2 [default='Bitcoin']; + optional bool show_display = 3 ; // optionally show on display before sending the result + optional MultisigRedeemScriptType multisig = 4; // filled if we are showing a multisig address + optional InputScriptType script_type = 5 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.) +} + +/** + * Request: Ask device for Ethereum address corresponding to address_n path + * @next PassphraseRequest + * @next EthereumAddress + * @next Failure + */ +message EthereumGetAddress { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional bool show_display = 2; // optionally show on display before sending the result +} + +/** + * Response: Contains address derived from device private seed + * @prev GetAddress + */ +message Address { + required string address = 1; // Coin address in Base58 encoding +} + +/** + * Response: Contains an Ethereum address derived from device private seed + * @prev EthereumGetAddress + */ +message EthereumAddress { + required bytes address = 1; // Coin address as an Ethereum 160 bit hash +} + +/** + * Request: Request device to wipe all sensitive data and settings + * @next ButtonRequest + */ +message WipeDevice { +} + +/** + * Request: Load seed and related internal settings from the computer + * @next ButtonRequest + * @next Success + * @next Failure + */ +message LoadDevice { + optional string mnemonic = 1; // seed encoded as BIP-39 mnemonic (12, 18 or 24 words) + optional HDNodeType node = 2; // BIP-32 node + optional string pin = 3; // set PIN protection + optional bool passphrase_protection = 4; // enable master node encryption using passphrase + optional string language = 5 [default='english']; // device language + optional string label = 6; // device label + optional bool skip_checksum = 7; // do not test mnemonic for valid BIP-39 checksum + optional uint32 u2f_counter = 8; // U2F counter +} + +/** + * Request: Ask device to do initialization involving user interaction + * @next EntropyRequest + * @next Failure + */ +message ResetDevice { + optional bool display_random = 1; // display entropy generated by the device before asking for additional entropy + optional uint32 strength = 2 [default=256]; // strength of seed in bits + optional bool passphrase_protection = 3; // enable master node encryption using passphrase + optional bool pin_protection = 4; // enable PIN protection + optional string language = 5 [default='english']; // device language + optional string label = 6; // device label + optional uint32 u2f_counter = 7; // U2F counter + optional bool skip_backup = 8; // postpone seed backup to BackupDevice workflow +} + +/** + * Request: Perform backup of the device seed if not backed up using ResetDevice + * @next ButtonRequest + */ +message BackupDevice { +} + +/** + * Response: Ask for additional entropy from host computer + * @prev ResetDevice + * @next EntropyAck + */ +message EntropyRequest { +} + +/** + * Request: Provide additional entropy for seed generation function + * @prev EntropyRequest + * @next ButtonRequest + */ +message EntropyAck { + optional bytes entropy = 1; // 256 bits (32 bytes) of random data +} + +/** + * Request: Start recovery workflow asking user for specific words of mnemonic + * Used to recovery device safely even on untrusted computer. + * @next WordRequest + */ +message RecoveryDevice { + optional uint32 word_count = 1; // number of words in BIP-39 mnemonic + optional bool passphrase_protection = 2; // enable master node encryption using passphrase + optional bool pin_protection = 3; // enable PIN protection + optional string language = 4 [default='english']; // device language + optional string label = 5; // device label + optional bool enforce_wordlist = 6; // enforce BIP-39 wordlist during the process + // 7 reserved for unused recovery method + optional uint32 type = 8; // supported recovery type (see RecoveryType) + optional uint32 u2f_counter = 9; // U2F counter + optional bool dry_run = 10; // perform dry-run recovery workflow (for safe mnemonic validation) +} + +/** + * Response: Device is waiting for user to enter word of the mnemonic + * Its position is shown only on device's internal display. + * @prev RecoveryDevice + * @prev WordAck + */ +message WordRequest { + optional WordRequestType type = 1; +} + +/** + * Request: Computer replies with word from the mnemonic + * @prev WordRequest + * @next WordRequest + * @next Success + * @next Failure + */ +message WordAck { + required string word = 1; // one word of mnemonic on asked position +} + +////////////////////////////// +// Message signing messages // +////////////////////////////// + +/** + * Request: Ask device to sign message + * @next MessageSignature + * @next Failure + */ +message SignMessage { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + required bytes message = 2; // message to be signed + optional string coin_name = 3 [default='Bitcoin']; // coin to use for signing + optional InputScriptType script_type = 4 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.) +} + +/** + * Request: Ask device to verify message + * @next Success + * @next Failure + */ +message VerifyMessage { + optional string address = 1; // address to verify + optional bytes signature = 2; // signature to verify + optional bytes message = 3; // message to verify + optional string coin_name = 4 [default='Bitcoin']; // coin to use for verifying +} + +/** + * Response: Signed message + * @prev SignMessage + */ +message MessageSignature { + optional string address = 1; // address used to sign the message + optional bytes signature = 2; // signature of the message +} + +/////////////////////////// +// Encryption/decryption // +/////////////////////////// + +/** + * Request: Ask device to encrypt message + * @next EncryptedMessage + * @next Failure + */ +message EncryptMessage { + optional bytes pubkey = 1; // public key + optional bytes message = 2; // message to encrypt + optional bool display_only = 3; // show just on display? (don't send back via wire) + repeated uint32 address_n = 4; // BIP-32 path to derive the signing key from master node + optional string coin_name = 5 [default='Bitcoin']; // coin to use for signing +} + +/** + * Response: Encrypted message + * @prev EncryptMessage + */ +message EncryptedMessage { + optional bytes nonce = 1; // nonce used during encryption + optional bytes message = 2; // encrypted message + optional bytes hmac = 3; // message hmac +} + +/** + * Request: Ask device to decrypt message + * @next Success + * @next Failure + */ +message DecryptMessage { + repeated uint32 address_n = 1; // BIP-32 path to derive the decryption key from master node + optional bytes nonce = 2; // nonce used during encryption + optional bytes message = 3; // message to decrypt + optional bytes hmac = 4; // message hmac +} + +/** + * Response: Decrypted message + * @prev DecryptedMessage + */ +message DecryptedMessage { + optional bytes message = 1; // decrypted message + optional string address = 2; // address used to sign the message (if used) +} + +/** + * Request: Ask device to encrypt or decrypt value of given key + * @next CipheredKeyValue + * @next Failure + */ +message CipherKeyValue { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional string key = 2; // key component of key:value + optional bytes value = 3; // value component of key:value + optional bool encrypt = 4; // are we encrypting (True) or decrypting (False)? + optional bool ask_on_encrypt = 5; // should we ask on encrypt operation? + optional bool ask_on_decrypt = 6; // should we ask on decrypt operation? + optional bytes iv = 7; // initialization vector (will be computed if not set) +} + +/** + * Response: Return ciphered/deciphered value + * @prev CipherKeyValue + */ +message CipheredKeyValue { + optional bytes value = 1; // ciphered/deciphered value +} + +////////////////////////////////// +// Transaction signing messages // +////////////////////////////////// + +/** + * Request: Estimated size of the transaction + * This behaves exactly like SignTx, which means that it can ask using TxRequest + * This call is non-blocking (except possible PassphraseRequest to unlock the seed) + * @next TxSize + * @next Failure + */ +message EstimateTxSize { + required uint32 outputs_count = 1; // number of transaction outputs + required uint32 inputs_count = 2; // number of transaction inputs + optional string coin_name = 3 [default='Bitcoin']; // coin to use +} + +/** + * Response: Estimated size of the transaction + * @prev EstimateTxSize + */ +message TxSize { + optional uint32 tx_size = 1; // estimated size of transaction in bytes +} + +/** + * Request: Ask device to sign transaction + * @next PassphraseRequest + * @next PinMatrixRequest + * @next TxRequest + * @next Failure + */ +message SignTx { + required uint32 outputs_count = 1; // number of transaction outputs + required uint32 inputs_count = 2; // number of transaction inputs + optional string coin_name = 3 [default='Bitcoin']; // coin to use + optional uint32 version = 4 [default=1]; // transaction version + optional uint32 lock_time = 5 [default=0]; // transaction lock_time +} + +/** + * Request: Simplified transaction signing + * This method doesn't support streaming, so there are hardware limits in number of inputs and outputs. + * In case of success, the result is returned using TxRequest message. + * @next PassphraseRequest + * @next PinMatrixRequest + * @next TxRequest + * @next Failure + */ +message SimpleSignTx { + repeated TxInputType inputs = 1; // transaction inputs + repeated TxOutputType outputs = 2; // transaction outputs + repeated TransactionType transactions = 3; // transactions whose outputs are used to build current inputs + optional string coin_name = 4 [default='Bitcoin']; // coin to use + optional uint32 version = 5 [default=1]; // transaction version + optional uint32 lock_time = 6 [default=0]; // transaction lock_time +} + +/** + * Response: Device asks for information for signing transaction or returns the last result + * If request_index is set, device awaits TxAck message (with fields filled in according to request_type) + * If signature_index is set, 'signature' contains signed input of signature_index's input + * @prev SignTx + * @prev SimpleSignTx + * @prev TxAck + */ +message TxRequest { + optional RequestType request_type = 1; // what should be filled in TxAck message? + optional TxRequestDetailsType details = 2; // request for tx details + optional TxRequestSerializedType serialized = 3; // serialized data and request for next +} + +/** + * Request: Reported transaction data + * @prev TxRequest + * @next TxRequest + */ +message TxAck { + optional TransactionType tx = 1; +} + +/** + * Request: Ask device to sign transaction + * All fields are optional from the protocol's point of view. Each field defaults to value `0` if missing. + * Note: the first at most 1024 bytes of data MUST be transmitted as part of this message. + * @next PassphraseRequest + * @next PinMatrixRequest + * @next EthereumTxRequest + * @next Failure + */ +message EthereumSignTx { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional bytes nonce = 2; // <=256 bit unsigned big endian + optional bytes gas_price = 3; // <=256 bit unsigned big endian (in wei) + optional bytes gas_limit = 4; // <=256 bit unsigned big endian + optional bytes to = 5; // 160 bit address hash + optional bytes value = 6; // <=256 bit unsigned big endian (in wei) + optional bytes data_initial_chunk = 7; // The initial data chunk (<= 1024 bytes) + optional uint32 data_length = 8; // Length of transaction payload + optional uint32 chain_id = 9; // Chain Id for EIP 155 +} + +/** + * Response: Device asks for more data from transaction payload, or returns the signature. + * If data_length is set, device awaits that many more bytes of payload. + * Otherwise, the signature_* fields contain the computed transaction signature. All three fields will be present. + * @prev EthereumSignTx + * @next EthereumTxAck + */ +message EthereumTxRequest { + optional uint32 data_length = 1; // Number of bytes being requested (<= 1024) + optional uint32 signature_v = 2; // Computed signature (recovery parameter, limited to 27 or 28) + optional bytes signature_r = 3; // Computed signature R component (256 bit) + optional bytes signature_s = 4; // Computed signature S component (256 bit) +} + +/** + * Request: Transaction payload data. + * @prev EthereumTxRequest + * @next EthereumTxRequest + */ +message EthereumTxAck { + optional bytes data_chunk = 1; // Bytes from transaction payload (<= 1024 bytes) +} + +//////////////////////////////////////// +// Ethereum: Message signing messages // +//////////////////////////////////////// + +/** + * Request: Ask device to sign message + * @next EthereumMessageSignature + * @next Failure + */ +message EthereumSignMessage { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + required bytes message = 2; // message to be signed +} + +/** + * Request: Ask device to verify message + * @next Success + * @next Failure + */ +message EthereumVerifyMessage { + optional bytes address = 1; // address to verify + optional bytes signature = 2; // signature to verify + optional bytes message = 3; // message to verify +} + +/** + * Response: Signed message + * @prev EthereumSignMessage + */ +message EthereumMessageSignature { + optional bytes address = 1; // address used to sign the message + optional bytes signature = 2; // signature of the message +} + +/////////////////////// +// Identity messages // +/////////////////////// + +/** + * Request: Ask device to sign identity + * @next SignedIdentity + * @next Failure + */ +message SignIdentity { + optional IdentityType identity = 1; // identity + optional bytes challenge_hidden = 2; // non-visible challenge + optional string challenge_visual = 3; // challenge shown on display (e.g. date+time) + optional string ecdsa_curve_name = 4; // ECDSA curve name to use +} + +/** + * Response: Device provides signed identity + * @prev SignIdentity + */ +message SignedIdentity { + optional string address = 1; // identity address + optional bytes public_key = 2; // identity public key + optional bytes signature = 3; // signature of the identity data +} + +/////////////////// +// ECDH messages // +/////////////////// + +/** + * Request: Ask device to generate ECDH session key + * @next ECDHSessionKey + * @next Failure + */ +message GetECDHSessionKey { + optional IdentityType identity = 1; // identity + optional bytes peer_public_key = 2; // peer's public key + optional string ecdsa_curve_name = 3; // ECDSA curve name to use +} + +/** + * Response: Device provides ECDH session key + * @prev GetECDHSessionKey + */ +message ECDHSessionKey { + optional bytes session_key = 1; // ECDH session key +} + +/////////////////// +// U2F messages // +/////////////////// + +/** + * Request: Set U2F counter + * @next Success + */ +message SetU2FCounter { + optional uint32 u2f_counter = 1; // counter +} + +///////////////////////// +// Bootloader messages // +///////////////////////// + +/** + * Request: Ask device to erase its firmware (so it can be replaced via FirmwareUpload) + * @next Success + * @next FirmwareRequest + * @next Failure + */ +message FirmwareErase { + optional uint32 length = 1; // length of new firmware +} + +/** + * Response: Ask for firmware chunk + * @next FirmwareUpload + */ +message FirmwareRequest { + optional uint32 offset = 1; // offset of requested firmware chunk + optional uint32 length = 2; // length of requested firmware chunk +} + +/** + * Request: Send firmware in binary form to the device + * @next Success + * @next Failure + */ +message FirmwareUpload { + required bytes payload = 1; // firmware to be loaded into device + optional bytes hash = 2; // hash of the payload +} + + +/** + * Request: Perform a device self-test + * @next Success + * @next Failure + */ +message SelfTest { + optional bytes payload = 1; // payload to be used in self-test +} + +///////////////////////////////////////////////////////////// +// Debug messages (only available if DebugLink is enabled) // +///////////////////////////////////////////////////////////// + +/** + * Request: "Press" the button on the device + * @next Success + */ +message DebugLinkDecision { + required bool yes_no = 1; // true for "Confirm", false for "Cancel" +} + +/** + * Request: Computer asks for device state + * @next DebugLinkState + */ +message DebugLinkGetState { +} + +/** + * Response: Device current state + * @prev DebugLinkGetState + */ +message DebugLinkState { + optional bytes layout = 1; // raw buffer of display + optional string pin = 2; // current PIN, blank if PIN is not set/enabled + optional string matrix = 3; // current PIN matrix + optional string mnemonic = 4; // current BIP-39 mnemonic + optional HDNodeType node = 5; // current BIP-32 node + optional bool passphrase_protection = 6; // is node/mnemonic encrypted using passphrase? + optional string reset_word = 7; // word on device display during ResetDevice workflow + optional bytes reset_entropy = 8; // current entropy during ResetDevice workflow + optional string recovery_fake_word = 9; // (fake) word on display during RecoveryDevice workflow + optional uint32 recovery_word_pos = 10; // index of mnemonic word the device is expecting during RecoveryDevice workflow +} + +/** + * Request: Ask device to restart + */ +message DebugLinkStop { +} + +/** + * Response: Device wants host to log event + */ +message DebugLinkLog { + optional uint32 level = 1; + optional string bucket = 2; + optional string text = 3; +} + +/** + * Request: Read memory from device + * @next DebugLinkMemory + */ +message DebugLinkMemoryRead { + optional uint32 address = 1; + optional uint32 length = 2; +} + +/** + * Response: Device sends memory back + * @prev DebugLinkMemoryRead + */ +message DebugLinkMemory { + optional bytes memory = 1; +} + +/** + * Request: Write memory to device. + * WARNING: Writing to the wrong location can irreparably break the device. + */ +message DebugLinkMemoryWrite { + optional uint32 address = 1; + optional bytes memory = 2; + optional bool flash = 3; +} + +/** + * Request: Erase block of flash on device + * WARNING: Writing to the wrong location can irreparably break the device. + */ +message DebugLinkFlashErase { + optional uint32 sector = 1; +} diff --git a/accounts/usbwallet/trezor/trezor.go b/accounts/usbwallet/trezor/trezor.go new file mode 100644 index 000000000..80cc75efc --- /dev/null +++ b/accounts/usbwallet/trezor/trezor.go @@ -0,0 +1,46 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// This file contains the implementation for interacting with the Trezor hardware +// wallets. The wire protocol spec can be found on the SatoshiLabs website: +// https://doc.satoshilabs.com/trezor-tech/api-protobuf.html + +//go:generate protoc --go_out=import_path=trezor:. types.proto messages.proto + +// Package trezor contains the wire protocol wrapper in Go. +package trezor + +import ( + "reflect" + + "github.com/golang/protobuf/proto" +) + +// Type returns the protocol buffer type number of a specific message. If the +// message is nil, this method panics! +func Type(msg proto.Message) uint16 { + return uint16(MessageType_value["MessageType_"+reflect.TypeOf(msg).Elem().Name()]) +} + +// Name returns the friendly message type name of a specific protocol buffer +// type number. +func Name(kind uint16) string { + name := MessageType_name[int32(kind)] + if len(name) < 12 { + return name + } + return name[12:] +} diff --git a/accounts/usbwallet/trezor/types.pb.go b/accounts/usbwallet/trezor/types.pb.go new file mode 100644 index 000000000..25b7672d2 --- /dev/null +++ b/accounts/usbwallet/trezor/types.pb.go @@ -0,0 +1,1333 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: types.proto + +/* +Package trezor is a generated protocol buffer package. + +It is generated from these files: + types.proto + messages.proto + +It has these top-level messages: + HDNodeType + HDNodePathType + CoinType + MultisigRedeemScriptType + TxInputType + TxOutputType + TxOutputBinType + TransactionType + TxRequestDetailsType + TxRequestSerializedType + IdentityType + Initialize + GetFeatures + Features + ClearSession + ApplySettings + ApplyFlags + ChangePin + Ping + Success + Failure + ButtonRequest + ButtonAck + PinMatrixRequest + PinMatrixAck + Cancel + PassphraseRequest + PassphraseAck + GetEntropy + Entropy + GetPublicKey + PublicKey + GetAddress + EthereumGetAddress + Address + EthereumAddress + WipeDevice + LoadDevice + ResetDevice + BackupDevice + EntropyRequest + EntropyAck + RecoveryDevice + WordRequest + WordAck + SignMessage + VerifyMessage + MessageSignature + EncryptMessage + EncryptedMessage + DecryptMessage + DecryptedMessage + CipherKeyValue + CipheredKeyValue + EstimateTxSize + TxSize + SignTx + SimpleSignTx + TxRequest + TxAck + EthereumSignTx + EthereumTxRequest + EthereumTxAck + EthereumSignMessage + EthereumVerifyMessage + EthereumMessageSignature + SignIdentity + SignedIdentity + GetECDHSessionKey + ECDHSessionKey + SetU2FCounter + FirmwareErase + FirmwareRequest + FirmwareUpload + SelfTest + DebugLinkDecision + DebugLinkGetState + DebugLinkState + DebugLinkStop + DebugLinkLog + DebugLinkMemoryRead + DebugLinkMemory + DebugLinkMemoryWrite + DebugLinkFlashErase +*/ +package trezor + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/protoc-gen-go/descriptor" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// * +// Type of failures returned by Failure message +// @used_in Failure +type FailureType int32 + +const ( + FailureType_Failure_UnexpectedMessage FailureType = 1 + FailureType_Failure_ButtonExpected FailureType = 2 + FailureType_Failure_DataError FailureType = 3 + FailureType_Failure_ActionCancelled FailureType = 4 + FailureType_Failure_PinExpected FailureType = 5 + FailureType_Failure_PinCancelled FailureType = 6 + FailureType_Failure_PinInvalid FailureType = 7 + FailureType_Failure_InvalidSignature FailureType = 8 + FailureType_Failure_ProcessError FailureType = 9 + FailureType_Failure_NotEnoughFunds FailureType = 10 + FailureType_Failure_NotInitialized FailureType = 11 + FailureType_Failure_FirmwareError FailureType = 99 +) + +var FailureType_name = map[int32]string{ + 1: "Failure_UnexpectedMessage", + 2: "Failure_ButtonExpected", + 3: "Failure_DataError", + 4: "Failure_ActionCancelled", + 5: "Failure_PinExpected", + 6: "Failure_PinCancelled", + 7: "Failure_PinInvalid", + 8: "Failure_InvalidSignature", + 9: "Failure_ProcessError", + 10: "Failure_NotEnoughFunds", + 11: "Failure_NotInitialized", + 99: "Failure_FirmwareError", +} +var FailureType_value = map[string]int32{ + "Failure_UnexpectedMessage": 1, + "Failure_ButtonExpected": 2, + "Failure_DataError": 3, + "Failure_ActionCancelled": 4, + "Failure_PinExpected": 5, + "Failure_PinCancelled": 6, + "Failure_PinInvalid": 7, + "Failure_InvalidSignature": 8, + "Failure_ProcessError": 9, + "Failure_NotEnoughFunds": 10, + "Failure_NotInitialized": 11, + "Failure_FirmwareError": 99, +} + +func (x FailureType) Enum() *FailureType { + p := new(FailureType) + *p = x + return p +} +func (x FailureType) String() string { + return proto.EnumName(FailureType_name, int32(x)) +} +func (x *FailureType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(FailureType_value, data, "FailureType") + if err != nil { + return err + } + *x = FailureType(value) + return nil +} +func (FailureType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +// * +// Type of script which will be used for transaction output +// @used_in TxOutputType +type OutputScriptType int32 + +const ( + OutputScriptType_PAYTOADDRESS OutputScriptType = 0 + OutputScriptType_PAYTOSCRIPTHASH OutputScriptType = 1 + OutputScriptType_PAYTOMULTISIG OutputScriptType = 2 + OutputScriptType_PAYTOOPRETURN OutputScriptType = 3 + OutputScriptType_PAYTOWITNESS OutputScriptType = 4 + OutputScriptType_PAYTOP2SHWITNESS OutputScriptType = 5 +) + +var OutputScriptType_name = map[int32]string{ + 0: "PAYTOADDRESS", + 1: "PAYTOSCRIPTHASH", + 2: "PAYTOMULTISIG", + 3: "PAYTOOPRETURN", + 4: "PAYTOWITNESS", + 5: "PAYTOP2SHWITNESS", +} +var OutputScriptType_value = map[string]int32{ + "PAYTOADDRESS": 0, + "PAYTOSCRIPTHASH": 1, + "PAYTOMULTISIG": 2, + "PAYTOOPRETURN": 3, + "PAYTOWITNESS": 4, + "PAYTOP2SHWITNESS": 5, +} + +func (x OutputScriptType) Enum() *OutputScriptType { + p := new(OutputScriptType) + *p = x + return p +} +func (x OutputScriptType) String() string { + return proto.EnumName(OutputScriptType_name, int32(x)) +} +func (x *OutputScriptType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(OutputScriptType_value, data, "OutputScriptType") + if err != nil { + return err + } + *x = OutputScriptType(value) + return nil +} +func (OutputScriptType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +// * +// Type of script which will be used for transaction output +// @used_in TxInputType +type InputScriptType int32 + +const ( + InputScriptType_SPENDADDRESS InputScriptType = 0 + InputScriptType_SPENDMULTISIG InputScriptType = 1 + InputScriptType_EXTERNAL InputScriptType = 2 + InputScriptType_SPENDWITNESS InputScriptType = 3 + InputScriptType_SPENDP2SHWITNESS InputScriptType = 4 +) + +var InputScriptType_name = map[int32]string{ + 0: "SPENDADDRESS", + 1: "SPENDMULTISIG", + 2: "EXTERNAL", + 3: "SPENDWITNESS", + 4: "SPENDP2SHWITNESS", +} +var InputScriptType_value = map[string]int32{ + "SPENDADDRESS": 0, + "SPENDMULTISIG": 1, + "EXTERNAL": 2, + "SPENDWITNESS": 3, + "SPENDP2SHWITNESS": 4, +} + +func (x InputScriptType) Enum() *InputScriptType { + p := new(InputScriptType) + *p = x + return p +} +func (x InputScriptType) String() string { + return proto.EnumName(InputScriptType_name, int32(x)) +} +func (x *InputScriptType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(InputScriptType_value, data, "InputScriptType") + if err != nil { + return err + } + *x = InputScriptType(value) + return nil +} +func (InputScriptType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +// * +// Type of information required by transaction signing process +// @used_in TxRequest +type RequestType int32 + +const ( + RequestType_TXINPUT RequestType = 0 + RequestType_TXOUTPUT RequestType = 1 + RequestType_TXMETA RequestType = 2 + RequestType_TXFINISHED RequestType = 3 + RequestType_TXEXTRADATA RequestType = 4 +) + +var RequestType_name = map[int32]string{ + 0: "TXINPUT", + 1: "TXOUTPUT", + 2: "TXMETA", + 3: "TXFINISHED", + 4: "TXEXTRADATA", +} +var RequestType_value = map[string]int32{ + "TXINPUT": 0, + "TXOUTPUT": 1, + "TXMETA": 2, + "TXFINISHED": 3, + "TXEXTRADATA": 4, +} + +func (x RequestType) Enum() *RequestType { + p := new(RequestType) + *p = x + return p +} +func (x RequestType) String() string { + return proto.EnumName(RequestType_name, int32(x)) +} +func (x *RequestType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(RequestType_value, data, "RequestType") + if err != nil { + return err + } + *x = RequestType(value) + return nil +} +func (RequestType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +// * +// Type of button request +// @used_in ButtonRequest +type ButtonRequestType int32 + +const ( + ButtonRequestType_ButtonRequest_Other ButtonRequestType = 1 + ButtonRequestType_ButtonRequest_FeeOverThreshold ButtonRequestType = 2 + ButtonRequestType_ButtonRequest_ConfirmOutput ButtonRequestType = 3 + ButtonRequestType_ButtonRequest_ResetDevice ButtonRequestType = 4 + ButtonRequestType_ButtonRequest_ConfirmWord ButtonRequestType = 5 + ButtonRequestType_ButtonRequest_WipeDevice ButtonRequestType = 6 + ButtonRequestType_ButtonRequest_ProtectCall ButtonRequestType = 7 + ButtonRequestType_ButtonRequest_SignTx ButtonRequestType = 8 + ButtonRequestType_ButtonRequest_FirmwareCheck ButtonRequestType = 9 + ButtonRequestType_ButtonRequest_Address ButtonRequestType = 10 + ButtonRequestType_ButtonRequest_PublicKey ButtonRequestType = 11 +) + +var ButtonRequestType_name = map[int32]string{ + 1: "ButtonRequest_Other", + 2: "ButtonRequest_FeeOverThreshold", + 3: "ButtonRequest_ConfirmOutput", + 4: "ButtonRequest_ResetDevice", + 5: "ButtonRequest_ConfirmWord", + 6: "ButtonRequest_WipeDevice", + 7: "ButtonRequest_ProtectCall", + 8: "ButtonRequest_SignTx", + 9: "ButtonRequest_FirmwareCheck", + 10: "ButtonRequest_Address", + 11: "ButtonRequest_PublicKey", +} +var ButtonRequestType_value = map[string]int32{ + "ButtonRequest_Other": 1, + "ButtonRequest_FeeOverThreshold": 2, + "ButtonRequest_ConfirmOutput": 3, + "ButtonRequest_ResetDevice": 4, + "ButtonRequest_ConfirmWord": 5, + "ButtonRequest_WipeDevice": 6, + "ButtonRequest_ProtectCall": 7, + "ButtonRequest_SignTx": 8, + "ButtonRequest_FirmwareCheck": 9, + "ButtonRequest_Address": 10, + "ButtonRequest_PublicKey": 11, +} + +func (x ButtonRequestType) Enum() *ButtonRequestType { + p := new(ButtonRequestType) + *p = x + return p +} +func (x ButtonRequestType) String() string { + return proto.EnumName(ButtonRequestType_name, int32(x)) +} +func (x *ButtonRequestType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(ButtonRequestType_value, data, "ButtonRequestType") + if err != nil { + return err + } + *x = ButtonRequestType(value) + return nil +} +func (ButtonRequestType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } + +// * +// Type of PIN request +// @used_in PinMatrixRequest +type PinMatrixRequestType int32 + +const ( + PinMatrixRequestType_PinMatrixRequestType_Current PinMatrixRequestType = 1 + PinMatrixRequestType_PinMatrixRequestType_NewFirst PinMatrixRequestType = 2 + PinMatrixRequestType_PinMatrixRequestType_NewSecond PinMatrixRequestType = 3 +) + +var PinMatrixRequestType_name = map[int32]string{ + 1: "PinMatrixRequestType_Current", + 2: "PinMatrixRequestType_NewFirst", + 3: "PinMatrixRequestType_NewSecond", +} +var PinMatrixRequestType_value = map[string]int32{ + "PinMatrixRequestType_Current": 1, + "PinMatrixRequestType_NewFirst": 2, + "PinMatrixRequestType_NewSecond": 3, +} + +func (x PinMatrixRequestType) Enum() *PinMatrixRequestType { + p := new(PinMatrixRequestType) + *p = x + return p +} +func (x PinMatrixRequestType) String() string { + return proto.EnumName(PinMatrixRequestType_name, int32(x)) +} +func (x *PinMatrixRequestType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(PinMatrixRequestType_value, data, "PinMatrixRequestType") + if err != nil { + return err + } + *x = PinMatrixRequestType(value) + return nil +} +func (PinMatrixRequestType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } + +// * +// Type of recovery procedure. These should be used as bitmask, e.g., +// `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix` +// listing every method supported by the host computer. +// +// Note that ScrambledWords must be supported by every implementation +// for backward compatibility; there is no way to not support it. +// +// @used_in RecoveryDevice +type RecoveryDeviceType int32 + +const ( + // use powers of two when extending this field + RecoveryDeviceType_RecoveryDeviceType_ScrambledWords RecoveryDeviceType = 0 + RecoveryDeviceType_RecoveryDeviceType_Matrix RecoveryDeviceType = 1 +) + +var RecoveryDeviceType_name = map[int32]string{ + 0: "RecoveryDeviceType_ScrambledWords", + 1: "RecoveryDeviceType_Matrix", +} +var RecoveryDeviceType_value = map[string]int32{ + "RecoveryDeviceType_ScrambledWords": 0, + "RecoveryDeviceType_Matrix": 1, +} + +func (x RecoveryDeviceType) Enum() *RecoveryDeviceType { + p := new(RecoveryDeviceType) + *p = x + return p +} +func (x RecoveryDeviceType) String() string { + return proto.EnumName(RecoveryDeviceType_name, int32(x)) +} +func (x *RecoveryDeviceType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(RecoveryDeviceType_value, data, "RecoveryDeviceType") + if err != nil { + return err + } + *x = RecoveryDeviceType(value) + return nil +} +func (RecoveryDeviceType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } + +// * +// Type of Recovery Word request +// @used_in WordRequest +type WordRequestType int32 + +const ( + WordRequestType_WordRequestType_Plain WordRequestType = 0 + WordRequestType_WordRequestType_Matrix9 WordRequestType = 1 + WordRequestType_WordRequestType_Matrix6 WordRequestType = 2 +) + +var WordRequestType_name = map[int32]string{ + 0: "WordRequestType_Plain", + 1: "WordRequestType_Matrix9", + 2: "WordRequestType_Matrix6", +} +var WordRequestType_value = map[string]int32{ + "WordRequestType_Plain": 0, + "WordRequestType_Matrix9": 1, + "WordRequestType_Matrix6": 2, +} + +func (x WordRequestType) Enum() *WordRequestType { + p := new(WordRequestType) + *p = x + return p +} +func (x WordRequestType) String() string { + return proto.EnumName(WordRequestType_name, int32(x)) +} +func (x *WordRequestType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(WordRequestType_value, data, "WordRequestType") + if err != nil { + return err + } + *x = WordRequestType(value) + return nil +} +func (WordRequestType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } + +// * +// Structure representing BIP32 (hierarchical deterministic) node +// Used for imports of private key into the device and exporting public key out of device +// @used_in PublicKey +// @used_in LoadDevice +// @used_in DebugLinkState +// @used_in Storage +type HDNodeType struct { + Depth *uint32 `protobuf:"varint,1,req,name=depth" json:"depth,omitempty"` + Fingerprint *uint32 `protobuf:"varint,2,req,name=fingerprint" json:"fingerprint,omitempty"` + ChildNum *uint32 `protobuf:"varint,3,req,name=child_num,json=childNum" json:"child_num,omitempty"` + ChainCode []byte `protobuf:"bytes,4,req,name=chain_code,json=chainCode" json:"chain_code,omitempty"` + PrivateKey []byte `protobuf:"bytes,5,opt,name=private_key,json=privateKey" json:"private_key,omitempty"` + PublicKey []byte `protobuf:"bytes,6,opt,name=public_key,json=publicKey" json:"public_key,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *HDNodeType) Reset() { *m = HDNodeType{} } +func (m *HDNodeType) String() string { return proto.CompactTextString(m) } +func (*HDNodeType) ProtoMessage() {} +func (*HDNodeType) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *HDNodeType) GetDepth() uint32 { + if m != nil && m.Depth != nil { + return *m.Depth + } + return 0 +} + +func (m *HDNodeType) GetFingerprint() uint32 { + if m != nil && m.Fingerprint != nil { + return *m.Fingerprint + } + return 0 +} + +func (m *HDNodeType) GetChildNum() uint32 { + if m != nil && m.ChildNum != nil { + return *m.ChildNum + } + return 0 +} + +func (m *HDNodeType) GetChainCode() []byte { + if m != nil { + return m.ChainCode + } + return nil +} + +func (m *HDNodeType) GetPrivateKey() []byte { + if m != nil { + return m.PrivateKey + } + return nil +} + +func (m *HDNodeType) GetPublicKey() []byte { + if m != nil { + return m.PublicKey + } + return nil +} + +type HDNodePathType struct { + Node *HDNodeType `protobuf:"bytes,1,req,name=node" json:"node,omitempty"` + AddressN []uint32 `protobuf:"varint,2,rep,name=address_n,json=addressN" json:"address_n,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *HDNodePathType) Reset() { *m = HDNodePathType{} } +func (m *HDNodePathType) String() string { return proto.CompactTextString(m) } +func (*HDNodePathType) ProtoMessage() {} +func (*HDNodePathType) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *HDNodePathType) GetNode() *HDNodeType { + if m != nil { + return m.Node + } + return nil +} + +func (m *HDNodePathType) GetAddressN() []uint32 { + if m != nil { + return m.AddressN + } + return nil +} + +// * +// Structure representing Coin +// @used_in Features +type CoinType struct { + CoinName *string `protobuf:"bytes,1,opt,name=coin_name,json=coinName" json:"coin_name,omitempty"` + CoinShortcut *string `protobuf:"bytes,2,opt,name=coin_shortcut,json=coinShortcut" json:"coin_shortcut,omitempty"` + AddressType *uint32 `protobuf:"varint,3,opt,name=address_type,json=addressType,def=0" json:"address_type,omitempty"` + MaxfeeKb *uint64 `protobuf:"varint,4,opt,name=maxfee_kb,json=maxfeeKb" json:"maxfee_kb,omitempty"` + AddressTypeP2Sh *uint32 `protobuf:"varint,5,opt,name=address_type_p2sh,json=addressTypeP2sh,def=5" json:"address_type_p2sh,omitempty"` + SignedMessageHeader *string `protobuf:"bytes,8,opt,name=signed_message_header,json=signedMessageHeader" json:"signed_message_header,omitempty"` + XpubMagic *uint32 `protobuf:"varint,9,opt,name=xpub_magic,json=xpubMagic,def=76067358" json:"xpub_magic,omitempty"` + XprvMagic *uint32 `protobuf:"varint,10,opt,name=xprv_magic,json=xprvMagic,def=76066276" json:"xprv_magic,omitempty"` + Segwit *bool `protobuf:"varint,11,opt,name=segwit" json:"segwit,omitempty"` + Forkid *uint32 `protobuf:"varint,12,opt,name=forkid" json:"forkid,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *CoinType) Reset() { *m = CoinType{} } +func (m *CoinType) String() string { return proto.CompactTextString(m) } +func (*CoinType) ProtoMessage() {} +func (*CoinType) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +const Default_CoinType_AddressType uint32 = 0 +const Default_CoinType_AddressTypeP2Sh uint32 = 5 +const Default_CoinType_XpubMagic uint32 = 76067358 +const Default_CoinType_XprvMagic uint32 = 76066276 + +func (m *CoinType) GetCoinName() string { + if m != nil && m.CoinName != nil { + return *m.CoinName + } + return "" +} + +func (m *CoinType) GetCoinShortcut() string { + if m != nil && m.CoinShortcut != nil { + return *m.CoinShortcut + } + return "" +} + +func (m *CoinType) GetAddressType() uint32 { + if m != nil && m.AddressType != nil { + return *m.AddressType + } + return Default_CoinType_AddressType +} + +func (m *CoinType) GetMaxfeeKb() uint64 { + if m != nil && m.MaxfeeKb != nil { + return *m.MaxfeeKb + } + return 0 +} + +func (m *CoinType) GetAddressTypeP2Sh() uint32 { + if m != nil && m.AddressTypeP2Sh != nil { + return *m.AddressTypeP2Sh + } + return Default_CoinType_AddressTypeP2Sh +} + +func (m *CoinType) GetSignedMessageHeader() string { + if m != nil && m.SignedMessageHeader != nil { + return *m.SignedMessageHeader + } + return "" +} + +func (m *CoinType) GetXpubMagic() uint32 { + if m != nil && m.XpubMagic != nil { + return *m.XpubMagic + } + return Default_CoinType_XpubMagic +} + +func (m *CoinType) GetXprvMagic() uint32 { + if m != nil && m.XprvMagic != nil { + return *m.XprvMagic + } + return Default_CoinType_XprvMagic +} + +func (m *CoinType) GetSegwit() bool { + if m != nil && m.Segwit != nil { + return *m.Segwit + } + return false +} + +func (m *CoinType) GetForkid() uint32 { + if m != nil && m.Forkid != nil { + return *m.Forkid + } + return 0 +} + +// * +// Type of redeem script used in input +// @used_in TxInputType +type MultisigRedeemScriptType struct { + Pubkeys []*HDNodePathType `protobuf:"bytes,1,rep,name=pubkeys" json:"pubkeys,omitempty"` + Signatures [][]byte `protobuf:"bytes,2,rep,name=signatures" json:"signatures,omitempty"` + M *uint32 `protobuf:"varint,3,opt,name=m" json:"m,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MultisigRedeemScriptType) Reset() { *m = MultisigRedeemScriptType{} } +func (m *MultisigRedeemScriptType) String() string { return proto.CompactTextString(m) } +func (*MultisigRedeemScriptType) ProtoMessage() {} +func (*MultisigRedeemScriptType) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +func (m *MultisigRedeemScriptType) GetPubkeys() []*HDNodePathType { + if m != nil { + return m.Pubkeys + } + return nil +} + +func (m *MultisigRedeemScriptType) GetSignatures() [][]byte { + if m != nil { + return m.Signatures + } + return nil +} + +func (m *MultisigRedeemScriptType) GetM() uint32 { + if m != nil && m.M != nil { + return *m.M + } + return 0 +} + +// * +// Structure representing transaction input +// @used_in SimpleSignTx +// @used_in TransactionType +type TxInputType struct { + AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` + PrevHash []byte `protobuf:"bytes,2,req,name=prev_hash,json=prevHash" json:"prev_hash,omitempty"` + PrevIndex *uint32 `protobuf:"varint,3,req,name=prev_index,json=prevIndex" json:"prev_index,omitempty"` + ScriptSig []byte `protobuf:"bytes,4,opt,name=script_sig,json=scriptSig" json:"script_sig,omitempty"` + Sequence *uint32 `protobuf:"varint,5,opt,name=sequence,def=4294967295" json:"sequence,omitempty"` + ScriptType *InputScriptType `protobuf:"varint,6,opt,name=script_type,json=scriptType,enum=InputScriptType,def=0" json:"script_type,omitempty"` + Multisig *MultisigRedeemScriptType `protobuf:"bytes,7,opt,name=multisig" json:"multisig,omitempty"` + Amount *uint64 `protobuf:"varint,8,opt,name=amount" json:"amount,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *TxInputType) Reset() { *m = TxInputType{} } +func (m *TxInputType) String() string { return proto.CompactTextString(m) } +func (*TxInputType) ProtoMessage() {} +func (*TxInputType) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } + +const Default_TxInputType_Sequence uint32 = 4294967295 +const Default_TxInputType_ScriptType InputScriptType = InputScriptType_SPENDADDRESS + +func (m *TxInputType) GetAddressN() []uint32 { + if m != nil { + return m.AddressN + } + return nil +} + +func (m *TxInputType) GetPrevHash() []byte { + if m != nil { + return m.PrevHash + } + return nil +} + +func (m *TxInputType) GetPrevIndex() uint32 { + if m != nil && m.PrevIndex != nil { + return *m.PrevIndex + } + return 0 +} + +func (m *TxInputType) GetScriptSig() []byte { + if m != nil { + return m.ScriptSig + } + return nil +} + +func (m *TxInputType) GetSequence() uint32 { + if m != nil && m.Sequence != nil { + return *m.Sequence + } + return Default_TxInputType_Sequence +} + +func (m *TxInputType) GetScriptType() InputScriptType { + if m != nil && m.ScriptType != nil { + return *m.ScriptType + } + return Default_TxInputType_ScriptType +} + +func (m *TxInputType) GetMultisig() *MultisigRedeemScriptType { + if m != nil { + return m.Multisig + } + return nil +} + +func (m *TxInputType) GetAmount() uint64 { + if m != nil && m.Amount != nil { + return *m.Amount + } + return 0 +} + +// * +// Structure representing transaction output +// @used_in SimpleSignTx +// @used_in TransactionType +type TxOutputType struct { + Address *string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` + AddressN []uint32 `protobuf:"varint,2,rep,name=address_n,json=addressN" json:"address_n,omitempty"` + Amount *uint64 `protobuf:"varint,3,req,name=amount" json:"amount,omitempty"` + ScriptType *OutputScriptType `protobuf:"varint,4,req,name=script_type,json=scriptType,enum=OutputScriptType" json:"script_type,omitempty"` + Multisig *MultisigRedeemScriptType `protobuf:"bytes,5,opt,name=multisig" json:"multisig,omitempty"` + OpReturnData []byte `protobuf:"bytes,6,opt,name=op_return_data,json=opReturnData" json:"op_return_data,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *TxOutputType) Reset() { *m = TxOutputType{} } +func (m *TxOutputType) String() string { return proto.CompactTextString(m) } +func (*TxOutputType) ProtoMessage() {} +func (*TxOutputType) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } + +func (m *TxOutputType) GetAddress() string { + if m != nil && m.Address != nil { + return *m.Address + } + return "" +} + +func (m *TxOutputType) GetAddressN() []uint32 { + if m != nil { + return m.AddressN + } + return nil +} + +func (m *TxOutputType) GetAmount() uint64 { + if m != nil && m.Amount != nil { + return *m.Amount + } + return 0 +} + +func (m *TxOutputType) GetScriptType() OutputScriptType { + if m != nil && m.ScriptType != nil { + return *m.ScriptType + } + return OutputScriptType_PAYTOADDRESS +} + +func (m *TxOutputType) GetMultisig() *MultisigRedeemScriptType { + if m != nil { + return m.Multisig + } + return nil +} + +func (m *TxOutputType) GetOpReturnData() []byte { + if m != nil { + return m.OpReturnData + } + return nil +} + +// * +// Structure representing compiled transaction output +// @used_in TransactionType +type TxOutputBinType struct { + Amount *uint64 `protobuf:"varint,1,req,name=amount" json:"amount,omitempty"` + ScriptPubkey []byte `protobuf:"bytes,2,req,name=script_pubkey,json=scriptPubkey" json:"script_pubkey,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *TxOutputBinType) Reset() { *m = TxOutputBinType{} } +func (m *TxOutputBinType) String() string { return proto.CompactTextString(m) } +func (*TxOutputBinType) ProtoMessage() {} +func (*TxOutputBinType) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } + +func (m *TxOutputBinType) GetAmount() uint64 { + if m != nil && m.Amount != nil { + return *m.Amount + } + return 0 +} + +func (m *TxOutputBinType) GetScriptPubkey() []byte { + if m != nil { + return m.ScriptPubkey + } + return nil +} + +// * +// Structure representing transaction +// @used_in SimpleSignTx +type TransactionType struct { + Version *uint32 `protobuf:"varint,1,opt,name=version" json:"version,omitempty"` + Inputs []*TxInputType `protobuf:"bytes,2,rep,name=inputs" json:"inputs,omitempty"` + BinOutputs []*TxOutputBinType `protobuf:"bytes,3,rep,name=bin_outputs,json=binOutputs" json:"bin_outputs,omitempty"` + Outputs []*TxOutputType `protobuf:"bytes,5,rep,name=outputs" json:"outputs,omitempty"` + LockTime *uint32 `protobuf:"varint,4,opt,name=lock_time,json=lockTime" json:"lock_time,omitempty"` + InputsCnt *uint32 `protobuf:"varint,6,opt,name=inputs_cnt,json=inputsCnt" json:"inputs_cnt,omitempty"` + OutputsCnt *uint32 `protobuf:"varint,7,opt,name=outputs_cnt,json=outputsCnt" json:"outputs_cnt,omitempty"` + ExtraData []byte `protobuf:"bytes,8,opt,name=extra_data,json=extraData" json:"extra_data,omitempty"` + ExtraDataLen *uint32 `protobuf:"varint,9,opt,name=extra_data_len,json=extraDataLen" json:"extra_data_len,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *TransactionType) Reset() { *m = TransactionType{} } +func (m *TransactionType) String() string { return proto.CompactTextString(m) } +func (*TransactionType) ProtoMessage() {} +func (*TransactionType) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } + +func (m *TransactionType) GetVersion() uint32 { + if m != nil && m.Version != nil { + return *m.Version + } + return 0 +} + +func (m *TransactionType) GetInputs() []*TxInputType { + if m != nil { + return m.Inputs + } + return nil +} + +func (m *TransactionType) GetBinOutputs() []*TxOutputBinType { + if m != nil { + return m.BinOutputs + } + return nil +} + +func (m *TransactionType) GetOutputs() []*TxOutputType { + if m != nil { + return m.Outputs + } + return nil +} + +func (m *TransactionType) GetLockTime() uint32 { + if m != nil && m.LockTime != nil { + return *m.LockTime + } + return 0 +} + +func (m *TransactionType) GetInputsCnt() uint32 { + if m != nil && m.InputsCnt != nil { + return *m.InputsCnt + } + return 0 +} + +func (m *TransactionType) GetOutputsCnt() uint32 { + if m != nil && m.OutputsCnt != nil { + return *m.OutputsCnt + } + return 0 +} + +func (m *TransactionType) GetExtraData() []byte { + if m != nil { + return m.ExtraData + } + return nil +} + +func (m *TransactionType) GetExtraDataLen() uint32 { + if m != nil && m.ExtraDataLen != nil { + return *m.ExtraDataLen + } + return 0 +} + +// * +// Structure representing request details +// @used_in TxRequest +type TxRequestDetailsType struct { + RequestIndex *uint32 `protobuf:"varint,1,opt,name=request_index,json=requestIndex" json:"request_index,omitempty"` + TxHash []byte `protobuf:"bytes,2,opt,name=tx_hash,json=txHash" json:"tx_hash,omitempty"` + ExtraDataLen *uint32 `protobuf:"varint,3,opt,name=extra_data_len,json=extraDataLen" json:"extra_data_len,omitempty"` + ExtraDataOffset *uint32 `protobuf:"varint,4,opt,name=extra_data_offset,json=extraDataOffset" json:"extra_data_offset,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *TxRequestDetailsType) Reset() { *m = TxRequestDetailsType{} } +func (m *TxRequestDetailsType) String() string { return proto.CompactTextString(m) } +func (*TxRequestDetailsType) ProtoMessage() {} +func (*TxRequestDetailsType) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } + +func (m *TxRequestDetailsType) GetRequestIndex() uint32 { + if m != nil && m.RequestIndex != nil { + return *m.RequestIndex + } + return 0 +} + +func (m *TxRequestDetailsType) GetTxHash() []byte { + if m != nil { + return m.TxHash + } + return nil +} + +func (m *TxRequestDetailsType) GetExtraDataLen() uint32 { + if m != nil && m.ExtraDataLen != nil { + return *m.ExtraDataLen + } + return 0 +} + +func (m *TxRequestDetailsType) GetExtraDataOffset() uint32 { + if m != nil && m.ExtraDataOffset != nil { + return *m.ExtraDataOffset + } + return 0 +} + +// * +// Structure representing serialized data +// @used_in TxRequest +type TxRequestSerializedType struct { + SignatureIndex *uint32 `protobuf:"varint,1,opt,name=signature_index,json=signatureIndex" json:"signature_index,omitempty"` + Signature []byte `protobuf:"bytes,2,opt,name=signature" json:"signature,omitempty"` + SerializedTx []byte `protobuf:"bytes,3,opt,name=serialized_tx,json=serializedTx" json:"serialized_tx,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *TxRequestSerializedType) Reset() { *m = TxRequestSerializedType{} } +func (m *TxRequestSerializedType) String() string { return proto.CompactTextString(m) } +func (*TxRequestSerializedType) ProtoMessage() {} +func (*TxRequestSerializedType) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } + +func (m *TxRequestSerializedType) GetSignatureIndex() uint32 { + if m != nil && m.SignatureIndex != nil { + return *m.SignatureIndex + } + return 0 +} + +func (m *TxRequestSerializedType) GetSignature() []byte { + if m != nil { + return m.Signature + } + return nil +} + +func (m *TxRequestSerializedType) GetSerializedTx() []byte { + if m != nil { + return m.SerializedTx + } + return nil +} + +// * +// Structure representing identity data +// @used_in IdentityType +type IdentityType struct { + Proto *string `protobuf:"bytes,1,opt,name=proto" json:"proto,omitempty"` + User *string `protobuf:"bytes,2,opt,name=user" json:"user,omitempty"` + Host *string `protobuf:"bytes,3,opt,name=host" json:"host,omitempty"` + Port *string `protobuf:"bytes,4,opt,name=port" json:"port,omitempty"` + Path *string `protobuf:"bytes,5,opt,name=path" json:"path,omitempty"` + Index *uint32 `protobuf:"varint,6,opt,name=index,def=0" json:"index,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *IdentityType) Reset() { *m = IdentityType{} } +func (m *IdentityType) String() string { return proto.CompactTextString(m) } +func (*IdentityType) ProtoMessage() {} +func (*IdentityType) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } + +const Default_IdentityType_Index uint32 = 0 + +func (m *IdentityType) GetProto() string { + if m != nil && m.Proto != nil { + return *m.Proto + } + return "" +} + +func (m *IdentityType) GetUser() string { + if m != nil && m.User != nil { + return *m.User + } + return "" +} + +func (m *IdentityType) GetHost() string { + if m != nil && m.Host != nil { + return *m.Host + } + return "" +} + +func (m *IdentityType) GetPort() string { + if m != nil && m.Port != nil { + return *m.Port + } + return "" +} + +func (m *IdentityType) GetPath() string { + if m != nil && m.Path != nil { + return *m.Path + } + return "" +} + +func (m *IdentityType) GetIndex() uint32 { + if m != nil && m.Index != nil { + return *m.Index + } + return Default_IdentityType_Index +} + +var E_WireIn = &proto.ExtensionDesc{ + ExtendedType: (*google_protobuf.EnumValueOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50002, + Name: "wire_in", + Tag: "varint,50002,opt,name=wire_in,json=wireIn", + Filename: "types.proto", +} + +var E_WireOut = &proto.ExtensionDesc{ + ExtendedType: (*google_protobuf.EnumValueOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50003, + Name: "wire_out", + Tag: "varint,50003,opt,name=wire_out,json=wireOut", + Filename: "types.proto", +} + +var E_WireDebugIn = &proto.ExtensionDesc{ + ExtendedType: (*google_protobuf.EnumValueOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50004, + Name: "wire_debug_in", + Tag: "varint,50004,opt,name=wire_debug_in,json=wireDebugIn", + Filename: "types.proto", +} + +var E_WireDebugOut = &proto.ExtensionDesc{ + ExtendedType: (*google_protobuf.EnumValueOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50005, + Name: "wire_debug_out", + Tag: "varint,50005,opt,name=wire_debug_out,json=wireDebugOut", + Filename: "types.proto", +} + +var E_WireTiny = &proto.ExtensionDesc{ + ExtendedType: (*google_protobuf.EnumValueOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50006, + Name: "wire_tiny", + Tag: "varint,50006,opt,name=wire_tiny,json=wireTiny", + Filename: "types.proto", +} + +var E_WireBootloader = &proto.ExtensionDesc{ + ExtendedType: (*google_protobuf.EnumValueOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50007, + Name: "wire_bootloader", + Tag: "varint,50007,opt,name=wire_bootloader,json=wireBootloader", + Filename: "types.proto", +} + +func init() { + proto.RegisterType((*HDNodeType)(nil), "HDNodeType") + proto.RegisterType((*HDNodePathType)(nil), "HDNodePathType") + proto.RegisterType((*CoinType)(nil), "CoinType") + proto.RegisterType((*MultisigRedeemScriptType)(nil), "MultisigRedeemScriptType") + proto.RegisterType((*TxInputType)(nil), "TxInputType") + proto.RegisterType((*TxOutputType)(nil), "TxOutputType") + proto.RegisterType((*TxOutputBinType)(nil), "TxOutputBinType") + proto.RegisterType((*TransactionType)(nil), "TransactionType") + proto.RegisterType((*TxRequestDetailsType)(nil), "TxRequestDetailsType") + proto.RegisterType((*TxRequestSerializedType)(nil), "TxRequestSerializedType") + proto.RegisterType((*IdentityType)(nil), "IdentityType") + proto.RegisterEnum("FailureType", FailureType_name, FailureType_value) + proto.RegisterEnum("OutputScriptType", OutputScriptType_name, OutputScriptType_value) + proto.RegisterEnum("InputScriptType", InputScriptType_name, InputScriptType_value) + proto.RegisterEnum("RequestType", RequestType_name, RequestType_value) + proto.RegisterEnum("ButtonRequestType", ButtonRequestType_name, ButtonRequestType_value) + proto.RegisterEnum("PinMatrixRequestType", PinMatrixRequestType_name, PinMatrixRequestType_value) + proto.RegisterEnum("RecoveryDeviceType", RecoveryDeviceType_name, RecoveryDeviceType_value) + proto.RegisterEnum("WordRequestType", WordRequestType_name, WordRequestType_value) + proto.RegisterExtension(E_WireIn) + proto.RegisterExtension(E_WireOut) + proto.RegisterExtension(E_WireDebugIn) + proto.RegisterExtension(E_WireDebugOut) + proto.RegisterExtension(E_WireTiny) + proto.RegisterExtension(E_WireBootloader) +} + +func init() { proto.RegisterFile("types.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 1899 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0xdb, 0x72, 0x1a, 0xc9, + 0x19, 0xf6, 0x00, 0x92, 0xe0, 0x07, 0xc4, 0xa8, 0x7d, 0xd0, 0x78, 0x6d, 0xaf, 0x31, 0x76, 0x62, + 0x45, 0x55, 0x61, 0x77, 0xc9, 0x5a, 0x8e, 0x55, 0xa9, 0x24, 0x3a, 0xa0, 0x15, 0x65, 0x0b, 0x51, + 0xc3, 0x28, 0x56, 0x72, 0x33, 0x35, 0xcc, 0xb4, 0xa0, 0x4b, 0x43, 0x37, 0xe9, 0xe9, 0x91, 0xd1, + 0xde, 0xe4, 0x2a, 0xc9, 0x55, 0x5e, 0x23, 0x6f, 0x91, 0xaa, 0xbc, 0x41, 0xaa, 0x36, 0xa7, 0xcb, + 0xbc, 0x41, 0xae, 0xf2, 0x00, 0xa9, 0x3e, 0x0c, 0x02, 0xc9, 0xde, 0xd2, 0x1d, 0xfd, 0x7d, 0xff, + 0xf9, 0xd0, 0x3d, 0x40, 0x59, 0x5c, 0x4e, 0x70, 0xd2, 0x9c, 0x70, 0x26, 0xd8, 0x67, 0xf5, 0x21, + 0x63, 0xc3, 0x18, 0x7f, 0xa1, 0x4e, 0x83, 0xf4, 0xec, 0x8b, 0x08, 0x27, 0x21, 0x27, 0x13, 0xc1, + 0xb8, 0x96, 0x68, 0xfc, 0xd5, 0x02, 0x38, 0xdc, 0xef, 0xb2, 0x08, 0x7b, 0x97, 0x13, 0x8c, 0xee, + 0xc1, 0x52, 0x84, 0x27, 0x62, 0xe4, 0x58, 0xf5, 0xdc, 0x46, 0xd5, 0xd5, 0x07, 0x54, 0x87, 0xf2, + 0x19, 0xa1, 0x43, 0xcc, 0x27, 0x9c, 0x50, 0xe1, 0xe4, 0x14, 0x37, 0x0f, 0xa1, 0x47, 0x50, 0x0a, + 0x47, 0x24, 0x8e, 0x7c, 0x9a, 0x8e, 0x9d, 0xbc, 0xe2, 0x8b, 0x0a, 0xe8, 0xa6, 0x63, 0xf4, 0x04, + 0x20, 0x1c, 0x05, 0x84, 0xfa, 0x21, 0x8b, 0xb0, 0x53, 0xa8, 0xe7, 0x36, 0x2a, 0x6e, 0x49, 0x21, + 0x7b, 0x2c, 0xc2, 0xe8, 0x29, 0x94, 0x27, 0x9c, 0x5c, 0x04, 0x02, 0xfb, 0xe7, 0xf8, 0xd2, 0x59, + 0xaa, 0x5b, 0x1b, 0x15, 0x17, 0x0c, 0xf4, 0x16, 0x5f, 0x4a, 0xfd, 0x49, 0x3a, 0x88, 0x49, 0xa8, + 0xf8, 0x65, 0xc5, 0x97, 0x34, 0xf2, 0x16, 0x5f, 0x36, 0xba, 0xb0, 0xaa, 0x33, 0xe8, 0x05, 0x62, + 0xa4, 0xb2, 0x78, 0x0a, 0x05, 0x2a, 0x5d, 0xc9, 0x24, 0xca, 0xad, 0x72, 0xf3, 0x2a, 0x41, 0x57, + 0x11, 0x32, 0xdc, 0x20, 0x8a, 0x38, 0x4e, 0x12, 0x9f, 0x3a, 0xb9, 0x7a, 0x5e, 0x86, 0x6b, 0x80, + 0x6e, 0xe3, 0x7f, 0x39, 0x28, 0xee, 0x31, 0x42, 0x95, 0x29, 0x99, 0x18, 0x23, 0xd4, 0xa7, 0xc1, + 0x58, 0xda, 0xb3, 0x36, 0x4a, 0x6e, 0x51, 0x02, 0xdd, 0x60, 0x8c, 0xd1, 0x73, 0xa8, 0x2a, 0x32, + 0x19, 0x31, 0x2e, 0xc2, 0x54, 0x56, 0x46, 0x0a, 0x54, 0x24, 0xd8, 0x37, 0x18, 0x7a, 0x01, 0x95, + 0xcc, 0x97, 0x6c, 0x8d, 0x93, 0xaf, 0x5b, 0x1b, 0xd5, 0x6d, 0xeb, 0x4b, 0xb7, 0x6c, 0xe0, 0xcc, + 0xcf, 0x38, 0x98, 0x9e, 0x61, 0xec, 0x9f, 0x0f, 0x9c, 0x42, 0xdd, 0xda, 0x28, 0xb8, 0x45, 0x0d, + 0xbc, 0x1d, 0xa0, 0x1f, 0xc3, 0xda, 0xbc, 0x09, 0x7f, 0xd2, 0x4a, 0x46, 0xaa, 0x4e, 0xd5, 0x6d, + 0xeb, 0x95, 0x5b, 0x9b, 0xb3, 0xd3, 0x6b, 0x25, 0x23, 0xd4, 0x82, 0xfb, 0x09, 0x19, 0x52, 0x1c, + 0xf9, 0x63, 0x9c, 0x24, 0xc1, 0x10, 0xfb, 0x23, 0x1c, 0x44, 0x98, 0x3b, 0x45, 0x15, 0xde, 0x5d, + 0x4d, 0x1e, 0x69, 0xee, 0x50, 0x51, 0xe8, 0x25, 0xc0, 0x74, 0x92, 0x0e, 0xfc, 0x71, 0x30, 0x24, + 0xa1, 0x53, 0x52, 0xb6, 0x8b, 0xaf, 0xb7, 0xbe, 0xdc, 0x7a, 0xfd, 0x93, 0x57, 0x3f, 0x75, 0x4b, + 0x92, 0x3b, 0x92, 0x94, 0x16, 0xe4, 0x17, 0x46, 0x10, 0xae, 0x04, 0xb7, 0x5a, 0xaf, 0xb7, 0xa4, + 0x20, 0xbf, 0xd0, 0x82, 0x0f, 0x60, 0x39, 0xc1, 0xc3, 0x0f, 0x44, 0x38, 0xe5, 0xba, 0xb5, 0x51, + 0x74, 0xcd, 0x49, 0xe2, 0x67, 0x8c, 0x9f, 0x93, 0xc8, 0xa9, 0x48, 0x65, 0xd7, 0x9c, 0x1a, 0x09, + 0x38, 0x47, 0x69, 0x2c, 0x48, 0x42, 0x86, 0x2e, 0x8e, 0x30, 0x1e, 0xf7, 0xd5, 0xa4, 0xaa, 0xea, + 0xfc, 0x08, 0x56, 0x26, 0xe9, 0xe0, 0x1c, 0x5f, 0x26, 0x8e, 0x55, 0xcf, 0x6f, 0x94, 0x5b, 0xb5, + 0xe6, 0x62, 0xcb, 0xdd, 0x8c, 0x47, 0x9f, 0x03, 0xc8, 0xfc, 0x02, 0x91, 0x72, 0x9c, 0xa8, 0xde, + 0x56, 0xdc, 0x39, 0x04, 0x55, 0xc0, 0x1a, 0xeb, 0x1e, 0xb8, 0xd6, 0xb8, 0xf1, 0x97, 0x1c, 0x94, + 0xbd, 0x69, 0x87, 0x4e, 0x52, 0x91, 0xb5, 0xe1, 0x6a, 0x30, 0xac, 0xc5, 0xc1, 0x90, 0xe4, 0x84, + 0xe3, 0x0b, 0x7f, 0x14, 0x24, 0x23, 0xb5, 0x04, 0x15, 0xb7, 0x28, 0x81, 0xc3, 0x20, 0x19, 0xa9, + 0x21, 0x95, 0x24, 0xa1, 0x11, 0x9e, 0x9a, 0x15, 0x50, 0xe2, 0x1d, 0x09, 0x48, 0x5a, 0x6f, 0x9e, + 0x9f, 0x90, 0xa1, 0x6a, 0x70, 0xc5, 0x2d, 0x69, 0xa4, 0x4f, 0x86, 0xe8, 0x87, 0x50, 0x4c, 0xf0, + 0x6f, 0x53, 0x4c, 0x43, 0x6c, 0x1a, 0x0b, 0x5f, 0xb7, 0xde, 0x7c, 0xfd, 0x66, 0xeb, 0x75, 0xeb, + 0xcd, 0x2b, 0x77, 0xc6, 0xa1, 0x5f, 0x40, 0xd9, 0x98, 0x51, 0xb3, 0x24, 0x77, 0x61, 0xb5, 0x65, + 0x37, 0x55, 0x02, 0x57, 0xf5, 0xda, 0xae, 0xf4, 0x7b, 0xed, 0xee, 0xfe, 0xce, 0xfe, 0xbe, 0xdb, + 0xee, 0xf7, 0x5d, 0xe3, 0x59, 0x25, 0xf8, 0x0a, 0x8a, 0x63, 0x53, 0x65, 0x67, 0xa5, 0x6e, 0x6d, + 0x94, 0x5b, 0x0f, 0x9b, 0x9f, 0x2a, 0xbb, 0x3b, 0x13, 0x95, 0x4d, 0x0b, 0xc6, 0x2c, 0xa5, 0x42, + 0xcd, 0x50, 0xc1, 0x35, 0xa7, 0xc6, 0x7f, 0x2d, 0xa8, 0x78, 0xd3, 0xe3, 0x54, 0x64, 0x05, 0x74, + 0x60, 0xc5, 0xd4, 0xcb, 0x6c, 0x4b, 0x76, 0xfc, 0xde, 0x9d, 0x9b, 0xb3, 0x2f, 0x2b, 0x37, 0xb3, + 0x8f, 0x5a, 0x8b, 0xf9, 0xca, 0xbb, 0x63, 0xb5, 0xb5, 0xd6, 0xd4, 0x0e, 0xe7, 0x22, 0xfd, 0x54, + 0x8a, 0x4b, 0xb7, 0x4f, 0xf1, 0x05, 0xac, 0xb2, 0x89, 0xcf, 0xb1, 0x48, 0x39, 0xf5, 0xa3, 0x40, + 0x04, 0xe6, 0xa6, 0xa9, 0xb0, 0x89, 0xab, 0xc0, 0xfd, 0x40, 0x04, 0x8d, 0x2e, 0xd4, 0xb2, 0x7c, + 0x77, 0xcd, 0x15, 0x71, 0x15, 0xbb, 0xb5, 0x10, 0xfb, 0x73, 0xa8, 0x9a, 0xd8, 0xf5, 0x6c, 0x9a, + 0x91, 0xa9, 0x68, 0xb0, 0xa7, 0xb0, 0xc6, 0xdf, 0x72, 0x50, 0xf3, 0x78, 0x40, 0x93, 0x20, 0x14, + 0x84, 0xd1, 0xac, 0x86, 0x17, 0x98, 0x27, 0x84, 0x51, 0x55, 0xc3, 0xaa, 0x9b, 0x1d, 0xd1, 0x0b, + 0x58, 0x26, 0xb2, 0xd5, 0x7a, 0xb0, 0xcb, 0xad, 0x4a, 0x73, 0x6e, 0x78, 0x5d, 0xc3, 0xa1, 0xaf, + 0xa0, 0x3c, 0x20, 0xd4, 0x67, 0x2a, 0xca, 0xc4, 0xc9, 0x2b, 0x51, 0xbb, 0x79, 0x2d, 0x6e, 0x17, + 0x06, 0x84, 0x6a, 0x24, 0x41, 0x2f, 0x61, 0x25, 0x13, 0x5f, 0x52, 0xe2, 0xd5, 0xe6, 0x7c, 0x5b, + 0xdd, 0x8c, 0x95, 0x5d, 0x8c, 0x59, 0x78, 0xee, 0x0b, 0x32, 0xc6, 0x6a, 0x8c, 0xab, 0x6e, 0x51, + 0x02, 0x1e, 0x19, 0x63, 0x39, 0xe4, 0x3a, 0x04, 0x3f, 0xa4, 0x42, 0x95, 0xaf, 0xea, 0x96, 0x34, + 0xb2, 0x47, 0x85, 0xbc, 0xe8, 0x8d, 0x19, 0xc5, 0xaf, 0x28, 0x1e, 0x0c, 0x24, 0x05, 0x9e, 0x00, + 0xe0, 0xa9, 0xe0, 0x81, 0x2e, 0x7f, 0x51, 0x2f, 0x89, 0x42, 0x64, 0xed, 0x65, 0x87, 0xae, 0x68, + 0x3f, 0xc6, 0x54, 0xdf, 0x53, 0x6e, 0x65, 0x26, 0xf2, 0x0e, 0xd3, 0xc6, 0x9f, 0x2d, 0xb8, 0xe7, + 0x4d, 0x5d, 0xb9, 0x31, 0x89, 0xd8, 0xc7, 0x22, 0x20, 0xb1, 0xbe, 0x62, 0x9f, 0x43, 0x95, 0x6b, + 0xd4, 0x2c, 0xa9, 0x2e, 0x6e, 0xc5, 0x80, 0x7a, 0x4f, 0xd7, 0x61, 0x45, 0x4c, 0xb3, 0x0d, 0x97, + 0xfe, 0x97, 0xc5, 0x54, 0xed, 0xf7, 0x4d, 0xe7, 0xf9, 0x9b, 0xce, 0xd1, 0x26, 0xac, 0xcd, 0x49, + 0xb1, 0xb3, 0xb3, 0x04, 0x0b, 0x53, 0xa6, 0xda, 0x4c, 0xf0, 0x58, 0xc1, 0x8d, 0xdf, 0x5b, 0xb0, + 0x3e, 0x0b, 0xb4, 0x8f, 0x39, 0x09, 0x62, 0xf2, 0x2d, 0x8e, 0x54, 0xac, 0x2f, 0xa1, 0x36, 0xbb, + 0xb3, 0x16, 0xa2, 0x5d, 0x9d, 0xc1, 0x3a, 0xde, 0xc7, 0x50, 0x9a, 0x21, 0x26, 0xe2, 0x2b, 0x40, + 0x8d, 0xe0, 0xcc, 0xb0, 0x2f, 0xa6, 0x2a, 0x66, 0x39, 0x82, 0x57, 0xde, 0xa6, 0x8d, 0x3f, 0x59, + 0x50, 0xe9, 0x44, 0x98, 0x0a, 0x22, 0x2e, 0xb3, 0x8f, 0x00, 0xf5, 0x71, 0x60, 0x36, 0x58, 0x1f, + 0x10, 0x82, 0x42, 0x9a, 0x60, 0x6e, 0xde, 0x38, 0xf5, 0x5b, 0x62, 0x23, 0x96, 0x08, 0x65, 0xb6, + 0xe4, 0xaa, 0xdf, 0x12, 0x9b, 0x30, 0xae, 0xb3, 0x2e, 0xb9, 0xea, 0xb7, 0xc2, 0x02, 0xa1, 0xdf, + 0x2c, 0x89, 0x05, 0x62, 0x84, 0xd6, 0x61, 0x49, 0x27, 0xb6, 0x9c, 0x3d, 0x88, 0xfa, 0xbc, 0xf9, + 0x5d, 0x0e, 0xca, 0x07, 0x01, 0x89, 0x53, 0xae, 0xbf, 0x49, 0x9e, 0xc0, 0x43, 0x73, 0xf4, 0x4f, + 0x28, 0x9e, 0x4e, 0x70, 0x28, 0x66, 0xaf, 0x97, 0x6d, 0xa1, 0xcf, 0xe0, 0x41, 0x46, 0xef, 0xa6, + 0x42, 0x30, 0xda, 0x36, 0x22, 0x76, 0x0e, 0xdd, 0x87, 0xb5, 0x8c, 0x93, 0x85, 0x6f, 0x73, 0xce, + 0xb8, 0x9d, 0x47, 0x8f, 0x60, 0x3d, 0x83, 0x77, 0xd4, 0xda, 0xed, 0x05, 0x34, 0xc4, 0x71, 0x8c, + 0x23, 0xbb, 0x80, 0xd6, 0xe1, 0x6e, 0x46, 0xf6, 0xc8, 0x95, 0xb1, 0x25, 0xe4, 0xc0, 0xbd, 0x39, + 0xe2, 0x4a, 0x65, 0x19, 0x3d, 0x00, 0x34, 0xc7, 0x74, 0xe8, 0x45, 0x10, 0x93, 0xc8, 0x5e, 0x41, + 0x8f, 0xc1, 0xc9, 0x70, 0x03, 0xf6, 0xb3, 0xd6, 0xd8, 0xc5, 0x05, 0x7b, 0x9c, 0x85, 0x38, 0x49, + 0x74, 0x7c, 0xa5, 0xf9, 0x94, 0xba, 0x4c, 0xb4, 0x29, 0x4b, 0x87, 0xa3, 0x83, 0x94, 0x46, 0x89, + 0x0d, 0xd7, 0xb8, 0x0e, 0x25, 0xc2, 0x74, 0xd2, 0x2e, 0xa3, 0x87, 0x70, 0x3f, 0xe3, 0x0e, 0x08, + 0x1f, 0x7f, 0x08, 0x38, 0xd6, 0x26, 0xc3, 0xcd, 0x3f, 0x5a, 0x60, 0x5f, 0xbf, 0x35, 0x91, 0x0d, + 0x95, 0xde, 0xce, 0xaf, 0xbd, 0x63, 0xf3, 0x50, 0xd8, 0x77, 0xd0, 0x5d, 0xa8, 0x29, 0xa4, 0xbf, + 0xe7, 0x76, 0x7a, 0xde, 0xe1, 0x4e, 0xff, 0xd0, 0xb6, 0xd0, 0x1a, 0x54, 0x15, 0x78, 0x74, 0xf2, + 0xce, 0xeb, 0xf4, 0x3b, 0xdf, 0xd8, 0xb9, 0x19, 0x74, 0xdc, 0x73, 0xdb, 0xde, 0x89, 0xdb, 0xb5, + 0xf3, 0x33, 0x63, 0xef, 0x3b, 0x5e, 0x57, 0x1a, 0x2b, 0xa0, 0x7b, 0x60, 0x2b, 0xa4, 0xd7, 0xea, + 0x1f, 0x66, 0xe8, 0xd2, 0x66, 0x0c, 0xb5, 0x6b, 0xcf, 0x95, 0x54, 0x9d, 0x7f, 0xb0, 0xec, 0x3b, + 0xd2, 0xbe, 0x42, 0x66, 0x2e, 0x2d, 0x54, 0x81, 0x62, 0xfb, 0xd4, 0x6b, 0xbb, 0xdd, 0x9d, 0x77, + 0x76, 0x6e, 0xa6, 0x92, 0xd9, 0xcd, 0x4b, 0x6f, 0x0a, 0x99, 0xf7, 0x56, 0xd8, 0x3c, 0x81, 0xb2, + 0xd9, 0x30, 0xe5, 0xa9, 0x0c, 0x2b, 0xde, 0x69, 0xa7, 0xdb, 0x3b, 0xf1, 0xec, 0x3b, 0xd2, 0xa2, + 0x77, 0x7a, 0x7c, 0xe2, 0xc9, 0x93, 0x85, 0x00, 0x96, 0xbd, 0xd3, 0xa3, 0xb6, 0xb7, 0x63, 0xe7, + 0xd0, 0x2a, 0x80, 0x77, 0x7a, 0xd0, 0xe9, 0x76, 0xfa, 0x87, 0xed, 0x7d, 0x3b, 0x8f, 0x6a, 0x50, + 0xf6, 0x4e, 0xdb, 0xa7, 0x9e, 0xbb, 0xb3, 0xbf, 0xe3, 0xed, 0xd8, 0x85, 0xcd, 0xff, 0xe4, 0x60, + 0x4d, 0x4f, 0xdb, 0xbc, 0xf5, 0x75, 0xb8, 0xbb, 0x00, 0xfa, 0xc7, 0x62, 0x84, 0xb9, 0x6d, 0xa1, + 0x06, 0x7c, 0xbe, 0x48, 0x1c, 0x60, 0x7c, 0x7c, 0x81, 0xb9, 0x37, 0xe2, 0x38, 0x19, 0xb1, 0x58, + 0xce, 0xea, 0x53, 0x78, 0xb4, 0x28, 0xb3, 0xc7, 0xe8, 0x19, 0xe1, 0x63, 0xdd, 0x35, 0x3b, 0x2f, + 0xf7, 0x60, 0x51, 0xc0, 0xc5, 0x09, 0x16, 0xfb, 0xf8, 0x82, 0x84, 0xd8, 0x2e, 0xdc, 0xa4, 0x8d, + 0xfe, 0x7b, 0xc6, 0xe5, 0xf4, 0x3e, 0x06, 0x67, 0x91, 0x7e, 0x4f, 0x26, 0xd8, 0x28, 0x2f, 0xdf, + 0x54, 0xee, 0x71, 0x26, 0x70, 0x28, 0xf6, 0x82, 0x38, 0xb6, 0x57, 0xe4, 0xa8, 0x2e, 0xd2, 0x72, + 0x8e, 0xbd, 0xa9, 0x5d, 0xbc, 0x19, 0x75, 0x36, 0x78, 0x7b, 0x23, 0x1c, 0x9e, 0xdb, 0x25, 0x39, + 0x93, 0x8b, 0x02, 0x3b, 0xfa, 0xcd, 0xb7, 0x41, 0xae, 0xe1, 0x35, 0xa7, 0xd9, 0x37, 0xbd, 0x5d, + 0xde, 0xfc, 0x1d, 0xdc, 0xeb, 0x11, 0x7a, 0x14, 0x08, 0x4e, 0xa6, 0xf3, 0x35, 0xae, 0xc3, 0xe3, + 0x8f, 0xe1, 0xfe, 0x5e, 0xca, 0x39, 0xa6, 0xc2, 0xb6, 0xd0, 0x33, 0x78, 0xf2, 0x51, 0x89, 0x2e, + 0xfe, 0x70, 0x40, 0x78, 0x22, 0xec, 0x9c, 0xec, 0xc7, 0xa7, 0x44, 0xfa, 0x38, 0x64, 0x34, 0xb2, + 0xf3, 0x9b, 0xbf, 0x01, 0xe4, 0xe2, 0x90, 0x5d, 0x60, 0x7e, 0xa9, 0xcb, 0xa4, 0xdc, 0xff, 0x00, + 0x9e, 0xdd, 0x44, 0xfd, 0x7e, 0xc8, 0x83, 0xf1, 0x20, 0xc6, 0x91, 0x2c, 0x76, 0x62, 0xdf, 0x91, + 0xf5, 0xfc, 0x88, 0x98, 0x76, 0x68, 0x5b, 0x9b, 0x67, 0x50, 0x93, 0x92, 0xf3, 0x79, 0x3d, 0x84, + 0xfb, 0xd7, 0x20, 0xbf, 0x17, 0x07, 0x84, 0xda, 0x77, 0x64, 0x9d, 0xae, 0x53, 0xda, 0xd2, 0x1b, + 0xdb, 0xfa, 0x34, 0xb9, 0x65, 0xe7, 0xb6, 0x7f, 0x06, 0x2b, 0x1f, 0x88, 0x7a, 0x41, 0xd0, 0xb3, + 0xa6, 0xfe, 0x2f, 0xd8, 0xcc, 0xfe, 0x0b, 0x36, 0xdb, 0x34, 0x1d, 0xff, 0x2a, 0x88, 0x53, 0x7c, + 0x3c, 0x91, 0x77, 0x60, 0xe2, 0x7c, 0xf7, 0x87, 0xbc, 0xfe, 0x52, 0x97, 0x3a, 0x1d, 0xba, 0xfd, + 0x73, 0x28, 0x2a, 0x6d, 0x96, 0x8a, 0xdb, 0xa8, 0xff, 0xdd, 0xa8, 0x2b, 0x97, 0xc7, 0xa9, 0xd8, + 0xfe, 0x06, 0xaa, 0x4a, 0x3f, 0xc2, 0x83, 0x74, 0x78, 0xcb, 0x18, 0xfe, 0x61, 0x8c, 0x94, 0xa5, + 0xe6, 0xbe, 0x54, 0xec, 0xd0, 0xed, 0x0e, 0xac, 0xce, 0x19, 0xba, 0x65, 0x38, 0xff, 0x34, 0x96, + 0x2a, 0x33, 0x4b, 0x32, 0xa6, 0x5f, 0x42, 0x49, 0x99, 0x12, 0x84, 0x5e, 0xde, 0xc6, 0xca, 0xbf, + 0x8c, 0x15, 0x55, 0x09, 0x8f, 0xd0, 0xcb, 0xed, 0x77, 0x50, 0x53, 0x16, 0x06, 0x8c, 0x89, 0x98, + 0xa9, 0x3f, 0x4f, 0xb7, 0xb0, 0xf3, 0x6f, 0x63, 0x47, 0x25, 0xb2, 0x3b, 0x53, 0xdd, 0xfd, 0x0a, + 0x9e, 0x87, 0x6c, 0xdc, 0x4c, 0x02, 0xc1, 0x92, 0x11, 0x89, 0x83, 0x41, 0xd2, 0x14, 0x1c, 0x7f, + 0xcb, 0x78, 0x33, 0x26, 0x83, 0x99, 0xbd, 0x5d, 0xf0, 0x14, 0x28, 0xdb, 0xfb, 0xff, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x70, 0x88, 0xcd, 0x71, 0xe2, 0x0f, 0x00, 0x00, +} diff --git a/accounts/usbwallet/trezor/types.proto b/accounts/usbwallet/trezor/types.proto new file mode 100644 index 000000000..acbe79e3f --- /dev/null +++ b/accounts/usbwallet/trezor/types.proto @@ -0,0 +1,278 @@ +// This file originates from the SatoshiLabs Trezor `common` repository at: +// https://github.com/trezor/trezor-common/blob/master/protob/types.proto +// dated 28.07.2017, commit dd8ec3231fb5f7992360aff9bdfe30bb58130f4b. + +syntax = "proto2"; + +/** + * Types for TREZOR communication + * + * @author Marek Palatinus + * @version 1.2 + */ + +// Sugar for easier handling in Java +option java_package = "com.satoshilabs.trezor.lib.protobuf"; +option java_outer_classname = "TrezorType"; + +import "google/protobuf/descriptor.proto"; + +/** + * Options for specifying message direction and type of wire (normal/debug) + */ +extend google.protobuf.EnumValueOptions { + optional bool wire_in = 50002; // message can be transmitted via wire from PC to TREZOR + optional bool wire_out = 50003; // message can be transmitted via wire from TREZOR to PC + optional bool wire_debug_in = 50004; // message can be transmitted via debug wire from PC to TREZOR + optional bool wire_debug_out = 50005; // message can be transmitted via debug wire from TREZOR to PC + optional bool wire_tiny = 50006; // message is handled by TREZOR when the USB stack is in tiny mode + optional bool wire_bootloader = 50007; // message is only handled by TREZOR Bootloader +} + +/** + * Type of failures returned by Failure message + * @used_in Failure + */ +enum FailureType { + Failure_UnexpectedMessage = 1; + Failure_ButtonExpected = 2; + Failure_DataError = 3; + Failure_ActionCancelled = 4; + Failure_PinExpected = 5; + Failure_PinCancelled = 6; + Failure_PinInvalid = 7; + Failure_InvalidSignature = 8; + Failure_ProcessError = 9; + Failure_NotEnoughFunds = 10; + Failure_NotInitialized = 11; + Failure_FirmwareError = 99; +} + +/** + * Type of script which will be used for transaction output + * @used_in TxOutputType + */ +enum OutputScriptType { + PAYTOADDRESS = 0; // used for all addresses (bitcoin, p2sh, witness) + PAYTOSCRIPTHASH = 1; // p2sh address (deprecated; use PAYTOADDRESS) + PAYTOMULTISIG = 2; // only for change output + PAYTOOPRETURN = 3; // op_return + PAYTOWITNESS = 4; // only for change output + PAYTOP2SHWITNESS = 5; // only for change output +} + +/** + * Type of script which will be used for transaction output + * @used_in TxInputType + */ +enum InputScriptType { + SPENDADDRESS = 0; // standard p2pkh address + SPENDMULTISIG = 1; // p2sh multisig address + EXTERNAL = 2; // reserved for external inputs (coinjoin) + SPENDWITNESS = 3; // native segwit + SPENDP2SHWITNESS = 4; // segwit over p2sh (backward compatible) +} + +/** + * Type of information required by transaction signing process + * @used_in TxRequest + */ +enum RequestType { + TXINPUT = 0; + TXOUTPUT = 1; + TXMETA = 2; + TXFINISHED = 3; + TXEXTRADATA = 4; +} + +/** + * Type of button request + * @used_in ButtonRequest + */ +enum ButtonRequestType { + ButtonRequest_Other = 1; + ButtonRequest_FeeOverThreshold = 2; + ButtonRequest_ConfirmOutput = 3; + ButtonRequest_ResetDevice = 4; + ButtonRequest_ConfirmWord = 5; + ButtonRequest_WipeDevice = 6; + ButtonRequest_ProtectCall = 7; + ButtonRequest_SignTx = 8; + ButtonRequest_FirmwareCheck = 9; + ButtonRequest_Address = 10; + ButtonRequest_PublicKey = 11; +} + +/** + * Type of PIN request + * @used_in PinMatrixRequest + */ +enum PinMatrixRequestType { + PinMatrixRequestType_Current = 1; + PinMatrixRequestType_NewFirst = 2; + PinMatrixRequestType_NewSecond = 3; +} + +/** + * Type of recovery procedure. These should be used as bitmask, e.g., + * `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix` + * listing every method supported by the host computer. + * + * Note that ScrambledWords must be supported by every implementation + * for backward compatibility; there is no way to not support it. + * + * @used_in RecoveryDevice + */ +enum RecoveryDeviceType { + // use powers of two when extending this field + RecoveryDeviceType_ScrambledWords = 0; // words in scrambled order + RecoveryDeviceType_Matrix = 1; // matrix recovery type +} + +/** + * Type of Recovery Word request + * @used_in WordRequest + */ +enum WordRequestType { + WordRequestType_Plain = 0; + WordRequestType_Matrix9 = 1; + WordRequestType_Matrix6 = 2; +} + +/** + * Structure representing BIP32 (hierarchical deterministic) node + * Used for imports of private key into the device and exporting public key out of device + * @used_in PublicKey + * @used_in LoadDevice + * @used_in DebugLinkState + * @used_in Storage + */ +message HDNodeType { + required uint32 depth = 1; + required uint32 fingerprint = 2; + required uint32 child_num = 3; + required bytes chain_code = 4; + optional bytes private_key = 5; + optional bytes public_key = 6; +} + +message HDNodePathType { + required HDNodeType node = 1; // BIP-32 node in deserialized form + repeated uint32 address_n = 2; // BIP-32 path to derive the key from node +} + +/** + * Structure representing Coin + * @used_in Features + */ +message CoinType { + optional string coin_name = 1; + optional string coin_shortcut = 2; + optional uint32 address_type = 3 [default=0]; + optional uint64 maxfee_kb = 4; + optional uint32 address_type_p2sh = 5 [default=5]; + optional string signed_message_header = 8; + optional uint32 xpub_magic = 9 [default=76067358]; // default=0x0488b21e + optional uint32 xprv_magic = 10 [default=76066276]; // default=0x0488ade4 + optional bool segwit = 11; + optional uint32 forkid = 12; +} + +/** + * Type of redeem script used in input + * @used_in TxInputType + */ +message MultisigRedeemScriptType { + repeated HDNodePathType pubkeys = 1; // pubkeys from multisig address (sorted lexicographically) + repeated bytes signatures = 2; // existing signatures for partially signed input + optional uint32 m = 3; // "m" from n, how many valid signatures is necessary for spending +} + +/** + * Structure representing transaction input + * @used_in SimpleSignTx + * @used_in TransactionType + */ +message TxInputType { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + required bytes prev_hash = 2; // hash of previous transaction output to spend by this input + required uint32 prev_index = 3; // index of previous output to spend + optional bytes script_sig = 4; // script signature, unset for tx to sign + optional uint32 sequence = 5 [default=4294967295]; // sequence (default=0xffffffff) + optional InputScriptType script_type = 6 [default=SPENDADDRESS]; // defines template of input script + optional MultisigRedeemScriptType multisig = 7; // Filled if input is going to spend multisig tx + optional uint64 amount = 8; // amount of previous transaction output (for segwit only) +} + +/** + * Structure representing transaction output + * @used_in SimpleSignTx + * @used_in TransactionType + */ +message TxOutputType { + optional string address = 1; // target coin address in Base58 encoding + repeated uint32 address_n = 2; // BIP-32 path to derive the key from master node; has higher priority than "address" + required uint64 amount = 3; // amount to spend in satoshis + required OutputScriptType script_type = 4; // output script type + optional MultisigRedeemScriptType multisig = 5; // defines multisig address; script_type must be PAYTOMULTISIG + optional bytes op_return_data = 6; // defines op_return data; script_type must be PAYTOOPRETURN, amount must be 0 +} + +/** + * Structure representing compiled transaction output + * @used_in TransactionType + */ +message TxOutputBinType { + required uint64 amount = 1; + required bytes script_pubkey = 2; +} + +/** + * Structure representing transaction + * @used_in SimpleSignTx + */ +message TransactionType { + optional uint32 version = 1; + repeated TxInputType inputs = 2; + repeated TxOutputBinType bin_outputs = 3; + repeated TxOutputType outputs = 5; + optional uint32 lock_time = 4; + optional uint32 inputs_cnt = 6; + optional uint32 outputs_cnt = 7; + optional bytes extra_data = 8; + optional uint32 extra_data_len = 9; +} + +/** + * Structure representing request details + * @used_in TxRequest + */ +message TxRequestDetailsType { + optional uint32 request_index = 1; // device expects TxAck message from the computer + optional bytes tx_hash = 2; // tx_hash of requested transaction + optional uint32 extra_data_len = 3; // length of requested extra data + optional uint32 extra_data_offset = 4; // offset of requested extra data +} + +/** + * Structure representing serialized data + * @used_in TxRequest + */ +message TxRequestSerializedType { + optional uint32 signature_index = 1; // 'signature' field contains signed input of this index + optional bytes signature = 2; // signature of the signature_index input + optional bytes serialized_tx = 3; // part of serialized and signed transaction +} + +/** + * Structure representing identity data + * @used_in IdentityType + */ +message IdentityType { + optional string proto = 1; // proto part of URI + optional string user = 2; // user part of URI + optional string host = 3; // host part of URI + optional string port = 4; // port part of URI + optional string path = 5; // path part of URI + optional uint32 index = 6 [default=0]; // identity index +} diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go new file mode 100644 index 000000000..7ff9c5372 --- /dev/null +++ b/accounts/usbwallet/wallet.go @@ -0,0 +1,594 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package usbwallet implements support for USB hardware wallets. +package usbwallet + +import ( + "context" + "fmt" + "io" + "math/big" + "sync" + "time" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/karalabe/hid" +) + +// Maximum time between wallet health checks to detect USB unplugs. +const heartbeatCycle = time.Second + +// Minimum time to wait between self derivation attempts, even it the user is +// requesting accounts like crazy. +const selfDeriveThrottling = time.Second + +// driver defines the vendor specific functionality hardware wallets instances +// must implement to allow using them with the wallet lifecycle management. +type driver interface { + // Status returns a textual status to aid the user in the current state of the + // wallet. It also returns an error indicating any failure the wallet might have + // encountered. + Status() (string, error) + + // Open initializes access to a wallet instance. The passphrase parameter may + // or may not be used by the implementation of a particular wallet instance. + Open(device io.ReadWriter, passphrase string) error + + // Close releases any resources held by an open wallet instance. + Close() error + + // Heartbeat performs a sanity check against the hardware wallet to see if it + // is still online and healthy. + Heartbeat() error + + // Derive sends a derivation request to the USB device and returns the Ethereum + // address located on that path. + Derive(path accounts.DerivationPath) (common.Address, error) + + // SignTx sends the transaction to the USB device and waits for the user to confirm + // or deny the transaction. + SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) +} + +// wallet represents the common functionality shared by all USB hardware +// wallets to prevent reimplementing the same complex maintenance mechanisms +// for different vendors. +type wallet struct { + hub *Hub // USB hub scanning + driver driver // Hardware implementation of the low level device operations + url *accounts.URL // Textual URL uniquely identifying this wallet + + info hid.DeviceInfo // Known USB device infos about the wallet + device *hid.Device // USB device advertising itself as a hardware wallet + + accounts []accounts.Account // List of derive accounts pinned on the hardware wallet + paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations + + deriveNextPaths []accounts.DerivationPath // Next derivation paths for account auto-discovery (multiple bases supported) + deriveNextAddrs []common.Address // Next derived account addresses for auto-discovery (multiple bases supported) + deriveChain ethereum.ChainStateReader // Blockchain state reader to discover used account with + deriveReq chan chan struct{} // Channel to request a self-derivation on + deriveQuit chan chan error // Channel to terminate the self-deriver with + + healthQuit chan chan error + + // Locking a hardware wallet is a bit special. Since hardware devices are lower + // performing, any communication with them might take a non negligible amount of + // time. Worse still, waiting for user confirmation can take arbitrarily long, + // but exclusive communication must be upheld during. Locking the entire wallet + // in the mean time however would stall any parts of the system that don't want + // to communicate, just read some state (e.g. list the accounts). + // + // As such, a hardware wallet needs two locks to function correctly. A state + // lock can be used to protect the wallet's software-side internal state, which + // must not be held exclusively during hardware communication. A communication + // lock can be used to achieve exclusive access to the device itself, this one + // however should allow "skipping" waiting for operations that might want to + // use the device, but can live without too (e.g. account self-derivation). + // + // Since we have two locks, it's important to know how to properly use them: + // - Communication requires the `device` to not change, so obtaining the + // commsLock should be done after having a stateLock. + // - Communication must not disable read access to the wallet state, so it + // must only ever hold a *read* lock to stateLock. + commsLock chan struct{} // Mutex (buf=1) for the USB comms without keeping the state locked + stateLock sync.RWMutex // Protects read and write access to the wallet struct fields + + log log.Logger // Contextual logger to tag the base with its id +} + +// URL implements accounts.Wallet, returning the URL of the USB hardware device. +func (w *wallet) URL() accounts.URL { + return *w.url // Immutable, no need for a lock +} + +// Status implements accounts.Wallet, returning a custom status message from the +// underlying vendor-specific hardware wallet implementation. +func (w *wallet) Status() (string, error) { + w.stateLock.RLock() // No device communication, state lock is enough + defer w.stateLock.RUnlock() + + status, failure := w.driver.Status() + if w.device == nil { + return "Closed", failure + } + return status, failure +} + +// Open implements accounts.Wallet, attempting to open a USB connection to the +// hardware wallet. +func (w *wallet) Open(passphrase string) error { + w.stateLock.Lock() // State lock is enough since there's no connection yet at this point + defer w.stateLock.Unlock() + + // If the device was already opened once, refuse to try again + if w.paths != nil { + return accounts.ErrWalletAlreadyOpen + } + // Make sure the actual device connection is done only once + if w.device == nil { + device, err := w.info.Open() + if err != nil { + return err + } + w.device = device + w.commsLock = make(chan struct{}, 1) + w.commsLock <- struct{}{} // Enable lock + } + // Delegate device initialization to the underlying driver + if err := w.driver.Open(w.device, passphrase); err != nil { + return err + } + // Connection successful, start life-cycle management + w.paths = make(map[common.Address]accounts.DerivationPath) + + w.deriveReq = make(chan chan struct{}) + w.deriveQuit = make(chan chan error) + w.healthQuit = make(chan chan error) + + go w.heartbeat() + go w.selfDerive() + + // Notify anyone listening for wallet events that a new device is accessible + go w.hub.updateFeed.Send(accounts.WalletEvent{Wallet: w, Kind: accounts.WalletOpened}) + + return nil +} + +// heartbeat is a health check loop for the USB wallets to periodically verify +// whether they are still present or if they malfunctioned. +func (w *wallet) heartbeat() { + w.log.Debug("USB wallet health-check started") + defer w.log.Debug("USB wallet health-check stopped") + + // Execute heartbeat checks until termination or error + var ( + errc chan error + err error + ) + for errc == nil && err == nil { + // Wait until termination is requested or the heartbeat cycle arrives + select { + case errc = <-w.healthQuit: + // Termination requested + continue + case <-time.After(heartbeatCycle): + // Heartbeat time + } + // Execute a tiny data exchange to see responsiveness + w.stateLock.RLock() + if w.device == nil { + // Terminated while waiting for the lock + w.stateLock.RUnlock() + continue + } + <-w.commsLock // Don't lock state while resolving version + err = w.driver.Heartbeat() + w.commsLock <- struct{}{} + w.stateLock.RUnlock() + + if err != nil { + w.stateLock.Lock() // Lock state to tear the wallet down + w.close() + w.stateLock.Unlock() + } + // Ignore non hardware related errors + err = nil + } + // In case of error, wait for termination + if err != nil { + w.log.Debug("USB wallet health-check failed", "err", err) + errc = <-w.healthQuit + } + errc <- err +} + +// Close implements accounts.Wallet, closing the USB connection to the device. +func (w *wallet) Close() error { + // Ensure the wallet was opened + w.stateLock.RLock() + hQuit, dQuit := w.healthQuit, w.deriveQuit + w.stateLock.RUnlock() + + // Terminate the health checks + var herr error + if hQuit != nil { + errc := make(chan error) + hQuit <- errc + herr = <-errc // Save for later, we *must* close the USB + } + // Terminate the self-derivations + var derr error + if dQuit != nil { + errc := make(chan error) + dQuit <- errc + derr = <-errc // Save for later, we *must* close the USB + } + // Terminate the device connection + w.stateLock.Lock() + defer w.stateLock.Unlock() + + w.healthQuit = nil + w.deriveQuit = nil + w.deriveReq = nil + + if err := w.close(); err != nil { + return err + } + if herr != nil { + return herr + } + return derr +} + +// close is the internal wallet closer that terminates the USB connection and +// resets all the fields to their defaults. +// +// Note, close assumes the state lock is held! +func (w *wallet) close() error { + // Allow duplicate closes, especially for health-check failures + if w.device == nil { + return nil + } + // Close the device, clear everything, then return + w.device.Close() + w.device = nil + + w.accounts, w.paths = nil, nil + return w.driver.Close() +} + +// Accounts implements accounts.Wallet, returning the list of accounts pinned to +// the USB hardware wallet. If self-derivation was enabled, the account list is +// periodically expanded based on current chain state. +func (w *wallet) Accounts() []accounts.Account { + // Attempt self-derivation if it's running + reqc := make(chan struct{}, 1) + select { + case w.deriveReq <- reqc: + // Self-derivation request accepted, wait for it + <-reqc + default: + // Self-derivation offline, throttled or busy, skip + } + // Return whatever account list we ended up with + w.stateLock.RLock() + defer w.stateLock.RUnlock() + + cpy := make([]accounts.Account, len(w.accounts)) + copy(cpy, w.accounts) + return cpy +} + +// selfDerive is an account derivation loop that upon request attempts to find +// new non-zero accounts. +func (w *wallet) selfDerive() { + w.log.Debug("USB wallet self-derivation started") + defer w.log.Debug("USB wallet self-derivation stopped") + + // Execute self-derivations until termination or error + var ( + reqc chan struct{} + errc chan error + err error + ) + for errc == nil && err == nil { + // Wait until either derivation or termination is requested + select { + case errc = <-w.deriveQuit: + // Termination requested + continue + case reqc = <-w.deriveReq: + // Account discovery requested + } + // Derivation needs a chain and device access, skip if either unavailable + w.stateLock.RLock() + if w.device == nil || w.deriveChain == nil { + w.stateLock.RUnlock() + reqc <- struct{}{} + continue + } + select { + case <-w.commsLock: + default: + w.stateLock.RUnlock() + reqc <- struct{}{} + continue + } + // Device lock obtained, derive the next batch of accounts + var ( + accs []accounts.Account + paths []accounts.DerivationPath + + nextPaths = append([]accounts.DerivationPath{}, w.deriveNextPaths...) + nextAddrs = append([]common.Address{}, w.deriveNextAddrs...) + + context = context.Background() + ) + for i := 0; i < len(nextAddrs); i++ { + for empty := false; !empty; { + // Retrieve the next derived Ethereum account + if nextAddrs[i] == (common.Address{}) { + if nextAddrs[i], err = w.driver.Derive(nextPaths[i]); err != nil { + w.log.Warn("USB wallet account derivation failed", "err", err) + break + } + } + // Check the account's status against the current chain state + var ( + balance *big.Int + nonce uint64 + ) + balance, err = w.deriveChain.BalanceAt(context, nextAddrs[i], nil) + if err != nil { + w.log.Warn("USB wallet balance retrieval failed", "err", err) + break + } + nonce, err = w.deriveChain.NonceAt(context, nextAddrs[i], nil) + if err != nil { + w.log.Warn("USB wallet nonce retrieval failed", "err", err) + break + } + // If the next account is empty, stop self-derivation, but add for the last base path + if balance.Sign() == 0 && nonce == 0 { + empty = true + if i < len(nextAddrs)-1 { + break + } + } + // We've just self-derived a new account, start tracking it locally + path := make(accounts.DerivationPath, len(nextPaths[i])) + copy(path[:], nextPaths[i][:]) + paths = append(paths, path) + + account := accounts.Account{ + Address: nextAddrs[i], + URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, + } + accs = append(accs, account) + + // Display a log message to the user for new (or previously empty accounts) + if _, known := w.paths[nextAddrs[i]]; !known || (!empty && nextAddrs[i] == w.deriveNextAddrs[i]) { + w.log.Info("USB wallet discovered new account", "address", nextAddrs[i], "path", path, "balance", balance, "nonce", nonce) + } + // Fetch the next potential account + if !empty { + nextAddrs[i] = common.Address{} + nextPaths[i][len(nextPaths[i])-1]++ + } + } + } + // Self derivation complete, release device lock + w.commsLock <- struct{}{} + w.stateLock.RUnlock() + + // Insert any accounts successfully derived + w.stateLock.Lock() + for i := 0; i < len(accs); i++ { + if _, ok := w.paths[accs[i].Address]; !ok { + w.accounts = append(w.accounts, accs[i]) + w.paths[accs[i].Address] = paths[i] + } + } + // Shift the self-derivation forward + // TODO(karalabe): don't overwrite changes from wallet.SelfDerive + w.deriveNextAddrs = nextAddrs + w.deriveNextPaths = nextPaths + w.stateLock.Unlock() + + // Notify the user of termination and loop after a bit of time (to avoid trashing) + reqc <- struct{}{} + if err == nil { + select { + case errc = <-w.deriveQuit: + // Termination requested, abort + case <-time.After(selfDeriveThrottling): + // Waited enough, willing to self-derive again + } + } + } + // In case of error, wait for termination + if err != nil { + w.log.Debug("USB wallet self-derivation failed", "err", err) + errc = <-w.deriveQuit + } + errc <- err +} + +// Contains implements accounts.Wallet, returning whether a particular account is +// or is not pinned into this wallet instance. Although we could attempt to resolve +// unpinned accounts, that would be an non-negligible hardware operation. +func (w *wallet) Contains(account accounts.Account) bool { + w.stateLock.RLock() + defer w.stateLock.RUnlock() + + _, exists := w.paths[account.Address] + return exists +} + +// Derive implements accounts.Wallet, deriving a new account at the specific +// derivation path. If pin is set to true, the account will be added to the list +// of tracked accounts. +func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { + // Try to derive the actual account and update its URL if successful + w.stateLock.RLock() // Avoid device disappearing during derivation + + if w.device == nil { + w.stateLock.RUnlock() + return accounts.Account{}, accounts.ErrWalletClosed + } + <-w.commsLock // Avoid concurrent hardware access + address, err := w.driver.Derive(path) + w.commsLock <- struct{}{} + + w.stateLock.RUnlock() + + // If an error occurred or no pinning was requested, return + if err != nil { + return accounts.Account{}, err + } + account := accounts.Account{ + Address: address, + URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, + } + if !pin { + return account, nil + } + // Pinning needs to modify the state + w.stateLock.Lock() + defer w.stateLock.Unlock() + + if _, ok := w.paths[address]; !ok { + w.accounts = append(w.accounts, account) + w.paths[address] = path + } + return account, nil +} + +// SelfDerive sets a base account derivation path from which the wallet attempts +// to discover non zero accounts and automatically add them to list of tracked +// accounts. +// +// Note, self derivaton will increment the last component of the specified path +// opposed to decending into a child path to allow discovering accounts starting +// from non zero components. +// +// Some hardware wallets switched derivation paths through their evolution, so +// this method supports providing multiple bases to discover old user accounts +// too. Only the last base will be used to derive the next empty account. +// +// You can disable automatic account discovery by calling SelfDerive with a nil +// chain state reader. +func (w *wallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { + w.stateLock.Lock() + defer w.stateLock.Unlock() + + w.deriveNextPaths = make([]accounts.DerivationPath, len(bases)) + for i, base := range bases { + w.deriveNextPaths[i] = make(accounts.DerivationPath, len(base)) + copy(w.deriveNextPaths[i][:], base[:]) + } + w.deriveNextAddrs = make([]common.Address, len(bases)) + w.deriveChain = chain +} + +// signHash implements accounts.Wallet, however signing arbitrary data is not +// supported for hardware wallets, so this method will always return an error. +func (w *wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) { + return nil, accounts.ErrNotSupported +} + +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed +func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { + return w.signHash(account, crypto.Keccak256(data)) +} + +// SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given +// data with the given account using passphrase as extra authentication. +// Since USB wallets don't rely on passphrases, these are silently ignored. +func (w *wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { + return w.SignData(account, mimeType, data) +} + +func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { + return w.signHash(account, accounts.TextHash(text)) +} + +// SignTx implements accounts.Wallet. It sends the transaction over to the Ledger +// wallet to request a confirmation from the user. It returns either the signed +// transaction or a failure if the user denied the transaction. +// +// Note, if the version of the Ethereum application running on the Ledger wallet is +// too old to sign EIP-155 transactions, but such is requested nonetheless, an error +// will be returned opposed to silently signing in Homestead mode. +func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + w.stateLock.RLock() // Comms have own mutex, this is for the state fields + defer w.stateLock.RUnlock() + + // If the wallet is closed, abort + if w.device == nil { + return nil, accounts.ErrWalletClosed + } + // Make sure the requested account is contained within + path, ok := w.paths[account.Address] + if !ok { + return nil, accounts.ErrUnknownAccount + } + // All infos gathered and metadata checks out, request signing + <-w.commsLock + defer func() { w.commsLock <- struct{}{} }() + + // Ensure the device isn't screwed with while user confirmation is pending + // TODO(karalabe): remove if hotplug lands on Windows + w.hub.commsLock.Lock() + w.hub.commsPend++ + w.hub.commsLock.Unlock() + + defer func() { + w.hub.commsLock.Lock() + w.hub.commsPend-- + w.hub.commsLock.Unlock() + }() + // Sign the transaction and verify the sender to avoid hardware fault surprises + sender, signed, err := w.driver.SignTx(path, tx, chainID) + if err != nil { + return nil, err + } + if sender != account.Address { + return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex()) + } + return signed, nil +} + +// SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary +// data is not supported for Ledger wallets, so this method will always return +// an error. +func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { + return w.SignText(account, accounts.TextHash(text)) +} + +// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given +// transaction with the given account using passphrase as extra authentication. +// Since USB wallets don't rely on passphrases, these are silently ignored. +func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + return w.SignTx(account, tx, chainID) +}