forked from TrueCloudLab/neoneo-go
Persist transactions (#51)
* added account_state + changed ECPoint to PublicKey * account state persist * in depth test for existing accounts. * implemented GetTransaction. * added enrollment TX * added persist of accounts and unspent coins * bumped version -> 0.32.0
This commit is contained in:
parent
a67728628e
commit
52fa41a12a
26 changed files with 930 additions and 66 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
0.31.0
|
0.32.0
|
||||||
|
|
137
pkg/core/account_state.go
Normal file
137
pkg/core/account_state.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Accounts is mapping between a account address and AccountState.
|
||||||
|
type Accounts map[util.Uint160]*AccountState
|
||||||
|
|
||||||
|
func (a Accounts) getAndChange(s storage.Store, hash util.Uint160) (*AccountState, error) {
|
||||||
|
if account, ok := a[hash]; ok {
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var account *AccountState
|
||||||
|
key := storage.AppendPrefix(storage.STAccount, hash.Bytes())
|
||||||
|
if b, err := s.Get(key); err == nil {
|
||||||
|
if err := account.DecodeBinary(bytes.NewReader(b)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
account = NewAccountState(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
a[hash] = account
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit writes all account states to the given Batch.
|
||||||
|
func (a Accounts) commit(b storage.Batch) error {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
for hash, state := range a {
|
||||||
|
if err := state.EncodeBinary(buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key := storage.AppendPrefix(storage.STAccount, hash.Bytes())
|
||||||
|
b.Put(key, buf.Bytes())
|
||||||
|
buf.Reset()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountState represents the state of a NEO account.
|
||||||
|
type AccountState struct {
|
||||||
|
ScriptHash util.Uint160
|
||||||
|
IsFrozen bool
|
||||||
|
Votes []*crypto.PublicKey
|
||||||
|
Balances map[util.Uint256]util.Fixed8
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAccountState returns a new AccountState object.
|
||||||
|
func NewAccountState(scriptHash util.Uint160) *AccountState {
|
||||||
|
return &AccountState{
|
||||||
|
ScriptHash: scriptHash,
|
||||||
|
IsFrozen: false,
|
||||||
|
Votes: []*crypto.PublicKey{},
|
||||||
|
Balances: make(map[util.Uint256]util.Fixed8),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary decodes AccountState from the given io.Reader.
|
||||||
|
func (s *AccountState) DecodeBinary(r io.Reader) error {
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &s.ScriptHash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &s.IsFrozen); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lenVotes := util.ReadVarUint(r)
|
||||||
|
s.Votes = make([]*crypto.PublicKey, lenVotes)
|
||||||
|
for i := 0; i < int(lenVotes); i++ {
|
||||||
|
s.Votes[i] = &crypto.PublicKey{}
|
||||||
|
if err := s.Votes[i].DecodeBinary(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Balances = make(map[util.Uint256]util.Fixed8)
|
||||||
|
lenBalances := util.ReadVarUint(r)
|
||||||
|
for i := 0; i < int(lenBalances); i++ {
|
||||||
|
key := util.Uint256{}
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var val util.Fixed8
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Balances[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary encode AccountState to the given io.Writer.
|
||||||
|
func (s *AccountState) EncodeBinary(w io.Writer) error {
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, s.ScriptHash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, s.IsFrozen); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := util.WriteVarUint(w, uint64(len(s.Votes))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, point := range s.Votes {
|
||||||
|
if err := point.EncodeBinary(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := util.WriteVarUint(w, uint64(len(s.Balances))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range s.Balances {
|
||||||
|
if v > 0 {
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, k); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
47
pkg/core/account_state_test.go
Normal file
47
pkg/core/account_state_test.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecodeEncodeAccountState(t *testing.T) {
|
||||||
|
var (
|
||||||
|
n = 10
|
||||||
|
balances = make(map[util.Uint256]util.Fixed8)
|
||||||
|
votes = make([]*crypto.PublicKey, n)
|
||||||
|
)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
balances[util.RandomUint256()] = util.Fixed8(int64(util.RandomInt(1, 10000)))
|
||||||
|
votes[i] = &crypto.PublicKey{crypto.RandomECPoint()}
|
||||||
|
}
|
||||||
|
|
||||||
|
a := &AccountState{
|
||||||
|
ScriptHash: util.RandomUint160(),
|
||||||
|
IsFrozen: true,
|
||||||
|
Votes: votes,
|
||||||
|
Balances: balances,
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := a.EncodeBinary(buf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
aDecode := &AccountState{}
|
||||||
|
if err := aDecode.DecodeBinary(buf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, a.ScriptHash, aDecode.ScriptHash)
|
||||||
|
assert.Equal(t, a.IsFrozen, aDecode.IsFrozen)
|
||||||
|
|
||||||
|
for i, vote := range a.Votes {
|
||||||
|
assert.Equal(t, vote.X, aDecode.Votes[i].X)
|
||||||
|
}
|
||||||
|
assert.Equal(t, a.Balances, aDecode.Balances)
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
@ -154,6 +155,13 @@ func (bc *Blockchain) run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For now this will return a hardcoded hash of the NEO governing token.
|
||||||
|
func (bc *Blockchain) governingToken() util.Uint256 {
|
||||||
|
neoNativeAsset := "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"
|
||||||
|
val, _ := util.Uint256DecodeString(neoNativeAsset)
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
// AddBlock processes the given block and will add it to the cache so it
|
// AddBlock processes the given block and will add it to the cache so it
|
||||||
// can be persisted.
|
// can be persisted.
|
||||||
func (bc *Blockchain) AddBlock(block *Block) error {
|
func (bc *Blockchain) AddBlock(block *Block) error {
|
||||||
|
@ -246,15 +254,99 @@ func (bc *Blockchain) processHeader(h *Header, batch storage.Batch, headerList *
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: persistBlock needs some more love, its implemented as in the original
|
||||||
|
// project. This for the sake of development speed and understanding of what
|
||||||
|
// is happening here, quite allot as you can see :). If things are wired together
|
||||||
|
// and all tests are in place, we can make a more optimized and cleaner implementation.
|
||||||
func (bc *Blockchain) persistBlock(block *Block) error {
|
func (bc *Blockchain) persistBlock(block *Block) error {
|
||||||
batch := bc.Batch()
|
var (
|
||||||
|
batch = bc.Batch()
|
||||||
|
unspentCoins = make(UnspentCoins)
|
||||||
|
accounts = make(Accounts)
|
||||||
|
)
|
||||||
|
|
||||||
storeAsBlock(batch, block, 0)
|
storeAsBlock(batch, block, 0)
|
||||||
storeAsCurrentBlock(batch, block)
|
storeAsCurrentBlock(batch, block)
|
||||||
|
|
||||||
|
for _, tx := range block.Transactions {
|
||||||
|
storeAsTransaction(batch, tx, block.Index)
|
||||||
|
|
||||||
|
// Add CoinStateConfirmed for each tx output.
|
||||||
|
unspent := make([]CoinState, len(tx.Outputs))
|
||||||
|
for i := 0; i < len(tx.Outputs); i++ {
|
||||||
|
unspent[i] = CoinStateConfirmed
|
||||||
|
}
|
||||||
|
unspentCoins[tx.Hash()] = &UnspentCoinState{unspent}
|
||||||
|
|
||||||
|
// Process TX outputs.
|
||||||
|
for _, output := range tx.Outputs {
|
||||||
|
account, err := accounts.getAndChange(bc.Store, output.ScriptHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := account.Balances[output.AssetID]; ok {
|
||||||
|
account.Balances[output.AssetID] += output.Amount
|
||||||
|
} else {
|
||||||
|
account.Balances[output.AssetID] = output.Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
if output.AssetID.Equals(bc.governingToken()) && len(account.Votes) > 0 {
|
||||||
|
log.Warnf("governing token detected in TX output need to update validators!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process TX inputs that are grouped by previous hash.
|
||||||
|
for prevHash, inputs := range tx.GroupInputsByPrevHash() {
|
||||||
|
prevTX, _, err := bc.GetTransaction(prevHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, input := range inputs {
|
||||||
|
unspent, err := unspentCoins.getAndChange(bc.Store, input.PrevHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
unspent.states[input.PrevIndex] = CoinStateSpent
|
||||||
|
|
||||||
|
prevTXOutput := prevTX.Outputs[input.PrevIndex]
|
||||||
|
account, err := accounts.getAndChange(bc.Store, prevTXOutput.ScriptHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if prevTXOutput.AssetID.Equals(bc.governingToken()) {
|
||||||
|
log.Warnf("governing token detected in TX input need to update validators!")
|
||||||
|
}
|
||||||
|
|
||||||
|
account.Balances[prevTXOutput.AssetID] -= prevTXOutput.Amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the underlying type of the TX.
|
||||||
|
switch tx.Data.(type) {
|
||||||
|
case *transaction.RegisterTX:
|
||||||
|
case *transaction.IssueTX:
|
||||||
|
case *transaction.ClaimTX:
|
||||||
|
case *transaction.EnrollmentTX:
|
||||||
|
case *transaction.StateTX:
|
||||||
|
case *transaction.PublishTX:
|
||||||
|
case *transaction.InvocationTX:
|
||||||
|
log.Warn("invocation TX but we have no VM, o noo :(")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist all to storage.
|
||||||
|
if err := accounts.commit(batch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := unspentCoins.commit(batch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := bc.PutBatch(batch); err != nil {
|
if err := bc.PutBatch(batch); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.AddUint32(&bc.blockHeight, 1)
|
atomic.AddUint32(&bc.blockHeight, 1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -302,6 +394,27 @@ func (bc *Blockchain) headerListLen() (n int) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTransaction returns a TX and its height by the given hash.
|
||||||
|
func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) {
|
||||||
|
key := storage.AppendPrefix(storage.DataTransaction, hash.BytesReverse())
|
||||||
|
b, err := bc.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
r := bytes.NewReader(b)
|
||||||
|
|
||||||
|
var height uint32
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &height); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := &transaction.Transaction{}
|
||||||
|
if err := tx.DecodeBinary(r); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return tx, height, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetBlock returns a Block by the given hash.
|
// GetBlock returns a Block by the given hash.
|
||||||
func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) {
|
func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) {
|
||||||
key := storage.AppendPrefix(storage.DataBlock, hash.BytesReverse())
|
key := storage.AppendPrefix(storage.DataBlock, hash.BytesReverse())
|
||||||
|
|
|
@ -122,6 +122,21 @@ func TestHasBlock(t *testing.T) {
|
||||||
assert.False(t, bc.HasBlock(newBlock.Hash()))
|
assert.False(t, bc.HasBlock(newBlock.Hash()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetTransaction(t *testing.T) {
|
||||||
|
block := getDecodedBlock(t, 1)
|
||||||
|
bc := newTestChain(t)
|
||||||
|
|
||||||
|
assert.Nil(t, bc.AddBlock(block))
|
||||||
|
assert.Nil(t, bc.persistBlock(block))
|
||||||
|
|
||||||
|
tx, height, err := bc.GetTransaction(block.Transactions[0].Hash())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, block.Index, height)
|
||||||
|
assert.Equal(t, block.Transactions[0], tx)
|
||||||
|
}
|
||||||
|
|
||||||
func newTestChain(t *testing.T) *Blockchain {
|
func newTestChain(t *testing.T) *Blockchain {
|
||||||
startHash, _ := util.Uint256DecodeString("a")
|
startHash, _ := util.Uint256DecodeString("a")
|
||||||
chain, err := NewBlockchain(storage.NewMemoryStore(), startHash)
|
chain, err := NewBlockchain(storage.NewMemoryStore(), startHash)
|
||||||
|
|
12
pkg/core/coin_state.go
Normal file
12
pkg/core/coin_state.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
// CoinState represents the state of a coin.
|
||||||
|
type CoinState uint8
|
||||||
|
|
||||||
|
// Viable CoinState constants.
|
||||||
|
const (
|
||||||
|
CoinStateConfirmed CoinState = 0
|
||||||
|
CoinStateSpent CoinState = 1 << 1
|
||||||
|
CoinStateClaimed CoinState = 1 << 2
|
||||||
|
CoinStateFrozen CoinState = 1 << 5
|
||||||
|
)
|
|
@ -51,7 +51,8 @@ func (s *LevelDBStore) Seek(key []byte, f func(k, v []byte)) {
|
||||||
iter.Release()
|
iter.Release()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Batch implements the Batch interface and returns a compatible Batch.
|
// Batch implements the Batch interface and returns a leveldb
|
||||||
|
// compatible Batch.
|
||||||
func (s *LevelDBStore) Batch() Batch {
|
func (s *LevelDBStore) Batch() Batch {
|
||||||
return new(leveldb.Batch)
|
return new(leveldb.Batch)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
// MemoryStore is an in-memory implementation of a Store, mainly
|
// MemoryStore is an in-memory implementation of a Store, mainly
|
||||||
// used for testing. Do not use MemoryStore in production.
|
// used for testing. Do not use MemoryStore in production.
|
||||||
type MemoryStore struct {
|
type MemoryStore struct {
|
||||||
|
@ -31,7 +35,7 @@ func NewMemoryStore() *MemoryStore {
|
||||||
|
|
||||||
// Get implements the Store interface.
|
// Get implements the Store interface.
|
||||||
func (s *MemoryStore) Get(key []byte) ([]byte, error) {
|
func (s *MemoryStore) Get(key []byte) ([]byte, error) {
|
||||||
if val, ok := s.mem[string(key)]; ok {
|
if val, ok := s.mem[makeKey(key)]; ok {
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
return nil, ErrKeyNotFound
|
return nil, ErrKeyNotFound
|
||||||
|
@ -39,7 +43,7 @@ func (s *MemoryStore) Get(key []byte) ([]byte, error) {
|
||||||
|
|
||||||
// Put implementes the Store interface.
|
// Put implementes the Store interface.
|
||||||
func (s *MemoryStore) Put(key, value []byte) error {
|
func (s *MemoryStore) Put(key, value []byte) error {
|
||||||
s.mem[string(key)] = value
|
s.mem[makeKey(key)] = value
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,3 +66,7 @@ func (s *MemoryStore) Batch() Batch {
|
||||||
m: make(map[*[]byte][]byte),
|
m: make(map[*[]byte][]byte),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeKey(k []byte) string {
|
||||||
|
return hex.EncodeToString(k)
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,17 @@ func TestGetPut(t *testing.T) {
|
||||||
assert.Equal(t, value, newVal)
|
assert.Equal(t, value, newVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKeyNotExist(t *testing.T) {
|
||||||
|
var (
|
||||||
|
s = NewMemoryStore()
|
||||||
|
key = []byte("sparse")
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err := s.Get(key)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, err.Error(), "key not found")
|
||||||
|
}
|
||||||
|
|
||||||
func TestPutBatch(t *testing.T) {
|
func TestPutBatch(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
s = NewMemoryStore()
|
s = NewMemoryStore()
|
||||||
|
|
|
@ -11,6 +11,7 @@ const (
|
||||||
DataTransaction KeyPrefix = 0x02
|
DataTransaction KeyPrefix = 0x02
|
||||||
STAccount KeyPrefix = 0x40
|
STAccount KeyPrefix = 0x40
|
||||||
STCoin KeyPrefix = 0x44
|
STCoin KeyPrefix = 0x44
|
||||||
|
STSpentCoin KeyPrefix = 0x45
|
||||||
STValidator KeyPrefix = 0x48
|
STValidator KeyPrefix = 0x48
|
||||||
STAsset KeyPrefix = 0x4c
|
STAsset KeyPrefix = 0x4c
|
||||||
STContract KeyPrefix = 0x50
|
STContract KeyPrefix = 0x50
|
||||||
|
|
28
pkg/core/transaction/enrollment.go
Normal file
28
pkg/core/transaction/enrollment.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package transaction
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Enrollment transaction represents an enrollment form, which indicates
|
||||||
|
// that the sponsor of the transaction would like to sign up as a validator.
|
||||||
|
// The way to sign up is: To construct an EnrollmentTransaction type of transaction,
|
||||||
|
// and send a deposit to the address of the PublicKey.
|
||||||
|
// The way to cancel the registration is: Spend the deposit on the address of the PublicKey.
|
||||||
|
type EnrollmentTX struct {
|
||||||
|
// PublicKey of the validator
|
||||||
|
PublicKey *crypto.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements the Payload interface.
|
||||||
|
func (tx *EnrollmentTX) DecodeBinary(r io.Reader) error {
|
||||||
|
tx.PublicKey = &crypto.PublicKey{}
|
||||||
|
return tx.PublicKey.DecodeBinary(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements the Payload interface.
|
||||||
|
func (tx *EnrollmentTX) EncodeBinary(w io.Writer) error {
|
||||||
|
return tx.PublicKey.EncodeBinary(w)
|
||||||
|
}
|
6
pkg/core/transaction/enrollment_test.go
Normal file
6
pkg/core/transaction/enrollment_test.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package transaction
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestDecodeEncode(t *testing.T) {
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/anthdm/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Input represents a Transaction input.
|
// Input represents a Transaction input.
|
||||||
|
|
30
pkg/core/transaction/publish.go
Normal file
30
pkg/core/transaction/publish.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package transaction
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublishTX represents a publish transaction.
|
||||||
|
// This is deprecated and should no longer be used.
|
||||||
|
type PublishTX struct {
|
||||||
|
Script []byte
|
||||||
|
ParamList []smartcontract.ParamType
|
||||||
|
ReturnType smartcontract.ParamType
|
||||||
|
NeedStorage bool
|
||||||
|
Name string
|
||||||
|
Author string
|
||||||
|
Email string
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements the Payload interface.
|
||||||
|
func (tx *PublishTX) DecodeBinary(r io.Reader) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements the Payload interface.
|
||||||
|
func (tx *PublishTX) EncodeBinary(w io.Writer) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -29,7 +29,7 @@ import (
|
||||||
// # 2. For currencies, use only unlimited models;
|
// # 2. For currencies, use only unlimited models;
|
||||||
// # 3. For point coupons, you can use any pattern;
|
// # 3. For point coupons, you can use any pattern;
|
||||||
|
|
||||||
// RegisterTx represents a register transaction.
|
// RegisterTX represents a register transaction.
|
||||||
type RegisterTX struct {
|
type RegisterTX struct {
|
||||||
// The type of the asset being registered.
|
// The type of the asset being registered.
|
||||||
AssetType AssetType
|
AssetType AssetType
|
||||||
|
@ -44,7 +44,9 @@ type RegisterTX struct {
|
||||||
// Decimals
|
// Decimals
|
||||||
Precision uint8
|
Precision uint8
|
||||||
|
|
||||||
Owner crypto.EllipticCurvePoint
|
// Public key of the owner
|
||||||
|
Owner *crypto.PublicKey
|
||||||
|
|
||||||
Admin util.Uint160
|
Admin util.Uint160
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,11 +67,10 @@ func (tx *RegisterTX) DecodeBinary(r io.Reader) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
point, err := crypto.NewEllipticCurvePointFromReader(r)
|
tx.Owner = &crypto.PublicKey{}
|
||||||
if err != nil {
|
if err := tx.Owner.DecodeBinary(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tx.Owner = point
|
|
||||||
|
|
||||||
return binary.Read(r, binary.LittleEndian, &tx.Admin)
|
return binary.Read(r, binary.LittleEndian, &tx.Admin)
|
||||||
}
|
}
|
||||||
|
|
29
pkg/core/transaction/state.go
Normal file
29
pkg/core/transaction/state.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package transaction
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateTX represents a state transaction.
|
||||||
|
type StateTX struct {
|
||||||
|
Descriptors []*StateDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements the Payload interface.
|
||||||
|
func (tx *StateTX) DecodeBinary(r io.Reader) error {
|
||||||
|
lenDesc := util.ReadVarUint(r)
|
||||||
|
for i := 0; i < int(lenDesc); i++ {
|
||||||
|
tx.Descriptors[i] = &StateDescriptor{}
|
||||||
|
if err := tx.Descriptors[i].DecodeBinary(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements the Payload interface.
|
||||||
|
func (tx *StateTX) EncodeBinary(w io.Writer) error {
|
||||||
|
return nil
|
||||||
|
}
|
58
pkg/core/transaction/state_descriptor.go
Normal file
58
pkg/core/transaction/state_descriptor.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package transaction
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DescStateType represents the type of StateDescriptor.
|
||||||
|
type DescStateType uint8
|
||||||
|
|
||||||
|
// Valid DescStateType constants.
|
||||||
|
const (
|
||||||
|
Account DescStateType = 0x40
|
||||||
|
Validator DescStateType = 0x48
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateDescriptor ..
|
||||||
|
type StateDescriptor struct {
|
||||||
|
Type DescStateType
|
||||||
|
Key []byte
|
||||||
|
Value []byte
|
||||||
|
Field string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements the Payload interface.
|
||||||
|
func (s *StateDescriptor) DecodeBinary(r io.Reader) error {
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &s.Type); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyLen := util.ReadVarUint(r)
|
||||||
|
s.Key = make([]byte, keyLen)
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, s.Key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
valLen := util.ReadVarUint(r)
|
||||||
|
s.Value = make([]byte, valLen)
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, s.Value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldLen := util.ReadVarUint(r)
|
||||||
|
field := make([]byte, fieldLen)
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, field); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Field = string(field)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements the Payload interface.
|
||||||
|
func (s *StateDescriptor) EncodeBinary(w io.Writer) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -147,6 +147,10 @@ func (t *Transaction) decodeData(r io.Reader) error {
|
||||||
case IssueType:
|
case IssueType:
|
||||||
t.Data = &IssueTX{}
|
t.Data = &IssueTX{}
|
||||||
return t.Data.(*IssueTX).DecodeBinary(r)
|
return t.Data.(*IssueTX).DecodeBinary(r)
|
||||||
|
case EnrollmentType:
|
||||||
|
t.Data = &EnrollmentTX{}
|
||||||
|
return t.Data.(*EnrollmentTX).DecodeBinary(r)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Warnf("invalid TX type %s", t.Type)
|
log.Warnf("invalid TX type %s", t.Type)
|
||||||
}
|
}
|
||||||
|
@ -180,8 +184,10 @@ func (t *Transaction) encodeHashableFields(w io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Underlying TXer.
|
// Underlying TXer.
|
||||||
if err := t.Data.EncodeBinary(w); err != nil {
|
if t.Data != nil {
|
||||||
return err
|
if err := t.Data.EncodeBinary(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attributes
|
// Attributes
|
||||||
|
@ -230,3 +236,12 @@ func (t *Transaction) createHash() (hash util.Uint256, err error) {
|
||||||
b = sha.Sum(nil)
|
b = sha.Sum(nil)
|
||||||
return util.Uint256DecodeBytes(util.ArrayReverse(b))
|
return util.Uint256DecodeBytes(util.ArrayReverse(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GroupTXInputsByPrevHash groups all TX inputs by their previous hash.
|
||||||
|
func (t *Transaction) GroupInputsByPrevHash() map[util.Uint256][]*Input {
|
||||||
|
m := make(map[util.Uint256][]*Input)
|
||||||
|
for _, in := range t.Inputs {
|
||||||
|
m[in.PrevHash] = append(m[in.PrevHash], in)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
81
pkg/core/unspent_coin_state.go
Normal file
81
pkg/core/unspent_coin_state.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnspentCoins is mapping between transactions and their unspent
|
||||||
|
// coin state.
|
||||||
|
type UnspentCoins map[util.Uint256]*UnspentCoinState
|
||||||
|
|
||||||
|
func (u UnspentCoins) getAndChange(s storage.Store, hash util.Uint256) (*UnspentCoinState, error) {
|
||||||
|
if unspent, ok := u[hash]; ok {
|
||||||
|
return unspent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var unspent *UnspentCoinState
|
||||||
|
key := storage.AppendPrefix(storage.STCoin, hash.BytesReverse())
|
||||||
|
if b, err := s.Get(key); err == nil {
|
||||||
|
if err := unspent.DecodeBinary(bytes.NewReader(b)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unspent = &UnspentCoinState{
|
||||||
|
states: []CoinState{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u[hash] = unspent
|
||||||
|
return unspent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnspentCoinState hold the state of a unspent coin.
|
||||||
|
type UnspentCoinState struct {
|
||||||
|
states []CoinState
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit writes all unspent coin states to the given Batch.
|
||||||
|
func (s UnspentCoins) commit(b storage.Batch) error {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
for hash, state := range s {
|
||||||
|
if err := state.EncodeBinary(buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key := storage.AppendPrefix(storage.STCoin, hash.BytesReverse())
|
||||||
|
b.Put(key, buf.Bytes())
|
||||||
|
buf.Reset()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary encodes UnspentCoinState to the given io.Writer.
|
||||||
|
func (s *UnspentCoinState) EncodeBinary(w io.Writer) error {
|
||||||
|
if err := util.WriteVarUint(w, uint64(len(s.states))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, state := range s.states {
|
||||||
|
if err := binary.Write(w, binary.LittleEndian, byte(state)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodBinary decodes UnspentCoinState from the given io.Reader.
|
||||||
|
func (s *UnspentCoinState) DecodeBinary(r io.Reader) error {
|
||||||
|
lenStates := util.ReadVarUint(r)
|
||||||
|
s.states = make([]CoinState, lenStates)
|
||||||
|
for i := 0; i < int(lenStates); i++ {
|
||||||
|
var state uint8
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &state); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.states[i] = CoinState(state)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
59
pkg/core/unspent_coint_state_test.go
Normal file
59
pkg/core/unspent_coint_state_test.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecodeEncode(t *testing.T) {
|
||||||
|
unspent := &UnspentCoinState{
|
||||||
|
states: []CoinState{
|
||||||
|
CoinStateConfirmed,
|
||||||
|
CoinStateSpent,
|
||||||
|
CoinStateSpent,
|
||||||
|
CoinStateSpent,
|
||||||
|
CoinStateConfirmed,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
assert.Nil(t, unspent.EncodeBinary(buf))
|
||||||
|
unspentDecode := &UnspentCoinState{}
|
||||||
|
assert.Nil(t, unspentDecode.DecodeBinary(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommit(t *testing.T) {
|
||||||
|
var (
|
||||||
|
store = storage.NewMemoryStore()
|
||||||
|
batch = store.Batch()
|
||||||
|
unspentCoins = make(UnspentCoins)
|
||||||
|
)
|
||||||
|
|
||||||
|
txA := util.RandomUint256()
|
||||||
|
txB := util.RandomUint256()
|
||||||
|
txC := util.RandomUint256()
|
||||||
|
|
||||||
|
unspentCoins[txA] = &UnspentCoinState{
|
||||||
|
states: []CoinState{CoinStateConfirmed},
|
||||||
|
}
|
||||||
|
unspentCoins[txB] = &UnspentCoinState{
|
||||||
|
states: []CoinState{
|
||||||
|
CoinStateConfirmed,
|
||||||
|
CoinStateConfirmed,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
unspentCoins[txC] = &UnspentCoinState{
|
||||||
|
states: []CoinState{
|
||||||
|
CoinStateConfirmed,
|
||||||
|
CoinStateConfirmed,
|
||||||
|
CoinStateConfirmed,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Nil(t, unspentCoins.commit(batch))
|
||||||
|
assert.Nil(t, store.PutBatch(batch))
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -65,6 +66,22 @@ func storeAsBlock(batch storage.Batch, block *Block, sysFee uint32) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// storeAsTransaction stores the given TX as DataTransaction.
|
||||||
|
func storeAsTransaction(batch storage.Batch, tx *transaction.Transaction, index uint32) error {
|
||||||
|
key := storage.AppendPrefix(storage.DataTransaction, tx.Hash().BytesReverse())
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := tx.EncodeBinary(buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := make([]byte, buf.Len()+4)
|
||||||
|
binary.LittleEndian.PutUint32(dest[:4], index)
|
||||||
|
copy(dest[4:], buf.Bytes())
|
||||||
|
batch.Put(key, dest)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// readStoredHeaderHashes returns a sorted list of header hashes
|
// readStoredHeaderHashes returns a sorted list of header hashes
|
||||||
// retrieved from the given Store.
|
// retrieved from the given Store.
|
||||||
func readStoredHeaderHashes(store storage.Store) ([]util.Uint256, error) {
|
func readStoredHeaderHashes(store storage.Store) ([]util.Uint256, error) {
|
||||||
|
|
16
pkg/core/validator_state.go
Normal file
16
pkg/core/validator_state.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validators is a mapping between public keys and ValidatorState.
|
||||||
|
type Validators map[*crypto.PublicKey]*ValidatorState
|
||||||
|
|
||||||
|
// ValidatorState holds the state of a validator.
|
||||||
|
type ValidatorState struct {
|
||||||
|
PublicKey *crypto.PublicKey
|
||||||
|
Registered bool
|
||||||
|
Votes util.Fixed8
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ package crypto
|
||||||
// Expanded and tweaked upon here under MIT license.
|
// Expanded and tweaked upon here under MIT license.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -21,51 +23,18 @@ type (
|
||||||
A *big.Int
|
A *big.Int
|
||||||
B *big.Int
|
B *big.Int
|
||||||
P *big.Int
|
P *big.Int
|
||||||
G EllipticCurvePoint
|
G ECPoint
|
||||||
N *big.Int
|
N *big.Int
|
||||||
H *big.Int
|
H *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
// EllipticCurveEllipticCurvePoint represents a point on the EllipticCurve.
|
// ECPoint represents a point on the EllipticCurve.
|
||||||
EllipticCurvePoint struct {
|
ECPoint struct {
|
||||||
X *big.Int
|
X *big.Int
|
||||||
Y *big.Int
|
Y *big.Int
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewEllipticCurvePointFromReader return a new point from the given reader.
|
|
||||||
// f == 4, 6 or 7 are not implemented.
|
|
||||||
func NewEllipticCurvePointFromReader(r io.Reader) (point EllipticCurvePoint, err error) {
|
|
||||||
var f uint8
|
|
||||||
if err = binary.Read(r, binary.LittleEndian, &f); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infinity
|
|
||||||
if f == 0 {
|
|
||||||
return EllipticCurvePoint{
|
|
||||||
X: new(big.Int),
|
|
||||||
Y: new(big.Int),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if f == 2 || f == 3 {
|
|
||||||
y := new(big.Int).SetBytes([]byte{f & 1})
|
|
||||||
data := make([]byte, 32)
|
|
||||||
if err = binary.Read(r, binary.LittleEndian, data); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data = util.ArrayReverse(data)
|
|
||||||
data = append(data, byte(0x00))
|
|
||||||
|
|
||||||
return EllipticCurvePoint{
|
|
||||||
X: new(big.Int).SetBytes(data),
|
|
||||||
Y: y,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEllipticCurve returns a ready to use EllipticCurve with preconfigured
|
// NewEllipticCurve returns a ready to use EllipticCurve with preconfigured
|
||||||
// fields for the NEO protocol.
|
// fields for the NEO protocol.
|
||||||
func NewEllipticCurve() EllipticCurve {
|
func NewEllipticCurve() EllipticCurve {
|
||||||
|
@ -94,9 +63,84 @@ func NewEllipticCurve() EllipticCurve {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *EllipticCurvePoint) format() string {
|
// RandomECPoint returns a random generated ECPoint, mostly used
|
||||||
if p.X == nil && p.Y == nil {
|
// for testing.
|
||||||
return "(inf,inf)"
|
func RandomECPoint() ECPoint {
|
||||||
|
c := NewEllipticCurve()
|
||||||
|
b := make([]byte, c.N.BitLen()/8+8)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||||
|
return ECPoint{}
|
||||||
|
}
|
||||||
|
|
||||||
|
d := new(big.Int).SetBytes(b)
|
||||||
|
d.Mod(d, new(big.Int).Sub(c.N, big.NewInt(1)))
|
||||||
|
d.Add(d, big.NewInt(1))
|
||||||
|
|
||||||
|
q := new(big.Int).SetBytes(d.Bytes())
|
||||||
|
return c.ScalarBaseMult(q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECPointFromReader return a new point from the given reader.
|
||||||
|
// f == 4, 6 or 7 are not implemented.
|
||||||
|
func ECPointFromReader(r io.Reader) (point ECPoint, err error) {
|
||||||
|
var f uint8
|
||||||
|
if err = binary.Read(r, binary.LittleEndian, &f); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(f)
|
||||||
|
|
||||||
|
// Infinity
|
||||||
|
if f == 0 {
|
||||||
|
return ECPoint{
|
||||||
|
X: new(big.Int),
|
||||||
|
Y: new(big.Int),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if f == 2 || f == 3 {
|
||||||
|
y := new(big.Int).SetBytes([]byte{f & 1})
|
||||||
|
data := make([]byte, 32)
|
||||||
|
if err = binary.Read(r, binary.LittleEndian, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = util.ArrayReverse(data)
|
||||||
|
data = append(data, byte(0x00))
|
||||||
|
|
||||||
|
return ECPoint{
|
||||||
|
X: new(big.Int).SetBytes(data),
|
||||||
|
Y: y,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary encodes the point to the given io.Writer.
|
||||||
|
func (p ECPoint) EncodeBinary(w io.Writer) error {
|
||||||
|
bx := p.X.Bytes()
|
||||||
|
padded := append(
|
||||||
|
bytes.Repeat(
|
||||||
|
[]byte{0x00},
|
||||||
|
32-len(bx),
|
||||||
|
),
|
||||||
|
bx...,
|
||||||
|
)
|
||||||
|
|
||||||
|
prefix := byte(0x03)
|
||||||
|
if p.Y.Bit(0) == 0 {
|
||||||
|
prefix = byte(0x02)
|
||||||
|
}
|
||||||
|
buf := make([]byte, len(padded)+1)
|
||||||
|
buf[0] = prefix
|
||||||
|
copy(buf[1:], padded)
|
||||||
|
|
||||||
|
return binary.Write(w, binary.LittleEndian, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements the Stringer interface.
|
||||||
|
func (p *ECPoint) String() string {
|
||||||
|
if p.IsInfinity() {
|
||||||
|
return "(inf, inf)"
|
||||||
}
|
}
|
||||||
bx := hex.EncodeToString(p.X.Bytes())
|
bx := hex.EncodeToString(p.X.Bytes())
|
||||||
by := hex.EncodeToString(p.Y.Bytes())
|
by := hex.EncodeToString(p.Y.Bytes())
|
||||||
|
@ -104,15 +148,17 @@ func (p *EllipticCurvePoint) format() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsInfinity checks if point P is infinity on EllipticCurve ec.
|
// IsInfinity checks if point P is infinity on EllipticCurve ec.
|
||||||
func (c *EllipticCurve) IsInfinity(P EllipticCurvePoint) bool {
|
func (p *ECPoint) IsInfinity() bool {
|
||||||
if P.X == nil && P.Y == nil {
|
return p.X == nil && p.Y == nil
|
||||||
return true
|
}
|
||||||
}
|
|
||||||
return false
|
// IsInfinity checks if point P is infinity on EllipticCurve ec.
|
||||||
|
func (c *EllipticCurve) IsInfinity(P ECPoint) bool {
|
||||||
|
return P.X == nil && P.Y == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsOnCurve checks if point P is on EllipticCurve ec.
|
// IsOnCurve checks if point P is on EllipticCurve ec.
|
||||||
func (c *EllipticCurve) IsOnCurve(P EllipticCurvePoint) bool {
|
func (c *EllipticCurve) IsOnCurve(P ECPoint) bool {
|
||||||
if c.IsInfinity(P) {
|
if c.IsInfinity(P) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -130,7 +176,7 @@ func (c *EllipticCurve) IsOnCurve(P EllipticCurvePoint) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add computes R = P + Q on EllipticCurve ec.
|
// Add computes R = P + Q on EllipticCurve ec.
|
||||||
func (c *EllipticCurve) Add(P, Q EllipticCurvePoint) (R EllipticCurvePoint) {
|
func (c *EllipticCurve) Add(P, Q ECPoint) (R ECPoint) {
|
||||||
// See rules 1-5 on SEC1 pg.7 http://www.secg.org/collateral/sec1_final.pdf
|
// See rules 1-5 on SEC1 pg.7 http://www.secg.org/collateral/sec1_final.pdf
|
||||||
if c.IsInfinity(P) && c.IsInfinity(Q) {
|
if c.IsInfinity(P) && c.IsInfinity(Q) {
|
||||||
R.X = nil
|
R.X = nil
|
||||||
|
@ -172,18 +218,18 @@ func (c *EllipticCurve) Add(P, Q EllipticCurvePoint) (R EllipticCurvePoint) {
|
||||||
subMod(P.X, R.X, c.P), c.P),
|
subMod(P.X, R.X, c.P), c.P),
|
||||||
P.Y, c.P)
|
P.Y, c.P)
|
||||||
} else {
|
} else {
|
||||||
panic(fmt.Sprintf("Unsupported point addition: %v + %v", P.format(), Q.format()))
|
panic(fmt.Sprintf("Unsupported point addition: %v + %v", P, Q))
|
||||||
}
|
}
|
||||||
|
|
||||||
return R
|
return R
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScalarMult computes Q = k * P on EllipticCurve ec.
|
// ScalarMult computes Q = k * P on EllipticCurve ec.
|
||||||
func (c *EllipticCurve) ScalarMult(k *big.Int, P EllipticCurvePoint) (Q EllipticCurvePoint) {
|
func (c *EllipticCurve) ScalarMult(k *big.Int, P ECPoint) (Q ECPoint) {
|
||||||
// Implementation based on pseudocode here:
|
// Implementation based on pseudocode here:
|
||||||
// https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
|
// https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
|
||||||
var R0 EllipticCurvePoint
|
var R0 ECPoint
|
||||||
var R1 EllipticCurvePoint
|
var R1 ECPoint
|
||||||
|
|
||||||
R0.X = nil
|
R0.X = nil
|
||||||
R0.Y = nil
|
R0.Y = nil
|
||||||
|
@ -203,12 +249,12 @@ func (c *EllipticCurve) ScalarMult(k *big.Int, P EllipticCurvePoint) (Q Elliptic
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScalarBaseMult computes Q = k * G on EllipticCurve ec.
|
// ScalarBaseMult computes Q = k * G on EllipticCurve ec.
|
||||||
func (c *EllipticCurve) ScalarBaseMult(k *big.Int) (Q EllipticCurvePoint) {
|
func (c *EllipticCurve) ScalarBaseMult(k *big.Int) (Q ECPoint) {
|
||||||
return c.ScalarMult(k, c.G)
|
return c.ScalarMult(k, c.G)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decompress decompresses coordinate x and ylsb (y's least significant bit) into a EllipticCurvePoint P on EllipticCurve ec.
|
// Decompress decompresses coordinate x and ylsb (y's least significant bit) into a ECPoint P on EllipticCurve ec.
|
||||||
func (c *EllipticCurve) Decompress(x *big.Int, ylsb uint) (P EllipticCurvePoint, err error) {
|
func (c *EllipticCurve) Decompress(x *big.Int, ylsb uint) (P ECPoint, err error) {
|
||||||
/* y**2 = x**3 + a*x + b % p */
|
/* y**2 = x**3 + a*x + b % p */
|
||||||
rhs := addMod(
|
rhs := addMod(
|
||||||
addMod(
|
addMod(
|
||||||
|
|
68
pkg/crypto/public_key.go
Normal file
68
pkg/crypto/public_key.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicKey represents a public key.
|
||||||
|
type PublicKey struct {
|
||||||
|
ECPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the byte array representation of the public key.
|
||||||
|
func (p *PublicKey) Bytes() []byte {
|
||||||
|
var (
|
||||||
|
x = p.X.Bytes()
|
||||||
|
paddedX = append(bytes.Repeat([]byte{0x00}, 32-len(x)), x...)
|
||||||
|
prefix = byte(0x03)
|
||||||
|
)
|
||||||
|
|
||||||
|
if p.Y.Bit(0) == 0 {
|
||||||
|
prefix = byte(0x02)
|
||||||
|
}
|
||||||
|
|
||||||
|
return append([]byte{prefix}, paddedX...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary decodes a PublicKey from the given io.Reader.
|
||||||
|
func (p *PublicKey) DecodeBinary(r io.Reader) error {
|
||||||
|
var prefix uint8
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &prefix); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compressed public keys.
|
||||||
|
if prefix == 0x02 || prefix == 0x03 {
|
||||||
|
c := NewEllipticCurve()
|
||||||
|
b := make([]byte, 32)
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
p.ECPoint, err = c.Decompress(new(big.Int).SetBytes(b), uint(prefix&0x1))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if prefix == 0x04 {
|
||||||
|
buf := make([]byte, 65)
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.X = new(big.Int).SetBytes(buf[1:33])
|
||||||
|
p.Y = new(big.Int).SetBytes(buf[33:65])
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid prefix %d", prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary encodes a PublicKey to the given io.Writer.
|
||||||
|
func (p *PublicKey) EncodeBinary(w io.Writer) error {
|
||||||
|
return binary.Write(w, binary.LittleEndian, p.Bytes())
|
||||||
|
}
|
20
pkg/crypto/public_key_test.go
Normal file
20
pkg/crypto/public_key_test.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncodeDecodePublicKey(t *testing.T) {
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
p := &PublicKey{RandomECPoint()}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
assert.Nil(t, p.EncodeBinary(buf))
|
||||||
|
|
||||||
|
pDecode := &PublicKey{}
|
||||||
|
assert.Nil(t, pDecode.DecodeBinary(buf))
|
||||||
|
assert.Equal(t, p.X, pDecode.X)
|
||||||
|
}
|
||||||
|
}
|
45
pkg/util/test_util.go
Normal file
45
pkg/util/test_util.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ripemd160"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RandomString returns a random string with the n as its length.
|
||||||
|
func RandomString(n int) string {
|
||||||
|
b := make([]byte, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = byte(RandomInt(65, 90))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RamdomInt returns a ramdom integer betweeen min and max.
|
||||||
|
func RandomInt(min, max int) int {
|
||||||
|
return min + rand.Intn(max-min)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandomUint256 returns a random Uint256.
|
||||||
|
func RandomUint256() Uint256 {
|
||||||
|
str := RandomString(20)
|
||||||
|
h := sha256.Sum256([]byte(str))
|
||||||
|
return Uint256(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandomUint160 returns a random Uint160.
|
||||||
|
func RandomUint160() Uint160 {
|
||||||
|
str := RandomString(20)
|
||||||
|
ripemd := ripemd160.New()
|
||||||
|
ripemd.Write([]byte(str))
|
||||||
|
h := ripemd.Sum(nil)
|
||||||
|
v, _ := Uint160DecodeBytes(h)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
|
}
|
Loading…
Reference in a new issue