diff --git a/account/account.go b/account/account.go index b050c9079a16cd61c8153be3129cfbdbdadb3b2b..3aec5db87403458036df2a8d44e60411bf0b198f 100644 --- a/account/account.go +++ b/account/account.go @@ -14,19 +14,14 @@ package account -// TODO: [ben] Account and PrivateAccount need to become a pure interface -// and then move the implementation to the manager types. -// Eg, Geth has its accounts, different from BurrowMint - import ( "bytes" + "encoding/json" "fmt" "io" - "github.com/hyperledger/burrow/common/sanity" + "github.com/hyperledger/burrow/binary" ptypes "github.com/hyperledger/burrow/permission/types" - - "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire" ) @@ -41,64 +36,319 @@ func SignBytes(chainID string, o Signable) []byte { buf, n, err := new(bytes.Buffer), new(int), new(error) o.WriteSignBytes(chainID, buf, n, err) if *err != nil { - sanity.PanicCrisis(err) + panic(fmt.Sprintf("could not write sign bytes for a signable: %s", *err)) } - return buf.Bytes() } -//----------------------------------------------------------------------------- +type Addressable interface { + // Get the 20 byte EVM address of this account + Address() Address + // Public key from which the Address is derived + PublicKey() PublicKey +} -// Account resides in the application state, and is mutated by transactions -// on the blockchain. -// Serialized by wire.[read|write]Reflect -type Account struct { - Address []byte `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` - Sequence int `json:"sequence"` - Balance int64 `json:"balance"` - Code []byte `json:"code"` // VM code - StorageRoot []byte `json:"storage_root"` // VM storage merkle root. +// The default immutable interface to an account +type Account interface { + Addressable + // The value held by this account in terms of the chain-native token + Balance() uint64 + // The EVM byte code held by this account (or equivalently, this contract) + Code() Bytecode + // The sequence number (or nonce) of this account, incremented each time a mutation of the + // Account is persisted to the blockchain state + Sequence() uint64 + // The hash of all the values in this accounts storage (typically the root of some subtree + // in the merkle tree of global storage state) + StorageRoot() []byte + // The permission flags and roles for this account + Permissions() ptypes.AccountPermissions + // Obtain a deterministic serialisation of this account + // (i.e. update order and Go runtime independent) + Encode() []byte +} - Permissions ptypes.AccountPermissions `json:"permissions"` +type MutableAccount interface { + Account + // Set public key (needed for lazy initialisation), should also set the dependent address + SetPublicKey(pubKey PublicKey) MutableAccount + // Subtract amount from account balance (will panic if amount is greater than balance) + SubtractFromBalance(amount uint64) MutableAccount + // Add amount to balance (will panic if amount plus balance is a uint64 overflow) + AddToBalance(amount uint64) MutableAccount + // Set EVM byte code associated with account + SetCode(code []byte) MutableAccount + // Increment Sequence number by 1 (capturing the current Sequence number as the index for any pending mutations) + IncSequence() MutableAccount + // Set the storage root hash + SetStorageRoot(storageRoot []byte) MutableAccount + // Set account permissions + SetPermissions(permissions ptypes.AccountPermissions) MutableAccount + // Get a pointer this account's AccountPermissions in order to mutate them + MutablePermissions() *ptypes.AccountPermissions + // Create a complete copy of this MutableAccount that is itself mutable + Copy() MutableAccount +} + +// ------------------------------------------------- +// ConcreteAccount + +// ConcreteAccount is the canonical serialisation and bash-in-place object for an Account +type ConcreteAccount struct { + Address Address + PublicKey PublicKey + Balance uint64 + Code Bytecode + Sequence uint64 + StorageRoot []byte + Permissions ptypes.AccountPermissions +} + +func NewConcreteAccount(pubKey PublicKey) ConcreteAccount { + return ConcreteAccount{ + Address: pubKey.Address(), + PublicKey: pubKey, + // Since nil slices and maps compare differently to empty ones + Code: Bytecode{}, + StorageRoot: []byte{}, + Permissions: ptypes.AccountPermissions{ + Roles: []string{}, + }, + } } -func (acc *Account) Copy() *Account { +func NewConcreteAccountFromSecret(secret string) ConcreteAccount { + return NewConcreteAccount(PublicKeyFromPubKey(PrivateKeyFromSecret(secret).PubKey())) +} + +// Return as immutable Account +func (acc ConcreteAccount) Account() Account { + return concreteAccountWrapper{&acc} +} + +// Return as mutable MutableAccount +func (acc ConcreteAccount) MutableAccount() MutableAccount { + return concreteAccountWrapper{&acc} +} + +func (acc *ConcreteAccount) Encode() []byte { + return wire.BinaryBytes(acc) +} + +func (acc *ConcreteAccount) Copy() *ConcreteAccount { accCopy := *acc return &accCopy } -func (acc *Account) String() string { +func (acc *ConcreteAccount) String() string { if acc == nil { - return "nil-Account" + return "Account{nil}" } - return fmt.Sprintf("Account{%X:%v B:%v C:%v S:%X P:%s}", acc.Address, acc.PubKey, acc.Balance, len(acc.Code), acc.StorageRoot, acc.Permissions) + + return fmt.Sprintf("Account{Address: %s; PublicKey: %v Balance: %v; CodeBytes: %v; StorageRoot: 0x%X; Permissions: %s}", + acc.Address, acc.PublicKey, acc.Balance, len(acc.Code), acc.StorageRoot, acc.Permissions) } -func AccountEncoder(o interface{}, w io.Writer, n *int, err *error) { - wire.WriteBinary(o.(*Account), w, n, err) +// ConcreteAccount +// ------------------------------------------------- +// Conversions +// +// Using the naming convention is this package of 'As<Type>' being +// a conversion from Account to <Type> and 'From<Type>' being conversion +// from <Type> to Account. Conversions are done by copying + +// Returns a mutable, serialisable ConcreteAccount by copying from account +func AsConcreteAccount(account Account) *ConcreteAccount { + if account == nil { + return nil + } + if ca, ok := account.(concreteAccountWrapper); ok { + return ca.ConcreteAccount + } + return &ConcreteAccount{ + Address: account.Address(), + PublicKey: account.PublicKey(), + Balance: account.Balance(), + Code: account.Code(), + Sequence: account.Sequence(), + StorageRoot: account.StorageRoot(), + Permissions: account.Permissions(), + } +} + +// Creates an otherwise zeroed Account from an Addressable and returns it as MutableAccount +func FromAddressable(addressable Addressable) MutableAccount { + return ConcreteAccount{ + Address: addressable.Address(), + PublicKey: addressable.PublicKey(), + // Since nil slices and maps compare differently to empty ones + Code: Bytecode{}, + StorageRoot: []byte{}, + Permissions: ptypes.AccountPermissions{ + Roles: []string{}, + }, + }.MutableAccount() +} + +// Returns an immutable account by copying from account +func AsAccount(account Account) Account { + if account == nil { + return nil + } + return AsConcreteAccount(account).Account() +} + +// Returns a MutableAccount by copying from account +func AsMutableAccount(account Account) MutableAccount { + if account == nil { + return nil + } + return AsConcreteAccount(account).MutableAccount() +} + +func GetMutableAccount(getter Getter, address Address) (MutableAccount, error) { + acc, err := getter.GetAccount(address) + if err != nil { + return nil, err + } + // If we get get our own concreteAccountWrapper back we can save an unnecessary copy and just + // return since ConcreteAccount.Account() will have been used to produce it which will already + // have copied + caw, ok := acc.(concreteAccountWrapper) + if ok { + return caw, nil + } + return AsMutableAccount(acc), nil } -func AccountDecoder(r io.Reader, n *int, err *error) interface{} { - return wire.ReadBinary(&Account{}, r, 0, n, err) +//---------------------------------------------- +// concreteAccount Wrapper + +// concreteAccountWrapper wraps ConcreteAccount to provide a immutable read-only view +// via its implementation of Account and a mutable implementation via its implementation of +// MutableAccount +type concreteAccountWrapper struct { + *ConcreteAccount `json:"unwrap"` +} + +var _ Account = concreteAccountWrapper{} + +func (caw concreteAccountWrapper) Address() Address { + return caw.ConcreteAccount.Address } -var AccountCodec = wire.Codec{ - Encode: AccountEncoder, - Decode: AccountDecoder, +func (caw concreteAccountWrapper) PublicKey() PublicKey { + return caw.ConcreteAccount.PublicKey } -func EncodeAccount(acc *Account) []byte { - w := new(bytes.Buffer) - var n int - var err error - AccountEncoder(acc, w, &n, &err) - return w.Bytes() +func (caw concreteAccountWrapper) Balance() uint64 { + return caw.ConcreteAccount.Balance } -func DecodeAccount(accBytes []byte) *Account { - var n int - var err error - acc := AccountDecoder(bytes.NewBuffer(accBytes), &n, &err) - return acc.(*Account) +func (caw concreteAccountWrapper) Code() Bytecode { + return caw.ConcreteAccount.Code +} + +func (caw concreteAccountWrapper) Sequence() uint64 { + return caw.ConcreteAccount.Sequence +} + +func (caw concreteAccountWrapper) StorageRoot() []byte { + return caw.ConcreteAccount.StorageRoot +} + +func (caw concreteAccountWrapper) Permissions() ptypes.AccountPermissions { + return caw.ConcreteAccount.Permissions +} + +func (caw concreteAccountWrapper) Encode() []byte { + return caw.ConcreteAccount.Encode() +} + +func (caw concreteAccountWrapper) MarshalJSON() ([]byte, error) { + return json.Marshal(caw.ConcreteAccount) +} + +// Account mutation via MutableAccount interface +var _ MutableAccount = concreteAccountWrapper{} + +func (caw concreteAccountWrapper) SetPublicKey(pubKey PublicKey) MutableAccount { + caw.ConcreteAccount.PublicKey = pubKey + addressFromPubKey := pubKey.Address() + // We don't want the wrong public key to take control of an account so we panic here + if caw.ConcreteAccount.Address != addressFromPubKey { + panic(fmt.Errorf("attempt to set public key of account %s to %v, "+ + "but that public key has address %s", + caw.ConcreteAccount.Address, pubKey, addressFromPubKey)) + } + return caw +} + +func (caw concreteAccountWrapper) SubtractFromBalance(amount uint64) MutableAccount { + if amount > caw.Balance() { + panic(fmt.Errorf("insufficient funds: attempt to subtract %v from the balance of %s", + amount, caw.ConcreteAccount)) + } + caw.ConcreteAccount.Balance -= amount + return caw +} + +func (caw concreteAccountWrapper) AddToBalance(amount uint64) MutableAccount { + if binary.IsUint64SumOverflow(caw.Balance(), amount) { + panic(fmt.Errorf("uint64 overflow: attempt to add %v to the balance of %s", + amount, caw.ConcreteAccount)) + } + caw.ConcreteAccount.Balance += amount + return caw +} + +func (caw concreteAccountWrapper) SetCode(code []byte) MutableAccount { + caw.ConcreteAccount.Code = code + return caw +} + +func (caw concreteAccountWrapper) IncSequence() MutableAccount { + caw.ConcreteAccount.Sequence += 1 + return caw +} + +func (caw concreteAccountWrapper) SetStorageRoot(storageRoot []byte) MutableAccount { + caw.ConcreteAccount.StorageRoot = storageRoot + return caw +} + +func (caw concreteAccountWrapper) SetPermissions(permissions ptypes.AccountPermissions) MutableAccount { + caw.ConcreteAccount.Permissions = permissions + return caw +} + +func (caw concreteAccountWrapper) MutablePermissions() *ptypes.AccountPermissions { + return &caw.ConcreteAccount.Permissions +} + +func (caw concreteAccountWrapper) Copy() MutableAccount { + return concreteAccountWrapper{caw.ConcreteAccount.Copy()} +} + +var _ = wire.RegisterInterface(struct{ Account }{}, wire.ConcreteType{concreteAccountWrapper{}, 0x01}) + +// concreteAccount Wrapper +//---------------------------------------------- +// Encoding/decoding + +func Decode(accBytes []byte) (Account, error) { + ca, err := DecodeConcrete(accBytes) + if err != nil { + return nil, err + } + return ca.Account(), nil +} + +func DecodeConcrete(accBytes []byte) (*ConcreteAccount, error) { + ca := new(concreteAccountWrapper) + err := wire.ReadBinaryBytes(accBytes, ca) + if err != nil { + return nil, fmt.Errorf("could not convert decoded account to *ConcreteAccount: %v", err) + } + return ca.ConcreteAccount, nil } diff --git a/account/account_test.go b/account/account_test.go new file mode 100644 index 0000000000000000000000000000000000000000..07c30618ee9a195a012913e486e606eefa2db82c --- /dev/null +++ b/account/account_test.go @@ -0,0 +1,105 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package account + +import ( + "testing" + + "encoding/json" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/go-wire" +) + +func TestAddress(t *testing.T) { + bs := []byte{ + 1, 2, 3, 4, 5, + 1, 2, 3, 4, 5, + 1, 2, 3, 4, 5, + 1, 2, 3, 4, 5, + } + addr, err := AddressFromBytes(bs) + assert.NoError(t, err) + word256 := addr.Word256() + leadingZeroes := []byte{ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + } + assert.Equal(t, leadingZeroes, word256[:12]) + addrFromWord256 := AddressFromWord256(word256) + assert.Equal(t, bs, addrFromWord256[:]) + assert.Equal(t, addr, addrFromWord256) +} + +func TestAccountSerialise(t *testing.T) { + type AccountContainingStruct struct { + Account Account + ChainID string + } + + // This test is really testing this go wire declaration in account.go + + acc := NewConcreteAccountFromSecret("Super Semi Secret") + + // Go wire cannot serialise a top-level interface type it needs to be a field or sub-field of a struct + // at some depth. i.e. you MUST wrap an interface if you want it to be decoded (they do not document this) + var accStruct = AccountContainingStruct{ + Account: acc.Account(), + ChainID: "TestChain", + } + + // We will write into this + accStructOut := AccountContainingStruct{} + + // We must pass in a value type to read from (accStruct), but provide a pointer type to write into (accStructout + wire.ReadBinaryBytes(wire.BinaryBytes(accStruct), &accStructOut) + + assert.Equal(t, accStruct, accStructOut) +} + +func TestDecodeConcrete(t *testing.T) { + concreteAcc := NewConcreteAccountFromSecret("Super Semi Secret") + acc := concreteAcc.Account() + encodedAcc := acc.Encode() + concreteAccOut, err := DecodeConcrete(encodedAcc) + require.NoError(t, err) + assert.Equal(t, concreteAcc, *concreteAccOut) + concreteAccOut, err = DecodeConcrete([]byte("flungepliffery munknut tolopops")) + assert.Error(t, err) +} + +func TestDecode(t *testing.T) { + concreteAcc := NewConcreteAccountFromSecret("Super Semi Secret") + acc := concreteAcc.Account() + accOut, err := Decode(acc.Encode()) + assert.NoError(t, err) + assert.Equal(t, concreteAcc, *AsConcreteAccount(accOut)) + + accOut, err = Decode([]byte("flungepliffery munknut tolopops")) + assert.Error(t, err) + assert.Nil(t, accOut) +} + +func TestMarshalJSON(t *testing.T) { + concreteAcc := NewConcreteAccountFromSecret("Super Semi Secret") + concreteAcc.Code = []byte{60, 23, 45} + acc := concreteAcc.Account() + bs, err := json.Marshal(acc) + assert.Equal(t, `{"Address":"745BD6BE33020146E04FA0F293A41E389887DE86","PublicKey":{"type":"ed25519","data":"8CEBC16C166A0614AD7C8E330318E774E1A039321F17274DF12ABA3B1BFC773C"},"Balance":0,"Code":"3C172D","Sequence":0,"StorageRoot":"","Permissions":{"Base":{"Perms":0,"SetBit":0},"Roles":[]}}`, + string(bs)) + assert.NoError(t, err) +} diff --git a/account/address.go b/account/address.go new file mode 100644 index 0000000000000000000000000000000000000000..5b011e657d65de1d31fff8c1c09aaadd1f220da6 --- /dev/null +++ b/account/address.go @@ -0,0 +1,95 @@ +package account + +import ( + "encoding/json" + "fmt" + + "github.com/hyperledger/burrow/binary" + "github.com/tmthrgd/go-hex" + "golang.org/x/crypto/ripemd160" +) + +type Address binary.Word160 + +var ZeroAddress = Address{} + +func AddressFromBytes(addr []byte) (address Address, err error) { + if len(addr) != binary.Word160Length { + err = fmt.Errorf("slice passed as address '%X' has %d bytes but should have %d bytes", + addr, len(addr), binary.Word160Length) + // It is caller's responsibility to check for errors. If they ignore the error we'll assume they want the + // best-effort mapping of the bytes passed to an address so we don't return here + } + copy(address[:], addr) + return +} + +func AddressFromHexString(str string) (Address, error) { + bs, err := hex.DecodeString(str) + if err != nil { + return ZeroAddress, err + } + return AddressFromBytes(bs) +} + +func MustAddressFromBytes(addr []byte) Address { + address, err := AddressFromBytes(addr) + if err != nil { + panic(fmt.Errorf("error reading address from bytes that caller does not expect: %s", err)) + } + return address +} + +func AddressFromWord256(addr binary.Word256) Address { + return Address(addr.Word160()) +} + +func (address Address) Word256() binary.Word256 { + return binary.Word160(address).Word256() +} + +// Copy address and return a slice onto the copy +func (address Address) Bytes() []byte { + addressCopy := address + return addressCopy[:] +} + +func (address Address) String() string { + return hex.EncodeUpperToString(address[:]) +} + +func (address *Address) UnmarshalJSON(data []byte) error { + str := new(string) + err := json.Unmarshal(data, str) + if err != nil { + return err + } + _, err = hex.Decode(address[:], []byte(*str)) + if err != nil { + return err + } + return nil +} + +func (address Address) MarshalJSON() ([]byte, error) { + return json.Marshal(hex.EncodeUpperToString(address[:])) +} + +func (address *Address) UnmarshalText(text []byte) error { + _, err := hex.Decode(address[:], text) + return err +} + +func (address Address) MarshalText() ([]byte, error) { + return ([]byte)(hex.EncodeUpperToString(address[:])), nil +} + +func NewContractAddress(caller Address, sequence uint64) (newAddr Address) { + temp := make([]byte, 32+8) + copy(temp, caller[:]) + binary.PutUint64BE(temp[32:], uint64(sequence)) + hasher := ripemd160.New() + hasher.Write(temp) // does not error + copy(newAddr[:], hasher.Sum(nil)) + return +} diff --git a/account/address_test.go b/account/address_test.go new file mode 100644 index 0000000000000000000000000000000000000000..25c572c6146b9ddc84e50b49502cdbf6dc26a2db --- /dev/null +++ b/account/address_test.go @@ -0,0 +1,59 @@ +package account + +import ( + "testing" + + "encoding/json" + + "github.com/stretchr/testify/assert" +) + +func TestNewContractAddress(t *testing.T) { + addr := NewContractAddress(Address{ + 233, 181, 216, 115, 19, + 53, 100, 101, 250, 227, + 60, 64, 108, 226, 194, + 151, 157, 230, 11, 203, + }, 1) + + assert.Equal(t, Address{ + 73, 234, 48, 252, 174, + 115, 27, 222, 54, 116, + 47, 133, 144, 21, 73, + 245, 21, 234, 26, 50, + }, addr) +} + +func TestAddress_MarshalJSON(t *testing.T) { + addr := Address{ + 73, 234, 48, 252, 174, + 115, 27, 222, 54, 116, + 47, 133, 144, 21, 73, + 245, 21, 234, 26, 50, + } + + bs, err := json.Marshal(addr) + assert.NoError(t, err) + + addrOut := new(Address) + err = json.Unmarshal(bs, addrOut) + + assert.Equal(t, addr, *addrOut) +} + +func TestAddress_MarshalText(t *testing.T) { + addr := Address{ + 73, 234, 48, 252, 174, + 115, 27, 222, 54, 116, + 47, 133, 144, 21, 73, + 245, 21, 234, 26, 50, + } + + bs, err := addr.MarshalText() + assert.NoError(t, err) + + addrOut := new(Address) + err = addrOut.UnmarshalText(bs) + + assert.Equal(t, addr, *addrOut) +} diff --git a/account/bytecode.go b/account/bytecode.go new file mode 100644 index 0000000000000000000000000000000000000000..76a49f970e7b37c09dc7036b5199ecd2ccf33325 --- /dev/null +++ b/account/bytecode.go @@ -0,0 +1,36 @@ +package account + +import ( + "encoding/json" + + "github.com/tmthrgd/go-hex" +) + +// TODO: write a simple lexer that prints the opcodes. Each byte is either an OpCode or part of the Word256 argument +// to Push[1...32] +type Bytecode []byte + +func (bc Bytecode) Bytes() []byte { + return bc[:] +} + +func (bc Bytecode) String() string { + return hex.EncodeToString(bc[:]) + +} +func (bc Bytecode) MarshalJSON() ([]byte, error) { + return json.Marshal(hex.EncodeUpperToString(bc[:])) +} + +func (bc Bytecode) UnmarshalJSON(data []byte) error { + str := new(string) + err := json.Unmarshal(data, str) + if err != nil { + return err + } + _, err = hex.Decode(bc[:], []byte(*str)) + if err != nil { + return err + } + return nil +} diff --git a/account/keys.go b/account/keys.go new file mode 100644 index 0000000000000000000000000000000000000000..189a9b318e3882b6209a4beefc83d2d2501d57b2 --- /dev/null +++ b/account/keys.go @@ -0,0 +1,61 @@ +package account + +import "github.com/tendermint/go-crypto" + +// This allows us to control serialisation + +type PublicKey struct { + crypto.PubKey `json:"unwrap"` +} + +func PublicKeyFromPubKey(pubKey crypto.PubKey) PublicKey { + return PublicKey{PubKey: pubKey} +} + +func PrivateKeyFromPrivKey(privKey crypto.PrivKey) PrivateKey { + return PrivateKey{PrivKey: privKey} +} + +func (pk PublicKey) Address() Address { + return MustAddressFromBytes(pk.PubKey.Address()) +} + +func (pk PublicKey) MarshalJSON() ([]byte, error) { + return pk.PubKey.MarshalJSON() +} + +func (pk *PublicKey) UnmarshalJSON(data []byte) error { + return pk.PubKey.UnmarshalJSON(data) +} + +func (pk PublicKey) MarshalText() ([]byte, error) { + return pk.MarshalJSON() +} + +func (pk *PublicKey) UnmarshalText(text []byte) error { + return pk.UnmarshalJSON(text) +} + +type PrivateKey struct { + crypto.PrivKey +} + +func (pk PrivateKey) PublicKey() PublicKey { + return PublicKeyFromPubKey(pk.PubKey()) +} + +func (pk PrivateKey) MarshalJSON() ([]byte, error) { + return pk.PrivKey.MarshalJSON() +} + +func (pk *PrivateKey) UnmarshalJSON(data []byte) error { + return pk.PrivKey.UnmarshalJSON(data) +} + +func (pk PrivateKey) MarshalText() ([]byte, error) { + return pk.MarshalJSON() +} + +func (pk *PrivateKey) UnmarshalText(text []byte) error { + return pk.UnmarshalJSON(text) +} diff --git a/account/priv_account.go b/account/priv_account.go deleted file mode 100644 index e2fa762fff0e62ce9162ea68e5dcbd4f6f07def5..0000000000000000000000000000000000000000 --- a/account/priv_account.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package account - -// TODO: [ben] Account and PrivateAccount need to become a pure interface -// and then move the implementation to the manager types. -// Eg, Geth has its accounts, different from BurrowMint - -import ( - "fmt" - - "github.com/hyperledger/burrow/common/sanity" - - "github.com/tendermint/ed25519" - "github.com/tendermint/go-crypto" - "github.com/tendermint/go-wire" -) - -type PrivAccount struct { - Address []byte `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` - PrivKey crypto.PrivKey `json:"priv_key"` -} - -func (pA *PrivAccount) Generate(index int) *PrivAccount { - newPrivKey := pA.PrivKey.(crypto.PrivKeyEd25519).Generate(index) - newPubKey := newPrivKey.PubKey() - newAddress := newPubKey.Address() - return &PrivAccount{ - Address: newAddress, - PubKey: newPubKey, - PrivKey: newPrivKey, - } -} - -func (pA *PrivAccount) Sign(chainID string, o Signable) crypto.Signature { - return pA.PrivKey.Sign(SignBytes(chainID, o)) -} - -func (pA *PrivAccount) String() string { - return fmt.Sprintf("PrivAccount{%X}", pA.Address) -} - -//---------------------------------------- - -// Generates a new account with private key. -func GenPrivAccount() *PrivAccount { - privKeyBytes := new([64]byte) - copy(privKeyBytes[:32], crypto.CRandBytes(32)) - pubKeyBytes := ed25519.MakePublicKey(privKeyBytes) - pubKey := crypto.PubKeyEd25519(*pubKeyBytes) - privKey := crypto.PrivKeyEd25519(*privKeyBytes) - return &PrivAccount{ - Address: pubKey.Address(), - PubKey: pubKey, - PrivKey: privKey, - } -} - -// Generates 32 priv key bytes from secret -func GenPrivKeyBytesFromSecret(secret string) []byte { - return wire.BinarySha256(secret) // Not Ripemd160 because we want 32 bytes. -} - -// Generates a new account with private key from SHA256 hash of a secret -func GenPrivAccountFromSecret(secret string) *PrivAccount { - privKey32 := GenPrivKeyBytesFromSecret(secret) - privKeyBytes := new([64]byte) - copy(privKeyBytes[:32], privKey32) - pubKeyBytes := ed25519.MakePublicKey(privKeyBytes) - pubKey := crypto.PubKeyEd25519(*pubKeyBytes) - privKey := crypto.PrivKeyEd25519(*privKeyBytes) - return &PrivAccount{ - Address: pubKey.Address(), - PubKey: pubKey, - PrivKey: privKey, - } -} - -func GenPrivAccountFromPrivKeyBytes(privKeyBytes []byte) *PrivAccount { - if len(privKeyBytes) != 64 { - sanity.PanicSanity(fmt.Sprintf("Expected 64 bytes but got %v", len(privKeyBytes))) - } - var privKeyArray [64]byte - copy(privKeyArray[:], privKeyBytes) - pubKeyBytes := ed25519.MakePublicKey(&privKeyArray) - pubKey := crypto.PubKeyEd25519(*pubKeyBytes) - privKey := crypto.PrivKeyEd25519(privKeyArray) - return &PrivAccount{ - Address: pubKey.Address(), - PubKey: pubKey, - PrivKey: privKey, - } -} diff --git a/account/private_account.go b/account/private_account.go new file mode 100644 index 0000000000000000000000000000000000000000..9dc73dbb30e7ec47a3f48ef320b4f30ea460c62f --- /dev/null +++ b/account/private_account.go @@ -0,0 +1,136 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package account + +import ( + "fmt" + + "github.com/tendermint/ed25519" + "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire" +) + +type Signer interface { + Sign(msg []byte) (crypto.Signature, error) +} + +type PrivateAccount interface { + Addressable + PrivateKey() PrivateKey + Signer +} + +type ConcretePrivateAccount struct { + Address Address + PublicKey PublicKey + PrivateKey PrivateKey +} + +type concretePrivateAccountWrapper struct { + *ConcretePrivateAccount `json:"unwrap"` +} + +var _ PrivateAccount = concretePrivateAccountWrapper{} + +var _ = wire.RegisterInterface(struct{ PrivateAccount }{}, wire.ConcreteType{concretePrivateAccountWrapper{}, 0x01}) + +func (cpaw concretePrivateAccountWrapper) Address() Address { + return cpaw.ConcretePrivateAccount.Address +} + +func (cpaw concretePrivateAccountWrapper) PublicKey() PublicKey { + return cpaw.ConcretePrivateAccount.PublicKey +} + +func (cpaw concretePrivateAccountWrapper) PrivateKey() PrivateKey { + return cpaw.ConcretePrivateAccount.PrivateKey +} + +func (pa ConcretePrivateAccount) PrivateAccount() concretePrivateAccountWrapper { + return concretePrivateAccountWrapper{&pa} +} + +func (pa ConcretePrivateAccount) Sign(msg []byte) (crypto.Signature, error) { + return pa.PrivateKey.Sign(msg), nil +} + +func ChainSign(pa PrivateAccount, chainID string, o Signable) crypto.Signature { + sig, _ := pa.Sign(SignBytes(chainID, o)) + return sig +} + +func (pa *ConcretePrivateAccount) Generate(index int) concretePrivateAccountWrapper { + newPrivKey := PrivateKeyFromPrivKey(pa.PrivateKey.Unwrap().(crypto.PrivKeyEd25519).Generate(index).Wrap()) + newPubKey := PublicKeyFromPubKey(newPrivKey.PubKey()) + newAddress := newPubKey.Address() + return ConcretePrivateAccount{ + Address: newAddress, + PublicKey: newPubKey, + PrivateKey: newPrivKey, + }.PrivateAccount() +} + +func (pa *ConcretePrivateAccount) String() string { + return fmt.Sprintf("ConcretePrivateAccount{%s}", pa.Address) +} + +//---------------------------------------- + +// Generates a new account with private key. +func GeneratePrivateAccount() concretePrivateAccountWrapper { + privKeyBytes := new([64]byte) + copy(privKeyBytes[:32], crypto.CRandBytes(32)) + pubKeyBytes := ed25519.MakePublicKey(privKeyBytes) + publicKey := PublicKeyFromPubKey(crypto.PubKeyEd25519(*pubKeyBytes).Wrap()) + address := publicKey.Address() + privateKey := PrivateKeyFromPrivKey(crypto.PrivKeyEd25519(*privKeyBytes).Wrap()) + return ConcretePrivateAccount{ + Address: address, + PublicKey: publicKey, + PrivateKey: privateKey, + }.PrivateAccount() +} + +func PrivateKeyFromSecret(secret string) PrivateKey { + return PrivateKeyFromPrivKey(crypto.GenPrivKeyEd25519FromSecret(wire.BinarySha256(secret)).Wrap()) +} + +// Generates a new account with private key from SHA256 hash of a secret +func GeneratePrivateAccountFromSecret(secret string) concretePrivateAccountWrapper { + privKey := PrivateKeyFromSecret(secret) + pubKey := PublicKeyFromPubKey(privKey.PubKey()) + return ConcretePrivateAccount{ + Address: pubKey.Address(), + PublicKey: pubKey, + PrivateKey: privKey, + }.PrivateAccount() +} + +func GeneratePrivateAccountFromPrivateKeyBytes(privKeyBytes []byte) concretePrivateAccountWrapper { + if len(privKeyBytes) != 64 { + panic(fmt.Sprintf("Expected 64 bytes but got %v", len(privKeyBytes))) + } + var privKeyArray [64]byte + copy(privKeyArray[:], privKeyBytes) + pubKeyBytes := ed25519.MakePublicKey(&privKeyArray) + publicKey := PublicKeyFromPubKey(crypto.PubKeyEd25519(*pubKeyBytes).Wrap()) + address := publicKey.Address() + privateKey := PrivateKeyFromPrivKey(crypto.PrivKeyEd25519(privKeyArray).Wrap()) + return ConcretePrivateAccount{ + Address: address, + PublicKey: publicKey, + PrivateKey: privateKey, + }.PrivateAccount() +} diff --git a/account/private_account_test.go b/account/private_account_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b08a244e0ffef413a2c5ab729fb088c377c73d0a --- /dev/null +++ b/account/private_account_test.go @@ -0,0 +1,48 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package account + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tendermint/go-wire" + "github.com/stretchr/testify/require" +) + +func TestPrivateAccountSerialise(t *testing.T) { + type PrivateAccountContainingStruct struct { + PrivateAccount PrivateAccount + ChainID string + } + // This test is really testing this go wire declaration in private_account.go + acc := GeneratePrivateAccountFromSecret("Super Secret Secret") + + // Go wire cannot serialise a top-level interface type it needs to be a field or sub-field of a struct + // at some depth. i.e. you MUST wrap an interface if you want it to be decoded (they do not document this) + var accStruct = PrivateAccountContainingStruct{ + PrivateAccount: acc, + ChainID: "TestChain", + } + + // We will write into this + accStructOut := PrivateAccountContainingStruct{} + + // We must pass in a value type to read from (accStruct), but provide a pointer type to write into (accStructout + err := wire.ReadBinaryBytes(wire.BinaryBytes(accStruct), &accStructOut) + require.NoError(t, err) + + assert.Equal(t, accStruct, accStructOut) +} diff --git a/account/state.go b/account/state.go new file mode 100644 index 0000000000000000000000000000000000000000..dcbba2424d1ba3227682e5d8a564bdb9998845c9 --- /dev/null +++ b/account/state.go @@ -0,0 +1,65 @@ +package account + +import ( + "github.com/hyperledger/burrow/binary" +) + +type Getter interface { + // Get an account by its address return nil if it does not exist (which should not be an error) + GetAccount(address Address) (Account, error) +} + +type Iterable interface { + // Iterates through accounts calling passed function once per account, if the consumer + // returns true the iteration breaks and returns true to indicate it iteration + // was escaped + IterateAccounts(consumer func(Account) (stop bool)) (stopped bool, err error) +} + +type Updater interface { + // Updates the fields of updatedAccount by address, creating the account + // if it does not exist + UpdateAccount(updatedAccount Account) error + // Remove the account at address + RemoveAccount(address Address) error +} + +type StorageGetter interface { + // Retrieve a 32-byte value stored at key for the account at address, return Zero256 if key does not exist but + // error if address does not + GetStorage(address Address, key binary.Word256) (value binary.Word256, err error) +} + +type StorageSetter interface { + // Store a 32-byte value at key for the account at address + SetStorage(address Address, key, value binary.Word256) error +} + +type StorageIterable interface { + // Iterates through the storage of account ad address calling the passed function once per account, + // if the iterator function returns true the iteration breaks and returns true to indicate it iteration + // was escaped + IterateStorage(address Address, consumer func(key, value binary.Word256) (stop bool)) (stopped bool, err error) +} + +// Compositions + +// Read-only account and storage state +type StateReader interface { + Getter + StorageGetter +} + +// Read and list account and storage state +type StateIterable interface { + StateReader + Iterable + StorageIterable +} + +// Read and write account and storage state +type StateWriter interface { + StateReader + Updater + StorageSetter +} diff --git a/account/validator.go b/account/validator.go new file mode 100644 index 0000000000000000000000000000000000000000..5271a2f4a01cdba71280175ff4fde479fb80576e --- /dev/null +++ b/account/validator.go @@ -0,0 +1,93 @@ +package account + +import ( + "encoding/json" +) + +type Validator interface { + Addressable + // The validator's voting power + Power() uint64 + // Alter the validator's voting power by amount that can be negative or positive. + // A power of 0 effectively unbonds the validator + WithNewPower(uint64) Validator +} + +// Neither abci_types or tm_types has quite the representation we want +type ConcreteValidator struct { + Address Address + PublicKey PublicKey + Power uint64 +} + +type concreteValidatorWrapper struct { + *ConcreteValidator `json:"unwrap"` +} + +var _ Validator = concreteValidatorWrapper{} + +func AsValidator(account Account) Validator { + return ConcreteValidator{ + Address: account.Address(), + PublicKey: account.PublicKey(), + Power: account.Balance(), + }.Validator() +} + +func AsConcreteValidator(validator Validator) *ConcreteValidator { + if validator == nil { + return nil + } + if ca, ok := validator.(concreteValidatorWrapper); ok { + return ca.ConcreteValidator + } + return &ConcreteValidator{ + Address: validator.Address(), + PublicKey: validator.PublicKey(), + Power: validator.Power(), + } +} + +func (cvw concreteValidatorWrapper) Address() Address { + return cvw.ConcreteValidator.Address +} + +func (cvw concreteValidatorWrapper) PublicKey() PublicKey { + return cvw.ConcreteValidator.PublicKey +} + +func (cvw concreteValidatorWrapper) Power() uint64 { + return cvw.ConcreteValidator.Power +} + +func (cvw concreteValidatorWrapper) WithNewPower(power uint64) Validator { + cv := cvw.Copy() + cv.Power = power + return concreteValidatorWrapper{ + ConcreteValidator: cv, + } +} + +func (cv ConcreteValidator) Validator() Validator { + return concreteValidatorWrapper{ + ConcreteValidator: &cv, + } +} + +func (cv *ConcreteValidator) Copy() *ConcreteValidator { + cvCopy := *cv + return &cvCopy +} + +func (cv *ConcreteValidator) String() string { + if cv == nil { + return "Nil Validator" + } + + bs, err := json.Marshal(cv) + if err != nil { + return "error serialising Validator" + } + + return string(bs) +} diff --git a/account/validator_test.go b/account/validator_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ff660300316d650d7e3ab64ff89ae4b88effecd4 --- /dev/null +++ b/account/validator_test.go @@ -0,0 +1,14 @@ +package account + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAlterPower(t *testing.T) { + val := AsValidator(NewConcreteAccountFromSecret("seeeeecret").Account()) + valInc := val.WithNewPower(2442132) + assert.Equal(t, uint64(0), val.Power()) + assert.Equal(t, uint64(2442132), valInc.Power()) +} diff --git a/binary/byteslice.go b/binary/byteslice.go new file mode 100644 index 0000000000000000000000000000000000000000..286280b32e37647c050ecffa36379e63e46a6038 --- /dev/null +++ b/binary/byteslice.go @@ -0,0 +1,48 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package binary + +func Fingerprint(slice []byte) []byte { + fingerprint := make([]byte, 6) + copy(fingerprint, slice) + return fingerprint +} + +func IsZeros(slice []byte) bool { + for _, byt := range slice { + if byt != byte(0) { + return false + } + } + return true +} + +func RightPadBytes(slice []byte, l int) []byte { + if l < len(slice) { + return slice + } + padded := make([]byte, l) + copy(padded[0:len(slice)], slice) + return padded +} + +func LeftPadBytes(slice []byte, l int) []byte { + if l < len(slice) { + return slice + } + padded := make([]byte, l) + copy(padded[l-len(slice):], slice) + return padded +} diff --git a/binary/integer.go b/binary/integer.go new file mode 100644 index 0000000000000000000000000000000000000000..f865148400b866d4b071f469c934d1fb297c2f7b --- /dev/null +++ b/binary/integer.go @@ -0,0 +1,77 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package binary + +import ( + "encoding/binary" + "math" + "sort" +) + +const Uint64TopBitMask = 1 << 63 + +// Sort for []uint64 + +type Uint64Slice []uint64 + +func (p Uint64Slice) Len() int { return len(p) } +func (p Uint64Slice) Less(i, j int) bool { return p[i] < p[j] } +func (p Uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p Uint64Slice) Sort() { sort.Sort(p) } + +func SearchUint64s(a []uint64, x uint64) int { + return sort.Search(len(a), func(i int) bool { return a[i] >= x }) +} + +func (p Uint64Slice) Search(x uint64) int { return SearchUint64s(p, x) } + +//-------------------------------------------------------------------------------- + +func PutUint64LE(dest []byte, i uint64) { + binary.LittleEndian.PutUint64(dest, i) +} + +func GetUint64LE(src []byte) uint64 { + return binary.LittleEndian.Uint64(src) +} + +func PutUint64BE(dest []byte, i uint64) { + binary.BigEndian.PutUint64(dest, i) +} + +func GetUint64BE(src []byte) uint64 { + return binary.BigEndian.Uint64(src) +} + +func PutInt64LE(dest []byte, i int64) { + binary.LittleEndian.PutUint64(dest, uint64(i)) +} + +func GetInt64LE(src []byte) int64 { + return int64(binary.LittleEndian.Uint64(src)) +} + +func PutInt64BE(dest []byte, i int64) { + binary.BigEndian.PutUint64(dest, uint64(i)) +} + +func GetInt64BE(src []byte) int64 { + return int64(binary.BigEndian.Uint64(src)) +} + +// Returns whether a + b would be a uint64 overflow +func IsUint64SumOverflow(a, b uint64) bool { + return math.MaxUint64-a < b +} diff --git a/binary/integer_test.go b/binary/integer_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e530c5a7bb6b312fb2e8777796889b1629cb4a45 --- /dev/null +++ b/binary/integer_test.go @@ -0,0 +1,19 @@ +package binary + +import ( + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsUint64SumOverflow(t *testing.T) { + var b uint64 = 0xdeadbeef + var a uint64 = math.MaxUint64 - b + assert.False(t, IsUint64SumOverflow(a-b, b)) + assert.False(t, IsUint64SumOverflow(a, b)) + assert.False(t, IsUint64SumOverflow(a+b, 0)) + assert.True(t, IsUint64SumOverflow(a, b+1)) + assert.True(t, IsUint64SumOverflow(a+b, 1)) + assert.True(t, IsUint64SumOverflow(a+1, b+1)) +} diff --git a/binary/word160.go b/binary/word160.go new file mode 100644 index 0000000000000000000000000000000000000000..991cbee7731d6c9eac7dd683f9648f6fb8e5f98a --- /dev/null +++ b/binary/word160.go @@ -0,0 +1,14 @@ +package binary + +const Word160Length = 20 +const Word256Word160Delta = 12 + +var Zero160 = Word160{} + +type Word160 [Word160Length]byte + +// Pad a Word160 on the left and embed it in a Word256 (as it is for account addresses in EVM) +func (w Word160) Word256() (word256 Word256) { + copy(word256[Word256Word160Delta:], w[:]) + return +} diff --git a/binary/word160_test.go b/binary/word160_test.go new file mode 100644 index 0000000000000000000000000000000000000000..86af40b5a542c589de91410239b230beb792d8e7 --- /dev/null +++ b/binary/word160_test.go @@ -0,0 +1,26 @@ +package binary + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWord160_Word256(t *testing.T) { + word256 := Word256{ + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, + } + word160 := Word160{ + 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, + } + assert.Equal(t, word256, word160.Word256()) + assert.Equal(t, word160, word256.Word160()) +} diff --git a/binary/word256.go b/binary/word256.go new file mode 100644 index 0000000000000000000000000000000000000000..6d11fe9aa27538f0ea21b4f94b8183edcfb4f13b --- /dev/null +++ b/binary/word256.go @@ -0,0 +1,139 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package binary + +import ( + "bytes" + "sort" +) + +var ( + Zero256 = Word256{} + One256 = Word256{1} +) + +const Word256Length = 32 + +var trimCutSet = string([]byte{0}) + +type Word256 [Word256Length]byte + +func (w Word256) String() string { + return string(w[:]) +} + +func (w Word256) Copy() Word256 { + return w +} + +func (w Word256) Bytes() []byte { + return w[:] +} + +// copied. +func (w Word256) Prefix(n int) []byte { + return w[:n] +} + +func (w Word256) Postfix(n int) []byte { + return w[32-n:] +} + +// Get a Word160 embedded a Word256 and padded on the left (as it is for account addresses in EVM) +func (w Word256) Word160() (w160 Word160) { + copy(w160[:], w[Word256Word160Delta:]) + return +} + +func (w Word256) IsZero() bool { + accum := byte(0) + for _, byt := range w { + accum |= byt + } + return accum == 0 +} + +func (w Word256) Compare(other Word256) int { + return bytes.Compare(w[:], other[:]) +} + +func (w Word256) UnpadLeft() []byte { + return bytes.TrimLeft(w[:], trimCutSet) +} + +func (w Word256) UnpadRight() []byte { + return bytes.TrimRight(w[:], trimCutSet) +} + +func Uint64ToWord256(i uint64) Word256 { + buf := [8]byte{} + PutUint64BE(buf[:], i) + return LeftPadWord256(buf[:]) +} + +func Int64ToWord256(i int64) Word256 { + buf := [8]byte{} + PutInt64BE(buf[:], i) + return LeftPadWord256(buf[:]) +} + +func RightPadWord256(bz []byte) (word Word256) { + copy(word[:], bz) + return +} + +func LeftPadWord256(bz []byte) (word Word256) { + copy(word[32-len(bz):], bz) + return +} + +func Uint64FromWord256(word Word256) uint64 { + buf := word.Postfix(8) + return GetUint64BE(buf) +} + +func Int64FromWord256(word Word256) int64 { + buf := word.Postfix(8) + return GetInt64BE(buf) +} + +//------------------------------------- + +type Tuple256 struct { + First Word256 + Second Word256 +} + +func (tuple Tuple256) Compare(other Tuple256) int { + firstCompare := tuple.First.Compare(other.First) + if firstCompare == 0 { + return tuple.Second.Compare(other.Second) + } else { + return firstCompare + } +} + +func Tuple256Split(t Tuple256) (Word256, Word256) { + return t.First, t.Second +} + +type Tuple256Slice []Tuple256 + +func (p Tuple256Slice) Len() int { return len(p) } +func (p Tuple256Slice) Less(i, j int) bool { + return p[i].Compare(p[j]) < 0 +} +func (p Tuple256Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p Tuple256Slice) Sort() { sort.Sort(p) } diff --git a/binary/word256_test.go b/binary/word256_test.go new file mode 100644 index 0000000000000000000000000000000000000000..055d39109e6e3cdbc249948b2071837ba3a9c87d --- /dev/null +++ b/binary/word256_test.go @@ -0,0 +1,39 @@ +package binary + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWord256_UnpadLeft(t *testing.T) { + bs := []byte{0x45, 0x12} + w := LeftPadWord256(bs) + wExpected := Word256{} + wExpected[30] = bs[0] + wExpected[31] = bs[1] + assert.Equal(t, wExpected, w) + assert.Equal(t, bs, w.UnpadLeft()) +} + +func TestWord256_UnpadRight(t *testing.T) { + bs := []byte{0x45, 0x12} + w := RightPadWord256(bs) + wExpected := Word256{} + wExpected[0] = bs[0] + wExpected[1] = bs[1] + assert.Equal(t, wExpected, w) + assert.Equal(t, bs, w.UnpadRight()) +} + +func TestLeftPadWord256(t *testing.T) { + assert.Equal(t, Zero256, LeftPadWord256(nil)) + assert.Equal(t, + Word256{ + 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, 2, 3, + }, + LeftPadWord256([]byte{1, 2, 3})) +}