aboutsummaryrefslogtreecommitdiffstats
path: root/accounts
diff options
context:
space:
mode:
Diffstat (limited to 'accounts')
-rw-r--r--accounts/abi/abi.go155
-rw-r--r--accounts/abi/abi_test.go330
-rw-r--r--accounts/abi/doc.go10
-rw-r--r--accounts/abi/numbers.go106
-rw-r--r--accounts/abi/numbers_test.go72
-rw-r--r--accounts/abi/type.go190
-rw-r--r--accounts/account_manager.go210
-rw-r--r--accounts/accounts_test.go69
8 files changed, 1142 insertions, 0 deletions
diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go
new file mode 100644
index 000000000..067381a48
--- /dev/null
+++ b/accounts/abi/abi.go
@@ -0,0 +1,155 @@
+package abi
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "strings"
+
+ "github.com/ethereum/go-ethereum/crypto"
+)
+
+// Callable method 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
+ Input []Argument
+ Return Type // not yet implemented
+}
+
+// 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 (m Method) String() (out string) {
+ out += m.Name
+ types := make([]string, len(m.Input))
+ i := 0
+ for _, input := range m.Input {
+ types[i] = input.Type.String()
+ i++
+ }
+ out += "(" + strings.Join(types, ",") + ")"
+
+ return
+}
+
+func (m Method) Id() []byte {
+ return crypto.Sha3([]byte(m.String()))[:4]
+}
+
+// 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
+}
+
+func (a *Argument) UnmarshalJSON(data []byte) error {
+ var extarg struct {
+ Name string
+ Type string
+ }
+ err := json.Unmarshal(data, &extarg)
+ if err != nil {
+ return fmt.Errorf("argument json err: %v", err)
+ }
+
+ a.Type, err = NewType(extarg.Type)
+ if err != nil {
+ return err
+ }
+ a.Name = extarg.Name
+
+ return nil
+}
+
+// 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 {
+ Methods map[string]Method
+}
+
+// tests, tests whether the given input would result in a successful
+// call. Checks argument list count and matches input to `input`.
+func (abi ABI) pack(name string, args ...interface{}) ([]byte, error) {
+ method := abi.Methods[name]
+
+ var ret []byte
+ for i, a := range args {
+ input := method.Input[i]
+
+ packed, err := input.Type.pack(a)
+ if err != nil {
+ return nil, fmt.Errorf("`%s` %v", name, err)
+ }
+ ret = append(ret, packed...)
+
+ }
+
+ return ret, 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) {
+ method, exist := abi.Methods[name]
+ if !exist {
+ return nil, fmt.Errorf("method '%s' not found", name)
+ }
+
+ // start with argument count match
+ if len(args) != len(method.Input) {
+ return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(method.Input))
+ }
+
+ arguments, err := abi.pack(name, args...)
+ if err != nil {
+ return nil, err
+ }
+
+ // Set function id
+ packed := abi.Methods[name].Id()
+ packed = append(packed, arguments...)
+
+ return packed, nil
+}
+
+func (abi *ABI) UnmarshalJSON(data []byte) error {
+ var methods []Method
+ if err := json.Unmarshal(data, &methods); err != nil {
+ return err
+ }
+
+ abi.Methods = make(map[string]Method)
+ for _, method := range methods {
+ abi.Methods[method.Name] = method
+ }
+
+ return nil
+}
+
+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
+}
diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go
new file mode 100644
index 000000000..f47fa80f0
--- /dev/null
+++ b/accounts/abi/abi_test.go
@@ -0,0 +1,330 @@
+package abi
+
+import (
+ "bytes"
+ "math/big"
+ "reflect"
+ "strings"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/crypto"
+)
+
+const jsondata = `
+[
+ { "name" : "balance", "const" : true },
+ { "name" : "send", "const" : false, "input" : [ { "name" : "amount", "type" : "uint256" } ] }
+]`
+
+const jsondata2 = `
+[
+ { "name" : "balance", "const" : true },
+ { "name" : "send", "const" : false, "input" : [ { "name" : "amount", "type" : "uint256" } ] },
+ { "name" : "test", "const" : false, "input" : [ { "name" : "number", "type" : "uint32" } ] },
+ { "name" : "string", "const" : false, "input" : [ { "name" : "input", "type" : "string" } ] },
+ { "name" : "bool", "const" : false, "input" : [ { "name" : "input", "type" : "bool" } ] },
+ { "name" : "address", "const" : false, "input" : [ { "name" : "input", "type" : "address" } ] },
+ { "name" : "string32", "const" : false, "input" : [ { "name" : "input", "type" : "string32" } ] },
+ { "name" : "uint64[2]", "const" : false, "input" : [ { "name" : "input", "type" : "uint64[2]" } ] },
+ { "name" : "uint64[]", "const" : false, "input" : [ { "name" : "input", "type" : "uint64[]" } ] },
+ { "name" : "foo", "const" : false, "input" : [ { "name" : "input", "type" : "uint32" } ] },
+ { "name" : "bar", "const" : false, "input" : [ { "name" : "input", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] },
+ { "name" : "slice", "const" : false, "input" : [ { "name" : "input", "type" : "uint32[2]" } ] },
+ { "name" : "slice256", "const" : false, "input" : [ { "name" : "input", "type" : "uint256[2]" } ] }
+]`
+
+func TestType(t *testing.T) {
+ typ, err := NewType("uint32")
+ if err != nil {
+ t.Error(err)
+ }
+ if typ.Kind != reflect.Ptr {
+ t.Error("expected uint32 to have kind Ptr")
+ }
+
+ typ, err = NewType("uint32[]")
+ if err != nil {
+ t.Error(err)
+ }
+ if typ.Kind != reflect.Slice {
+ t.Error("expected uint32[] to have type slice")
+ }
+ if typ.Type != ubig_ts {
+ t.Error("expcted uith32[] to have type uint64")
+ }
+
+ typ, err = NewType("uint32[2]")
+ if err != nil {
+ t.Error(err)
+ }
+ if typ.Kind != reflect.Slice {
+ t.Error("expected uint32[2] to have kind slice")
+ }
+ if typ.Type != ubig_ts {
+ t.Error("expcted uith32[2] to have type uint64")
+ }
+ if typ.Size != 2 {
+ t.Error("expected uint32[2] to have a size of 2")
+ }
+}
+
+func TestReader(t *testing.T) {
+ Uint256, _ := NewType("uint256")
+ exp := ABI{
+ Methods: map[string]Method{
+ "balance": Method{
+ "balance", true, nil, Type{},
+ },
+ "send": Method{
+ "send", false, []Argument{
+ Argument{"amount", Uint256},
+ }, Type{},
+ },
+ },
+ }
+
+ abi, err := JSON(strings.NewReader(jsondata))
+ if err != nil {
+ t.Error(err)
+ }
+
+ // deep equal fails for some reason
+ t.Skip()
+ if !reflect.DeepEqual(abi, exp) {
+ t.Errorf("\nabi: %v\ndoes not match exp: %v", abi, exp)
+ }
+}
+
+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("send", 1000); err != nil {
+ t.Error("expected send(1000) to cast to big")
+ }
+
+ 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)
+ }
+
+ str10 := string(make([]byte, 10))
+ if _, err := abi.Pack("string32", str10); err != nil {
+ t.Error(err)
+ }
+
+ str32 := string(make([]byte, 32))
+ if _, err := abi.Pack("string32", str32); err != nil {
+ t.Error(err)
+ }
+
+ str33 := string(make([]byte, 33))
+ if _, err := abi.Pack("string32", str33); err == nil {
+ t.Error("expected str33 to throw out of bound error")
+ }
+}
+
+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()
+ }
+
+ addr := make([]byte, 20)
+ if _, err := abi.Pack("address", addr); err != nil {
+ t.Error(err)
+ }
+
+ addr = make([]byte, 21)
+ if _, err := abi.Pack("address", addr); err == nil {
+ t.Error("expected address of 21 width to throw")
+ }
+
+ slice := make([]byte, 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 TestTestAddress(t *testing.T) {
+ abi, err := JSON(strings.NewReader(jsondata2))
+ if err != nil {
+ t.Error(err)
+ t.FailNow()
+ }
+
+ addr := make([]byte, 20)
+ if _, err := abi.Pack("address", addr); err != nil {
+ t.Error(err)
+ }
+}
+
+func TestMethodSignature(t *testing.T) {
+ String, _ := NewType("string")
+ String32, _ := NewType("string32")
+ m := Method{"foo", false, []Argument{Argument{"bar", String32}, Argument{"baz", String}}, Type{}}
+ exp := "foo(string32,string)"
+ if m.String() != exp {
+ t.Error("signature mismatch", exp, "!=", m.String())
+ }
+
+ idexp := crypto.Sha3([]byte(exp))[:4]
+ if !bytes.Equal(m.Id(), idexp) {
+ t.Errorf("expected ids to match %x != %x", m.Id(), idexp)
+ }
+
+ uintt, _ := NewType("uint")
+ m = Method{"foo", false, []Argument{Argument{"bar", uintt}}, Type{}}
+ exp = "foo(uint256)"
+ if m.String() != exp {
+ t.Error("signature mismatch", exp, "!=", m.String())
+ }
+}
+
+func TestPack(t *testing.T) {
+ abi, err := JSON(strings.NewReader(jsondata2))
+ if err != nil {
+ t.Error(err)
+ t.FailNow()
+ }
+
+ sig := crypto.Sha3([]byte("foo(uint32)"))[:4]
+ sig = append(sig, make([]byte, 32)...)
+ sig[35] = 10
+
+ packed, err := abi.Pack("foo", uint32(10))
+ if err != nil {
+ t.Error(err)
+ t.FailNow()
+ }
+
+ if !bytes.Equal(packed, sig) {
+ t.Errorf("expected %x got %x", sig, packed)
+ }
+}
+
+func TestMultiPack(t *testing.T) {
+ abi, err := JSON(strings.NewReader(jsondata2))
+ if err != nil {
+ t.Error(err)
+ t.FailNow()
+ }
+
+ sig := crypto.Sha3([]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 TestPackSlice(t *testing.T) {
+ abi, err := JSON(strings.NewReader(jsondata2))
+ if err != nil {
+ t.Error(err)
+ t.FailNow()
+ }
+
+ sig := crypto.Sha3([]byte("slice(uint32[2])"))[:4]
+ sig = append(sig, make([]byte, 64)...)
+ sig[35] = 1
+ sig[67] = 2
+
+ packed, err := abi.Pack("slice", []uint32{1, 2})
+ if err != nil {
+ t.Error(err)
+ t.FailNow()
+ }
+
+ if !bytes.Equal(packed, sig) {
+ t.Errorf("expected %x got %x", sig, packed)
+ }
+}
+
+func TestPackSliceBig(t *testing.T) {
+ abi, err := JSON(strings.NewReader(jsondata2))
+ if err != nil {
+ t.Error(err)
+ t.FailNow()
+ }
+
+ sig := crypto.Sha3([]byte("slice256(uint256[2])"))[:4]
+ sig = append(sig, make([]byte, 64)...)
+ sig[35] = 1
+ sig[67] = 2
+
+ packed, err := abi.Pack("slice256", []*big.Int{big.NewInt(1), big.NewInt(2)})
+ if err != nil {
+ t.Error(err)
+ t.FailNow()
+ }
+
+ if !bytes.Equal(packed, sig) {
+ t.Errorf("expected %x got %x", sig, packed)
+ }
+}
diff --git a/accounts/abi/doc.go b/accounts/abi/doc.go
new file mode 100644
index 000000000..648c971e1
--- /dev/null
+++ b/accounts/abi/doc.go
@@ -0,0 +1,10 @@
+// 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/numbers.go b/accounts/abi/numbers.go
new file mode 100644
index 000000000..9205c005e
--- /dev/null
+++ b/accounts/abi/numbers.go
@@ -0,0 +1,106 @@
+package abi
+
+import (
+ "math/big"
+ "reflect"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+var big_t = reflect.TypeOf(&big.Int{})
+var ubig_t = reflect.TypeOf(&big.Int{})
+var byte_t = reflect.TypeOf(byte(0))
+var byte_ts = reflect.TypeOf([]byte(nil))
+var uint_t = reflect.TypeOf(uint(0))
+var uint8_t = reflect.TypeOf(uint8(0))
+var uint16_t = reflect.TypeOf(uint16(0))
+var uint32_t = reflect.TypeOf(uint32(0))
+var uint64_t = reflect.TypeOf(uint64(0))
+var int_t = reflect.TypeOf(int(0))
+var int8_t = reflect.TypeOf(int8(0))
+var int16_t = reflect.TypeOf(int16(0))
+var int32_t = reflect.TypeOf(int32(0))
+var int64_t = reflect.TypeOf(int64(0))
+
+var uint_ts = reflect.TypeOf([]uint(nil))
+var uint8_ts = reflect.TypeOf([]uint8(nil))
+var uint16_ts = reflect.TypeOf([]uint16(nil))
+var uint32_ts = reflect.TypeOf([]uint32(nil))
+var uint64_ts = reflect.TypeOf([]uint64(nil))
+var ubig_ts = reflect.TypeOf([]*big.Int(nil))
+
+var int_ts = reflect.TypeOf([]int(nil))
+var int8_ts = reflect.TypeOf([]int8(nil))
+var int16_ts = reflect.TypeOf([]int16(nil))
+var int32_ts = reflect.TypeOf([]int32(nil))
+var int64_ts = reflect.TypeOf([]int64(nil))
+var big_ts = reflect.TypeOf([]*big.Int(nil))
+
+// U256 will ensure unsigned 256bit on big nums
+func U256(n *big.Int) []byte {
+ return common.LeftPadBytes(common.U256(n).Bytes(), 32)
+}
+
+func S256(n *big.Int) []byte {
+ sint := common.S256(n)
+ ret := common.LeftPadBytes(sint.Bytes(), 32)
+ if sint.Cmp(common.Big0) < 0 {
+ for i, b := range ret {
+ if b == 0 {
+ ret[i] = 1
+ continue
+ }
+ break
+ }
+ }
+
+ return ret
+}
+
+// S256 will ensure signed 256bit on big nums
+func U2U256(n uint64) []byte {
+ return U256(big.NewInt(int64(n)))
+}
+
+func S2S256(n int64) []byte {
+ return S256(big.NewInt(n))
+}
+
+// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation
+func packNum(value reflect.Value, to byte) []byte {
+ switch kind := value.Kind(); kind {
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ if to == UintTy {
+ return U2U256(value.Uint())
+ } else {
+ return S2S256(int64(value.Uint()))
+ }
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ if to == UintTy {
+ return U2U256(uint64(value.Int()))
+ } else {
+ return S2S256(value.Int())
+ }
+ case reflect.Ptr:
+ // This only takes care of packing and casting. No type checking is done here. It should be done prior to using this function.
+ if to == UintTy {
+ return U256(value.Interface().(*big.Int))
+ } else {
+ return S256(value.Interface().(*big.Int))
+ }
+
+ }
+
+ return nil
+}
+
+// checks whether the given reflect value is signed. This also works for slices with a number type
+func isSigned(v reflect.Value) bool {
+ switch v.Type() {
+ case ubig_ts, big_ts, big_t, ubig_t:
+ return true
+ case int_ts, int8_ts, int16_ts, int32_ts, int64_ts, int_t, int8_t, int16_t, int32_t, int64_t:
+ return true
+ }
+ return false
+}
diff --git a/accounts/abi/numbers_test.go b/accounts/abi/numbers_test.go
new file mode 100644
index 000000000..319d7fb51
--- /dev/null
+++ b/accounts/abi/numbers_test.go
@@ -0,0 +1,72 @@
+package abi
+
+import (
+ "bytes"
+ "math/big"
+ "reflect"
+ "testing"
+)
+
+func TestNumberTypes(t *testing.T) {
+ ubytes := make([]byte, 32)
+ ubytes[31] = 1
+ sbytesmin := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
+
+ unsigned := U256(big.NewInt(1))
+ if !bytes.Equal(unsigned, ubytes) {
+ t.Error("expected %x got %x", ubytes, unsigned)
+ }
+
+ signed := S256(big.NewInt(1))
+ if !bytes.Equal(signed, ubytes) {
+ t.Error("expected %x got %x", ubytes, unsigned)
+ }
+
+ signed = S256(big.NewInt(-1))
+ if !bytes.Equal(signed, sbytesmin) {
+ t.Error("expected %x got %x", ubytes, unsigned)
+ }
+}
+
+func TestPackNumber(t *testing.T) {
+ ubytes := make([]byte, 32)
+ ubytes[31] = 1
+ sbytesmin := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
+ maxunsigned := []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
+
+ packed := packNum(reflect.ValueOf(1), IntTy)
+ if !bytes.Equal(packed, ubytes) {
+ t.Errorf("expected %x got %x", ubytes, packed)
+ }
+ packed = packNum(reflect.ValueOf(-1), IntTy)
+ if !bytes.Equal(packed, sbytesmin) {
+ t.Errorf("expected %x got %x", ubytes, packed)
+ }
+ packed = packNum(reflect.ValueOf(1), UintTy)
+ if !bytes.Equal(packed, ubytes) {
+ t.Errorf("expected %x got %x", ubytes, packed)
+ }
+ packed = packNum(reflect.ValueOf(-1), UintTy)
+ if !bytes.Equal(packed, maxunsigned) {
+ t.Errorf("expected %x got %x", maxunsigned, packed)
+ }
+
+ packed = packNum(reflect.ValueOf("string"), UintTy)
+ if packed != nil {
+ t.Errorf("expected 'string' to pack to nil. got %x instead", packed)
+ }
+}
+
+func TestSigned(t *testing.T) {
+ if isSigned(reflect.ValueOf(uint(10))) {
+ t.Error()
+ }
+
+ if !isSigned(reflect.ValueOf(int(10))) {
+ t.Error()
+ }
+
+ if !isSigned(reflect.ValueOf(big.NewInt(10))) {
+ t.Error()
+ }
+}
diff --git a/accounts/abi/type.go b/accounts/abi/type.go
new file mode 100644
index 000000000..56520b672
--- /dev/null
+++ b/accounts/abi/type.go
@@ -0,0 +1,190 @@
+package abi
+
+import (
+ "fmt"
+ "reflect"
+ "regexp"
+ "strconv"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+const (
+ IntTy byte = iota
+ UintTy
+ BoolTy
+ SliceTy
+ AddressTy
+ RealTy
+)
+
+// Type is the reflection of the supported argument type
+type Type struct {
+ Kind reflect.Kind
+ Type reflect.Type
+ Size int
+ T byte // Our own type checking
+ stringKind string // holds the unparsed string for deriving signatures
+}
+
+// New type returns a fully parsed Type given by the input string or an error if it can't be parsed.
+//
+// Strings can be in the format of:
+//
+// Input = Type [ "[" [ Number ] "]" ] Name .
+// Type = [ "u" ] "int" [ Number ] .
+//
+// Examples:
+//
+// string int uint real
+// string32 int8 uint8 uint[]
+// address int256 uint256 real[2]
+func NewType(t string) (typ Type, err error) {
+ // 1. full string 2. type 3. (opt.) is slice 4. (opt.) size
+ freg, err := regexp.Compile("([a-zA-Z0-9]+)(\\[([0-9]*)?\\])?")
+ if err != nil {
+ return Type{}, err
+ }
+ res := freg.FindAllStringSubmatch(t, -1)[0]
+ var (
+ isslice bool
+ size int
+ )
+ switch {
+ case res[3] != "":
+ // err is ignored. Already checked for number through the regexp
+ size, _ = strconv.Atoi(res[3])
+ isslice = true
+ case res[2] != "":
+ isslice = true
+ size = -1
+ case res[0] == "":
+ return Type{}, fmt.Errorf("type parse error for `%s`", t)
+ }
+
+ treg, err := regexp.Compile("([a-zA-Z]+)([0-9]*)?")
+ if err != nil {
+ return Type{}, err
+ }
+
+ parsedType := treg.FindAllStringSubmatch(res[1], -1)[0]
+ vsize, _ := strconv.Atoi(parsedType[2])
+ vtype := parsedType[1]
+ // substitute canonical representation
+ if vsize == 0 && (vtype == "int" || vtype == "uint") {
+ vsize = 256
+ t += "256"
+ }
+
+ if isslice {
+ typ.Kind = reflect.Slice
+ typ.Size = size
+ switch vtype {
+ case "int":
+ typ.Type = big_ts
+ case "uint":
+ typ.Type = ubig_ts
+ default:
+ return Type{}, fmt.Errorf("unsupported arg slice type: %s", t)
+ }
+ } else {
+ switch vtype {
+ case "int":
+ typ.Kind = reflect.Ptr
+ typ.Type = big_t
+ typ.Size = 256
+ typ.T = IntTy
+ case "uint":
+ typ.Kind = reflect.Ptr
+ typ.Type = ubig_t
+ typ.Size = 256
+ typ.T = UintTy
+ case "bool":
+ typ.Kind = reflect.Bool
+ case "real": // TODO
+ typ.Kind = reflect.Invalid
+ case "address":
+ typ.Kind = reflect.Slice
+ typ.Type = byte_ts
+ typ.Size = 20
+ typ.T = AddressTy
+ case "string":
+ typ.Kind = reflect.String
+ typ.Size = -1
+ if vsize > 0 {
+ typ.Size = 32
+ }
+ default:
+ return Type{}, fmt.Errorf("unsupported arg type: %s", t)
+ }
+ }
+ typ.stringKind = t
+
+ return
+}
+
+func (t Type) String() (out string) {
+ return t.stringKind
+}
+
+// Test the given input parameter `v` and checks if it matches certain
+// criteria
+// * Big integers are checks for ptr types and if the given value is
+// assignable
+// * Integer are checked for size
+// * Strings, addresses and bytes are checks for type and size
+func (t Type) pack(v interface{}) ([]byte, error) {
+ value := reflect.ValueOf(v)
+ switch kind := value.Kind(); kind {
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ if t.Type != ubig_t {
+ return nil, fmt.Errorf("type mismatch: %s for %T", t.Type, v)
+ }
+ return packNum(value, t.T), nil
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ if t.Type != ubig_t {
+ return nil, fmt.Errorf("type mismatch: %s for %T", t.Type, v)
+ }
+ return packNum(value, t.T), nil
+ case reflect.Ptr:
+ // If the value is a ptr do a assign check (only used by
+ // big.Int for now)
+ if t.Type == ubig_t && value.Type() != ubig_t {
+ return nil, fmt.Errorf("type mismatch: %s for %T", t.Type, v)
+ }
+ return packNum(value, t.T), nil
+ case reflect.String:
+ if t.Size > -1 && value.Len() > t.Size {
+ return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size)
+ }
+ return []byte(common.LeftPadString(t.String(), 32)), nil
+ case reflect.Slice:
+ if t.Size > -1 && value.Len() > t.Size {
+ return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size)
+ }
+
+ // Address is a special slice. The slice acts as one rather than a list of elements.
+ if t.T == AddressTy {
+ return common.LeftPadBytes(v.([]byte), 32), nil
+ }
+
+ // Signed / Unsigned check
+ if (t.T != IntTy && isSigned(value)) || (t.T == UintTy && isSigned(value)) {
+ return nil, fmt.Errorf("slice of incompatible types.")
+ }
+
+ var packed []byte
+ for i := 0; i < value.Len(); i++ {
+ packed = append(packed, packNum(value.Index(i), t.T)...)
+ }
+ return packed, nil
+ case reflect.Bool:
+ if value.Bool() {
+ return common.LeftPadBytes(common.Big1.Bytes(), 32), nil
+ } else {
+ return common.LeftPadBytes(common.Big0.Bytes(), 32), nil
+ }
+ }
+
+ panic("unreached")
+}
diff --git a/accounts/account_manager.go b/accounts/account_manager.go
new file mode 100644
index 000000000..646dc8376
--- /dev/null
+++ b/accounts/account_manager.go
@@ -0,0 +1,210 @@
+/*
+ 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 Lesser 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 Lesser General Public License
+ along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @authors
+ * Gustav Simonsson <gustav.simonsson@gmail.com>
+ * @date 2015
+ *
+ */
+/*
+
+This abstracts part of a user's interaction with an account she controls.
+It's not an abstraction of core Ethereum accounts data type / logic -
+for that see the core processing code of blocks / txs.
+
+Currently this is pretty much a passthrough to the KeyStore2 interface,
+and accounts persistence is derived from stored keys' addresses
+
+*/
+package accounts
+
+import (
+ "bytes"
+ "crypto/ecdsa"
+ crand "crypto/rand"
+ "os"
+
+ "errors"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/crypto"
+)
+
+var (
+ ErrLocked = errors.New("account is locked")
+ ErrNoKeys = errors.New("no keys in store")
+)
+
+type Account struct {
+ Address []byte
+}
+
+type Manager struct {
+ keyStore crypto.KeyStore2
+ unlocked map[string]*unlocked
+ mutex sync.RWMutex
+}
+
+type unlocked struct {
+ *crypto.Key
+ abort chan struct{}
+}
+
+func NewManager(keyStore crypto.KeyStore2) *Manager {
+ return &Manager{
+ keyStore: keyStore,
+ unlocked: make(map[string]*unlocked),
+ }
+}
+
+func (am *Manager) HasAccount(addr []byte) bool {
+ accounts, _ := am.Accounts()
+ for _, acct := range accounts {
+ if bytes.Compare(acct.Address, addr) == 0 {
+ return true
+ }
+ }
+ return false
+}
+
+// Coinbase returns the account address that mining rewards are sent to.
+func (am *Manager) Coinbase() (addr []byte, err error) {
+ // TODO: persist coinbase address on disk
+ return am.firstAddr()
+}
+
+func (am *Manager) firstAddr() ([]byte, error) {
+ addrs, err := am.keyStore.GetKeyAddresses()
+ if os.IsNotExist(err) {
+ return nil, ErrNoKeys
+ } else if err != nil {
+ return nil, err
+ }
+ if len(addrs) == 0 {
+ return nil, ErrNoKeys
+ }
+ return addrs[0], nil
+}
+
+func (am *Manager) DeleteAccount(address []byte, auth string) error {
+ return am.keyStore.DeleteKey(address, auth)
+}
+
+func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) {
+ am.mutex.RLock()
+ unlockedKey, found := am.unlocked[string(a.Address)]
+ am.mutex.RUnlock()
+ if !found {
+ return nil, ErrLocked
+ }
+ signature, err = crypto.Sign(toSign, unlockedKey.PrivateKey)
+ return signature, err
+}
+
+// TimedUnlock unlocks the account with the given address.
+// When timeout has passed, the account will be locked again.
+func (am *Manager) TimedUnlock(addr []byte, keyAuth string, timeout time.Duration) error {
+ key, err := am.keyStore.GetKey(addr, keyAuth)
+ if err != nil {
+ return err
+ }
+ u := am.addUnlocked(addr, key)
+ go am.dropLater(addr, u, timeout)
+ return nil
+}
+
+// Unlock unlocks the account with the given address. The account
+// stays unlocked until the program exits or until a TimedUnlock
+// timeout (started after the call to Unlock) expires.
+func (am *Manager) Unlock(addr []byte, keyAuth string) error {
+ key, err := am.keyStore.GetKey(addr, keyAuth)
+ if err != nil {
+ return err
+ }
+ am.addUnlocked(addr, key)
+ return nil
+}
+
+func (am *Manager) NewAccount(auth string) (Account, error) {
+ key, err := am.keyStore.GenerateNewKey(crand.Reader, auth)
+ if err != nil {
+ return Account{}, err
+ }
+ return Account{Address: key.Address}, nil
+}
+
+func (am *Manager) Accounts() ([]Account, error) {
+ addresses, err := am.keyStore.GetKeyAddresses()
+ if os.IsNotExist(err) {
+ return nil, ErrNoKeys
+ } else if err != nil {
+ return nil, err
+ }
+ accounts := make([]Account, len(addresses))
+ for i, addr := range addresses {
+ accounts[i] = Account{
+ Address: addr,
+ }
+ }
+ return accounts, err
+}
+
+func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked {
+ u := &unlocked{Key: key, abort: make(chan struct{})}
+ am.mutex.Lock()
+ prev, found := am.unlocked[string(addr)]
+ if found {
+ // terminate dropLater for this key to avoid unexpected drops.
+ close(prev.abort)
+ // the key is zeroed here instead of in dropLater because
+ // there might not actually be a dropLater running for this
+ // key, i.e. when Unlock was used.
+ zeroKey(prev.PrivateKey)
+ }
+ am.unlocked[string(addr)] = u
+ am.mutex.Unlock()
+ return u
+}
+
+func (am *Manager) dropLater(addr []byte, u *unlocked, timeout time.Duration) {
+ t := time.NewTimer(timeout)
+ defer t.Stop()
+ select {
+ case <-u.abort:
+ // just quit
+ case <-t.C:
+ am.mutex.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 am.unlocked[string(addr)] == u {
+ zeroKey(u.PrivateKey)
+ delete(am.unlocked, string(addr))
+ }
+ am.mutex.Unlock()
+ }
+}
+
+// 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/accounts_test.go b/accounts/accounts_test.go
new file mode 100644
index 000000000..427114cbd
--- /dev/null
+++ b/accounts/accounts_test.go
@@ -0,0 +1,69 @@
+package accounts
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/crypto/randentropy"
+)
+
+func TestSign(t *testing.T) {
+ dir, ks := tmpKeyStore(t, crypto.NewKeyStorePlain)
+ defer os.RemoveAll(dir)
+
+ am := NewManager(ks)
+ pass := "" // not used but required by API
+ a1, err := am.NewAccount(pass)
+ toSign := randentropy.GetEntropyCSPRNG(32)
+ am.Unlock(a1.Address, "")
+
+ _, err = am.Sign(a1, toSign)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestTimedUnlock(t *testing.T) {
+ dir, ks := tmpKeyStore(t, crypto.NewKeyStorePassphrase)
+ defer os.RemoveAll(dir)
+
+ am := NewManager(ks)
+ pass := "foo"
+ a1, err := am.NewAccount(pass)
+ toSign := randentropy.GetEntropyCSPRNG(32)
+
+ // Signing without passphrase fails because account is locked
+ _, err = am.Sign(a1, toSign)
+ if err != ErrLocked {
+ t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err)
+ }
+
+ // Signing with passphrase works
+ if err = am.TimedUnlock(a1.Address, pass, 100*time.Millisecond); err != nil {
+ t.Fatal(err)
+ }
+
+ // Signing without passphrase works because account is temp unlocked
+ _, err = am.Sign(a1, toSign)
+ if err != nil {
+ t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
+ }
+
+ // Signing fails again after automatic locking
+ time.Sleep(150 * time.Millisecond)
+ _, err = am.Sign(a1, toSign)
+ if err != ErrLocked {
+ t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
+ }
+}
+
+func tmpKeyStore(t *testing.T, new func(string) crypto.KeyStore2) (string, crypto.KeyStore2) {
+ d, err := ioutil.TempDir("", "eth-keystore-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ return d, new(d)
+}