Base wallet implementation (#35)
* Initial draft of the neo-go wallet * Cleanup + more test for util package * integrated wallet into neo-cli partially * base wallet implementation + smartcontract code.
This commit is contained in:
parent
942650dd8b
commit
f3f6662fc9
33 changed files with 1754 additions and 87 deletions
52
Gopkg.lock
generated
52
Gopkg.lock
generated
|
@ -2,19 +2,10 @@
|
|||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/CityOfZion/neo-go"
|
||||
packages = [
|
||||
"cli/server",
|
||||
"cli/smartcontract",
|
||||
"pkg/core",
|
||||
"pkg/network",
|
||||
"pkg/network/payload",
|
||||
"pkg/util",
|
||||
"pkg/vm",
|
||||
"pkg/vm/compiler"
|
||||
]
|
||||
revision = "8fe079ec8ebb83ce53d6f1535bfe513271231958"
|
||||
version = "0.11.0"
|
||||
branch = "master"
|
||||
name = "github.com/anthdm/rfc6979"
|
||||
packages = ["."]
|
||||
revision = "6a90f24967ebb1aa57b22f74a13dbb3faad8cf3d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -47,9 +38,42 @@
|
|||
revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
|
||||
version = "v1.20.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"pbkdf2",
|
||||
"ripemd160",
|
||||
"scrypt"
|
||||
]
|
||||
revision = "8c653846df49742c4c85ec37e5d9f8d3ba657895"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"internal/gen",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"transform",
|
||||
"unicode/cldr",
|
||||
"unicode/norm"
|
||||
]
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/tools"
|
||||
packages = [
|
||||
"go/ast/astutil",
|
||||
"go/buildutil",
|
||||
"go/loader"
|
||||
]
|
||||
revision = "73e16cff9e0d4a802937444bebb562458548241d"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "ac1d2cc7e9e2cb3985a84af2b75ad9fd78a3b8278abb6d5f7b6dd2210df7c6a2"
|
||||
inputs-digest = "74c6b0a11057b8c0fd68342f85e010e323cda6407bec2a889f161b5890928aaf"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
12
Gopkg.toml
12
Gopkg.toml
|
@ -35,3 +35,15 @@
|
|||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/anthdm/rfc6979"
|
||||
|
||||
[[constraint]]
|
||||
name = "golang.org/x/text"
|
||||
version = "0.3.0"
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.18.0
|
||||
0.19.0
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/CityOfZion/neo-go/cli/server"
|
||||
"github.com/CityOfZion/neo-go/cli/smartcontract"
|
||||
"github.com/CityOfZion/neo-go/cli/wallet"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
@ -16,6 +17,7 @@ func main() {
|
|||
ctl.Commands = []cli.Command{
|
||||
server.NewCommand(),
|
||||
smartcontract.NewCommand(),
|
||||
wallet.NewCommand(),
|
||||
}
|
||||
|
||||
ctl.Run(os.Args)
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// NewCommand creates a new Node command
|
||||
// NewCommand creates a new Node command.
|
||||
func NewCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "node",
|
||||
|
|
116
cli/wallet/wallet.go
Normal file
116
cli/wallet/wallet.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoPath = errors.New("Target path where the wallet should be stored is mandatory and should be passed using (--path, -p) flags.")
|
||||
errPhraseMissmatch = errors.New("The entered passphrases do not match. Maybe you have misspelled them?")
|
||||
)
|
||||
|
||||
// NewComand creates a new Wallet command.
|
||||
func NewCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "wallet",
|
||||
Usage: "create, open and manage a NEO wallet",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "create",
|
||||
Usage: "create a new wallet",
|
||||
Action: createWallet,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "path, p",
|
||||
Usage: "Target location of the wallet file.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "account, a",
|
||||
Usage: "Create a new account",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "open",
|
||||
Usage: "open a existing NEO wallet",
|
||||
Action: openWallet,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "path, p",
|
||||
Usage: "Target location of the wallet file.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func openWallet(ctx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createWallet(ctx *cli.Context) error {
|
||||
path := ctx.String("path")
|
||||
if len(path) == 0 {
|
||||
return cli.NewExitError(errNoPath, 1)
|
||||
}
|
||||
wall, err := wallet.NewWallet(path)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
if err := wall.Save(); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
if ctx.Bool("account") {
|
||||
if err := createAccount(ctx, wall); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
}
|
||||
|
||||
dumpWallet(wall)
|
||||
fmt.Printf("wallet succesfully created, file location is %s\n", wall.Path())
|
||||
return nil
|
||||
}
|
||||
|
||||
func createAccount(ctx *cli.Context, wall *wallet.Wallet) error {
|
||||
var (
|
||||
rawName,
|
||||
rawPhrase,
|
||||
rawPhraseCheck []byte
|
||||
)
|
||||
buf := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("Enter the name of the account > ")
|
||||
rawName, _ = buf.ReadBytes('\n')
|
||||
fmt.Print("Enter passphrase > ")
|
||||
rawPhrase, _ = buf.ReadBytes('\n')
|
||||
fmt.Print("Confirm passphrase > ")
|
||||
rawPhraseCheck, _ = buf.ReadBytes('\n')
|
||||
|
||||
// Clean data
|
||||
var (
|
||||
name = strings.TrimRight(string(rawName), "\n")
|
||||
phrase = strings.TrimRight(string(rawPhrase), "\n")
|
||||
phraseCheck = strings.TrimRight(string(rawPhraseCheck), "\n")
|
||||
)
|
||||
|
||||
if phrase != phraseCheck {
|
||||
return errPhraseMissmatch
|
||||
}
|
||||
|
||||
return wall.CreateAccount(name, phrase)
|
||||
}
|
||||
|
||||
func dumpWallet(wall *wallet.Wallet) {
|
||||
b, _ := wall.JSON()
|
||||
fmt.Println("")
|
||||
fmt.Println(string(b))
|
||||
fmt.Println("")
|
||||
}
|
|
@ -84,7 +84,7 @@ func (b *BlockBase) Hash() (hash util.Uint256, err error) {
|
|||
|
||||
// Double hash the encoded fields.
|
||||
hash = sha256.Sum256(buf.Bytes())
|
||||
hash = sha256.Sum256(hash.ToSlice())
|
||||
hash = sha256.Sum256(hash.Bytes())
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ func (bc *Blockchain) genesisBlock() *Block {
|
|||
// togheter much faster.
|
||||
// For more information about the genesis block:
|
||||
// https://neotracker.io/block/height/0
|
||||
mr, _ := util.Uint256DecodeFromString("803ff4abe3ea6533bcc0be574efa02f83ae8fdc651c879056b0d9be336c01bf4")
|
||||
mr, _ := util.Uint256DecodeString("803ff4abe3ea6533bcc0be574efa02f83ae8fdc651c879056b0d9be336c01bf4")
|
||||
|
||||
return &Block{
|
||||
BlockBase: BlockBase{
|
||||
|
@ -171,7 +171,7 @@ func (bc *Blockchain) processHeader(h *Header, batch Batch) error {
|
|||
return err
|
||||
}
|
||||
|
||||
preBlock := preDataBlock.add(hash.ToSliceReverse())
|
||||
preBlock := preDataBlock.add(hash.BytesReverse())
|
||||
batch[&preBlock] = buf.Bytes()
|
||||
preHeader := preSYSCurrentHeader.toSlice()
|
||||
batch[&preHeader] = hashAndIndexToBytes(hash, h.Index)
|
||||
|
@ -209,5 +209,5 @@ func (bc *Blockchain) HeaderHeight() uint32 {
|
|||
func hashAndIndexToBytes(h util.Uint256, index uint32) []byte {
|
||||
buf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(buf, index)
|
||||
return append(h.ToSliceReverse(), buf...)
|
||||
return append(h.BytesReverse(), buf...)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
func TestNewBlockchain(t *testing.T) {
|
||||
startHash, _ := util.Uint256DecodeFromString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099")
|
||||
startHash, _ := util.Uint256DecodeString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099")
|
||||
bc := NewBlockchain(nil, nil, startHash)
|
||||
|
||||
want := uint32(0)
|
||||
|
@ -28,7 +28,7 @@ func TestNewBlockchain(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAddHeaders(t *testing.T) {
|
||||
startHash, _ := util.Uint256DecodeFromString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099")
|
||||
startHash, _ := util.Uint256DecodeString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099")
|
||||
bc := NewBlockchain(NewMemoryStore(), log.New(os.Stdout, "", 0), startHash)
|
||||
|
||||
h1 := &Header{BlockBase: BlockBase{Version: 0, Index: 1, Script: &Witness{}}}
|
||||
|
|
93
pkg/crypto/aes256.go
Normal file
93
pkg/crypto/aes256.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
)
|
||||
|
||||
// AESEncrypt encrypts the key with the given source.
|
||||
func AESEncrypt(src, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ecb := newECBEncrypter(block)
|
||||
out := make([]byte, len(src))
|
||||
ecb.CryptBlocks(out, src)
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AESDecrypt decrypts the encrypted source with the given key.
|
||||
func AESDecrypt(crypted, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockMode := newECBDecrypter(block)
|
||||
out := make([]byte, len(crypted))
|
||||
blockMode.CryptBlocks(out, crypted)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type ecb struct {
|
||||
b cipher.Block
|
||||
blockSize int
|
||||
}
|
||||
|
||||
func newECB(b cipher.Block) *ecb {
|
||||
return &ecb{
|
||||
b: b,
|
||||
blockSize: b.BlockSize(),
|
||||
}
|
||||
}
|
||||
|
||||
type ecbEncrypter ecb
|
||||
|
||||
func newECBEncrypter(b cipher.Block) cipher.BlockMode {
|
||||
return (*ecbEncrypter)(newECB(b))
|
||||
}
|
||||
|
||||
func (ecb *ecbEncrypter) BlockSize() int {
|
||||
return ecb.blockSize
|
||||
}
|
||||
|
||||
func (ecb *ecbEncrypter) CryptBlocks(dst, src []byte) {
|
||||
if len(src)%ecb.blockSize != 0 {
|
||||
panic("crypto/cipher: input not full blocks")
|
||||
}
|
||||
if len(dst) < len(src) {
|
||||
panic("crypto/cipher: output smaller than input")
|
||||
}
|
||||
for len(src) > 0 {
|
||||
ecb.b.Encrypt(dst, src[:ecb.blockSize])
|
||||
src = src[ecb.blockSize:]
|
||||
dst = dst[ecb.blockSize:]
|
||||
}
|
||||
}
|
||||
|
||||
type ecbDecrypter ecb
|
||||
|
||||
func newECBDecrypter(b cipher.Block) cipher.BlockMode {
|
||||
return (*ecbDecrypter)(newECB(b))
|
||||
}
|
||||
|
||||
func (ecb ecbDecrypter) BlockSize() int {
|
||||
return ecb.blockSize
|
||||
}
|
||||
|
||||
func (ecb *ecbDecrypter) CryptBlocks(dst, src []byte) {
|
||||
if len(src)%ecb.blockSize != 0 {
|
||||
panic("crypto/cipher: input not full blocks")
|
||||
}
|
||||
if len(dst) < len(src) {
|
||||
panic("crypto/cipher: output smaller than input")
|
||||
}
|
||||
for len(src) > 0 {
|
||||
ecb.b.Decrypt(dst, src[:ecb.blockSize])
|
||||
src = src[ecb.blockSize:]
|
||||
dst = dst[ecb.blockSize:]
|
||||
}
|
||||
}
|
130
pkg/crypto/base58.go
Normal file
130
pkg/crypto/base58.go
Normal file
|
@ -0,0 +1,130 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
const prefix rune = '1'
|
||||
|
||||
var decodeMap = map[rune]int64{
|
||||
'1': 0, '2': 1, '3': 2, '4': 3, '5': 4,
|
||||
'6': 5, '7': 6, '8': 7, '9': 8, 'A': 9,
|
||||
'B': 10, 'C': 11, 'D': 12, 'E': 13, 'F': 14,
|
||||
'G': 15, 'H': 16, 'J': 17, 'K': 18, 'L': 19,
|
||||
'M': 20, 'N': 21, 'P': 22, 'Q': 23, 'R': 24,
|
||||
'S': 25, 'T': 26, 'U': 27, 'V': 28, 'W': 29,
|
||||
'X': 30, 'Y': 31, 'Z': 32, 'a': 33, 'b': 34,
|
||||
'c': 35, 'd': 36, 'e': 37, 'f': 38, 'g': 39,
|
||||
'h': 40, 'i': 41, 'j': 42, 'k': 43, 'm': 44,
|
||||
'n': 45, 'o': 46, 'p': 47, 'q': 48, 'r': 49,
|
||||
's': 50, 't': 51, 'u': 52, 'v': 53, 'w': 54,
|
||||
'x': 55, 'y': 56, 'z': 57,
|
||||
}
|
||||
|
||||
// Base58Decode decodes the base58 encoded string.
|
||||
func Base58Decode(s string) ([]byte, error) {
|
||||
var (
|
||||
startIndex = 0
|
||||
zero = 0
|
||||
)
|
||||
for i, c := range s {
|
||||
if c == prefix {
|
||||
zero++
|
||||
} else {
|
||||
startIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
n = big.NewInt(0)
|
||||
div = big.NewInt(58)
|
||||
)
|
||||
for _, c := range s[startIndex:] {
|
||||
charIndex, ok := decodeMap[c]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"invalid character '%c' when decoding this base58 string: '%s'", c, s,
|
||||
)
|
||||
}
|
||||
n.Add(n.Mul(n, div), big.NewInt(charIndex))
|
||||
}
|
||||
|
||||
out := n.Bytes()
|
||||
buf := make([]byte, (zero + len(out)))
|
||||
copy(buf[zero:], out[:])
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Base58Encode encodes a byte slice to be a base58 encoded string.
|
||||
func Base58Encode(bytes []byte) string {
|
||||
var (
|
||||
lookupTable = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
x = new(big.Int).SetBytes(bytes)
|
||||
r = new(big.Int)
|
||||
m = big.NewInt(58)
|
||||
zero = big.NewInt(0)
|
||||
encoded string
|
||||
)
|
||||
|
||||
for x.Cmp(zero) > 0 {
|
||||
x.QuoRem(x, m, r)
|
||||
encoded = string(lookupTable[r.Int64()]) + encoded
|
||||
}
|
||||
|
||||
return encoded
|
||||
}
|
||||
|
||||
func Base58CheckDecode(s string) (b []byte, err error) {
|
||||
b, err = Base58Decode(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] != '1' {
|
||||
break
|
||||
}
|
||||
b = append([]byte{0x00}, b...)
|
||||
}
|
||||
|
||||
if len(b) < 5 {
|
||||
return nil, fmt.Errorf("Invalid base-58 check string: missing checksum.")
|
||||
}
|
||||
|
||||
sha := sha256.New()
|
||||
sha.Write(b[:len(b)-4])
|
||||
hash := sha.Sum(nil)
|
||||
|
||||
sha.Reset()
|
||||
sha.Write(hash)
|
||||
hash = sha.Sum(nil)
|
||||
|
||||
if bytes.Compare(hash[0:4], b[len(b)-4:]) != 0 {
|
||||
return nil, fmt.Errorf("Invalid base-58 check string: invalid checksum.")
|
||||
}
|
||||
|
||||
// Strip the 4 byte long hash.
|
||||
b = b[:len(b)-4]
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Base58checkEncode encodes b into a base-58 check encoded string.
|
||||
func Base58CheckEncode(b []byte) string {
|
||||
sha := sha256.New()
|
||||
sha.Write(b)
|
||||
hash := sha.Sum(nil)
|
||||
|
||||
sha.Reset()
|
||||
sha.Write(hash)
|
||||
hash = sha.Sum(nil)
|
||||
|
||||
b = append(b, hash[0:4]...)
|
||||
|
||||
return Base58Encode(b)
|
||||
}
|
196
pkg/crypto/elliptic_curve.go
Normal file
196
pkg/crypto/elliptic_curve.go
Normal file
|
@ -0,0 +1,196 @@
|
|||
package crypto
|
||||
|
||||
// Original work completed by @vsergeev: https://github.com/vsergeev/btckeygenie
|
||||
// Expanded and tweaked upon here under MIT license.
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type (
|
||||
// EllipticCurve represents the parameters of a short Weierstrass equation elliptic
|
||||
// curve.
|
||||
EllipticCurve struct {
|
||||
A *big.Int
|
||||
B *big.Int
|
||||
P *big.Int
|
||||
G EllipticCurvePoint
|
||||
N *big.Int
|
||||
H *big.Int
|
||||
}
|
||||
|
||||
// EllipticCurveEllipticCurvePoint represents a point on the EllipticCurve.
|
||||
EllipticCurvePoint struct {
|
||||
X *big.Int
|
||||
Y *big.Int
|
||||
}
|
||||
)
|
||||
|
||||
// NewEllipticCurve returns a ready to use EllipticCurve with preconfigured
|
||||
// fields for the NEO protocol.
|
||||
func NewEllipticCurve() EllipticCurve {
|
||||
c := EllipticCurve{}
|
||||
|
||||
c.P, _ = new(big.Int).SetString(
|
||||
"FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16,
|
||||
)
|
||||
c.A, _ = new(big.Int).SetString(
|
||||
"FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16,
|
||||
)
|
||||
c.B, _ = new(big.Int).SetString(
|
||||
"5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16,
|
||||
)
|
||||
c.G.X, _ = new(big.Int).SetString(
|
||||
"6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16,
|
||||
)
|
||||
c.G.Y, _ = new(big.Int).SetString(
|
||||
"4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16,
|
||||
)
|
||||
c.N, _ = new(big.Int).SetString(
|
||||
"FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16,
|
||||
)
|
||||
c.H, _ = new(big.Int).SetString("01", 16)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (p *EllipticCurvePoint) format() string {
|
||||
if p.X == nil && p.Y == nil {
|
||||
return "(inf,inf)"
|
||||
}
|
||||
bx := hex.EncodeToString(p.X.Bytes())
|
||||
by := hex.EncodeToString(p.Y.Bytes())
|
||||
return fmt.Sprintf("(%s, %s)", bx, by)
|
||||
}
|
||||
|
||||
// IsInfinity checks if point P is infinity on EllipticCurve ec.
|
||||
func (c *EllipticCurve) IsInfinity(P EllipticCurvePoint) bool {
|
||||
if P.X == nil && P.Y == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsOnCurve checks if point P is on EllipticCurve ec.
|
||||
func (c *EllipticCurve) IsOnCurve(P EllipticCurvePoint) bool {
|
||||
if c.IsInfinity(P) {
|
||||
return false
|
||||
}
|
||||
lhs := mulMod(P.Y, P.Y, c.P)
|
||||
rhs := addMod(
|
||||
addMod(
|
||||
expMod(P.X, big.NewInt(3), c.P),
|
||||
mulMod(c.A, P.X, c.P), c.P),
|
||||
c.B, c.P)
|
||||
|
||||
if lhs.Cmp(rhs) == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Add computes R = P + Q on EllipticCurve ec.
|
||||
func (c *EllipticCurve) Add(P, Q EllipticCurvePoint) (R EllipticCurvePoint) {
|
||||
// See rules 1-5 on SEC1 pg.7 http://www.secg.org/collateral/sec1_final.pdf
|
||||
if c.IsInfinity(P) && c.IsInfinity(Q) {
|
||||
R.X = nil
|
||||
R.Y = nil
|
||||
} else if c.IsInfinity(P) {
|
||||
R.X = new(big.Int).Set(Q.X)
|
||||
R.Y = new(big.Int).Set(Q.Y)
|
||||
} else if c.IsInfinity(Q) {
|
||||
R.X = new(big.Int).Set(P.X)
|
||||
R.Y = new(big.Int).Set(P.Y)
|
||||
} else if P.X.Cmp(Q.X) == 0 && addMod(P.Y, Q.Y, c.P).Sign() == 0 {
|
||||
R.X = nil
|
||||
R.Y = nil
|
||||
} else if P.X.Cmp(Q.X) == 0 && P.Y.Cmp(Q.Y) == 0 && P.Y.Sign() != 0 {
|
||||
num := addMod(
|
||||
mulMod(big.NewInt(3),
|
||||
mulMod(P.X, P.X, c.P), c.P),
|
||||
c.A, c.P)
|
||||
den := invMod(mulMod(big.NewInt(2), P.Y, c.P), c.P)
|
||||
lambda := mulMod(num, den, c.P)
|
||||
R.X = subMod(
|
||||
mulMod(lambda, lambda, c.P),
|
||||
mulMod(big.NewInt(2), P.X, c.P),
|
||||
c.P)
|
||||
R.Y = subMod(
|
||||
mulMod(lambda, subMod(P.X, R.X, c.P), c.P),
|
||||
P.Y, c.P)
|
||||
} else if P.X.Cmp(Q.X) != 0 {
|
||||
num := subMod(Q.Y, P.Y, c.P)
|
||||
den := invMod(subMod(Q.X, P.X, c.P), c.P)
|
||||
lambda := mulMod(num, den, c.P)
|
||||
R.X = subMod(
|
||||
subMod(
|
||||
mulMod(lambda, lambda, c.P),
|
||||
P.X, c.P),
|
||||
Q.X, c.P)
|
||||
R.Y = subMod(
|
||||
mulMod(lambda,
|
||||
subMod(P.X, R.X, c.P), c.P),
|
||||
P.Y, c.P)
|
||||
} else {
|
||||
panic(fmt.Sprintf("Unsupported point addition: %v + %v", P.format(), Q.format()))
|
||||
}
|
||||
|
||||
return R
|
||||
}
|
||||
|
||||
// ScalarMult computes Q = k * P on EllipticCurve ec.
|
||||
func (c *EllipticCurve) ScalarMult(k *big.Int, P EllipticCurvePoint) (Q EllipticCurvePoint) {
|
||||
// Implementation based on pseudocode here:
|
||||
// https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
|
||||
var R0 EllipticCurvePoint
|
||||
var R1 EllipticCurvePoint
|
||||
|
||||
R0.X = nil
|
||||
R0.Y = nil
|
||||
R1.X = new(big.Int).Set(P.X)
|
||||
R1.Y = new(big.Int).Set(P.Y)
|
||||
|
||||
for i := c.N.BitLen() - 1; i >= 0; i-- {
|
||||
if k.Bit(i) == 0 {
|
||||
R1 = c.Add(R0, R1)
|
||||
R0 = c.Add(R0, R0)
|
||||
} else {
|
||||
R0 = c.Add(R0, R1)
|
||||
R1 = c.Add(R1, R1)
|
||||
}
|
||||
}
|
||||
return R0
|
||||
}
|
||||
|
||||
// ScalarBaseMult computes Q = k * G on EllipticCurve ec.
|
||||
func (c *EllipticCurve) ScalarBaseMult(k *big.Int) (Q EllipticCurvePoint) {
|
||||
return c.ScalarMult(k, c.G)
|
||||
}
|
||||
|
||||
// Decompress decompresses coordinate x and ylsb (y's least significant bit) into a EllipticCurvePoint P on EllipticCurve ec.
|
||||
func (c *EllipticCurve) Decompress(x *big.Int, ylsb uint) (P EllipticCurvePoint, err error) {
|
||||
/* y**2 = x**3 + a*x + b % p */
|
||||
rhs := addMod(
|
||||
addMod(
|
||||
expMod(x, big.NewInt(3), c.P),
|
||||
mulMod(c.A, x, c.P),
|
||||
c.P),
|
||||
c.B, c.P)
|
||||
|
||||
y := sqrtMod(rhs, c.P)
|
||||
if y.Bit(0) != (ylsb & 0x1) {
|
||||
y = subMod(big.NewInt(0), y, c.P)
|
||||
}
|
||||
|
||||
P.X = x
|
||||
P.Y = y
|
||||
|
||||
if !c.IsOnCurve(P) {
|
||||
return P, errors.New("compressed (x, ylsb) not on curve")
|
||||
}
|
||||
|
||||
return P, nil
|
||||
}
|
61
pkg/crypto/modular_arithmetic.go
Normal file
61
pkg/crypto/modular_arithmetic.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package crypto
|
||||
|
||||
import "math/big"
|
||||
|
||||
// addMod computes z = (x + y) % p.
|
||||
func addMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) {
|
||||
z = new(big.Int).Add(x, y)
|
||||
z.Mod(z, p)
|
||||
return z
|
||||
}
|
||||
|
||||
// subMod computes z = (x - y) % p.
|
||||
func subMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) {
|
||||
z = new(big.Int).Sub(x, y)
|
||||
z.Mod(z, p)
|
||||
return z
|
||||
}
|
||||
|
||||
// mulMod computes z = (x * y) % p.
|
||||
func mulMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) {
|
||||
n := new(big.Int).Set(x)
|
||||
z = big.NewInt(0)
|
||||
|
||||
for i := 0; i < y.BitLen(); i++ {
|
||||
if y.Bit(i) == 1 {
|
||||
z = addMod(z, n, p)
|
||||
}
|
||||
n = addMod(n, n, p)
|
||||
}
|
||||
|
||||
return z
|
||||
}
|
||||
|
||||
// invMod computes z = (1/x) % p.
|
||||
func invMod(x *big.Int, p *big.Int) (z *big.Int) {
|
||||
z = new(big.Int).ModInverse(x, p)
|
||||
return z
|
||||
}
|
||||
|
||||
// expMod computes z = (x^e) % p.
|
||||
func expMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) {
|
||||
z = new(big.Int).Exp(x, y, p)
|
||||
return z
|
||||
}
|
||||
|
||||
// sqrtMod computes z = sqrt(x) % p.
|
||||
func sqrtMod(x *big.Int, p *big.Int) (z *big.Int) {
|
||||
/* assert that p % 4 == 3 */
|
||||
if new(big.Int).Mod(p, big.NewInt(4)).Cmp(big.NewInt(3)) != 0 {
|
||||
panic("p is not equal to 3 mod 4!")
|
||||
}
|
||||
|
||||
/* z = sqrt(x) % p = x^((p+1)/4) % p */
|
||||
|
||||
/* e = (p+1)/4 */
|
||||
e := new(big.Int).Add(p, big.NewInt(1))
|
||||
e = e.Rsh(e, 2)
|
||||
|
||||
z = expMod(x, e, p)
|
||||
return z
|
||||
}
|
|
@ -110,7 +110,7 @@ func NewServer(net NetMode) *Server {
|
|||
}
|
||||
|
||||
// For now I will hard code a genesis block of the docker privnet container.
|
||||
startHash, _ := util.Uint256DecodeFromString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099")
|
||||
startHash, _ := util.Uint256DecodeString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099")
|
||||
|
||||
s := &Server{
|
||||
id: util.RandUint32(1111111, 9999999),
|
||||
|
|
5
pkg/smartcontract/contract.go
Normal file
5
pkg/smartcontract/contract.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package smartcontract
|
||||
|
||||
// Contract represents a NEO smartcontract.
|
||||
type Contract struct {
|
||||
}
|
14
pkg/util/array.go
Normal file
14
pkg/util/array.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package util
|
||||
|
||||
// ArrayReverse return a reversed version of the given byte slice.
|
||||
func ArrayReverse(b []byte) []byte {
|
||||
// Protect from big.Ints that have 1 len bytes.
|
||||
if len(b) < 2 {
|
||||
return b
|
||||
}
|
||||
dest := make([]byte, len(b))
|
||||
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
|
||||
dest[i], dest[j] = b[j], b[i]
|
||||
}
|
||||
return dest
|
||||
}
|
25
pkg/util/array_test.go
Normal file
25
pkg/util/array_test.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestArrayReverse(t *testing.T) {
|
||||
arr := []byte{0x01, 0x02, 0x03, 0x04}
|
||||
have := ArrayReverse(arr)
|
||||
want := []byte{0x04, 0x03, 0x02, 0x01}
|
||||
if bytes.Compare(have, want) != 0 {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
// This tests a bug that occured with arrays of size 1
|
||||
func TestArrayReverseLen2(t *testing.T) {
|
||||
arr := []byte{0x01}
|
||||
have := ArrayReverse(arr)
|
||||
want := []byte{0x01}
|
||||
if bytes.Compare(have, want) != 0 {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package util
|
|||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const uint160Size = 20
|
||||
|
@ -9,8 +10,31 @@ const uint160Size = 20
|
|||
// Uint160 is a 20 byte long unsigned integer.
|
||||
type Uint160 [uint160Size]uint8
|
||||
|
||||
// ToSlice returns a byte slice of u.
|
||||
func (u Uint160) ToSlice() []byte {
|
||||
// Uint160DecodeString attempts to decode the given string into an Uint160.
|
||||
func Uint160DecodeString(s string) (u Uint160, err error) {
|
||||
if len(s) != uint160Size*2 {
|
||||
return u, fmt.Errorf("expected string size of %d got %d", uint160Size*2, len(s))
|
||||
}
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
return Uint160DecodeBytes(b)
|
||||
}
|
||||
|
||||
// Uint160DecodeBytes attempts to decode the given bytes into an Uint160.
|
||||
func Uint160DecodeBytes(b []byte) (u Uint160, err error) {
|
||||
if len(b) != uint160Size {
|
||||
return u, fmt.Errorf("expected byte size of %d got %d", uint160Size, len(b))
|
||||
}
|
||||
for i := 0; i < uint160Size; i++ {
|
||||
u[i] = b[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Bytes returns the byte slice representation of u.
|
||||
func (u Uint160) Bytes() []byte {
|
||||
b := make([]byte, uint160Size)
|
||||
for i := 0; i < uint160Size; i++ {
|
||||
b[i] = byte(u[i])
|
||||
|
@ -20,10 +44,15 @@ func (u Uint160) ToSlice() []byte {
|
|||
|
||||
// String implements the stringer interface.
|
||||
func (u Uint160) String() string {
|
||||
return hex.EncodeToString(u.ToSlice())
|
||||
return hex.EncodeToString(u.Bytes())
|
||||
}
|
||||
|
||||
// Equals returns true if both Uint256 values are the same.
|
||||
func (u Uint160) Equals(other Uint160) bool {
|
||||
return u.String() == other.String()
|
||||
for i := 0; i < uint160Size; i++ {
|
||||
if u[i] != other[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
1
pkg/util/uint160_test.go
Normal file
1
pkg/util/uint160_test.go
Normal file
|
@ -0,0 +1 @@
|
|||
package util
|
|
@ -10,56 +10,32 @@ const uint256Size = 32
|
|||
// Uint256 is a 32 byte long unsigned integer.
|
||||
type Uint256 [uint256Size]uint8
|
||||
|
||||
// Uint256DecodeFromString returns an Uint256 from a (hex) string.
|
||||
func Uint256DecodeFromString(s string) (Uint256, error) {
|
||||
var val Uint256
|
||||
|
||||
// Uint256DecodeString attempts to decode the given string into an Uint256.
|
||||
func Uint256DecodeString(s string) (u Uint256, err error) {
|
||||
if len(s) != uint256Size*2 {
|
||||
return val, fmt.Errorf("expected string size of %d got %d", uint256Size*2, len(s))
|
||||
return u, fmt.Errorf("expected string size of %d got %d", uint256Size*2, len(s))
|
||||
}
|
||||
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return val, err
|
||||
return u, err
|
||||
}
|
||||
|
||||
b = ToArrayReverse(b)
|
||||
|
||||
return Uint256DecodeFromBytes(b)
|
||||
return Uint256DecodeBytes(b)
|
||||
}
|
||||
|
||||
// Uint256DecodeFromBytes return an Uint256 from a byte slice.
|
||||
func Uint256DecodeFromBytes(b []byte) (Uint256, error) {
|
||||
var val Uint256
|
||||
|
||||
// Uint256DecodeBytes attempts to decode the given string into an Uint256.
|
||||
func Uint256DecodeBytes(b []byte) (u Uint256, err error) {
|
||||
b = ArrayReverse(b)
|
||||
if len(b) != uint256Size {
|
||||
return val, fmt.Errorf("expected []byte of size %d got %d", uint256Size, len(b))
|
||||
return u, fmt.Errorf("expected []byte of size %d got %d", uint256Size, len(b))
|
||||
}
|
||||
|
||||
for i := 0; i < uint256Size; i++ {
|
||||
val[i] = b[i]
|
||||
u[i] = b[i]
|
||||
}
|
||||
|
||||
return val, nil
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// ToArrayReverse return a reversed version of the given byte slice.
|
||||
func ToArrayReverse(b []byte) []byte {
|
||||
// Protect from big.Ints that have 1 len bytes.
|
||||
if len(b) < 2 {
|
||||
return b
|
||||
}
|
||||
|
||||
dest := make([]byte, len(b))
|
||||
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
|
||||
dest[i], dest[j] = b[j], b[i]
|
||||
}
|
||||
|
||||
return dest
|
||||
}
|
||||
|
||||
// ToSlice returns a byte slice of u.
|
||||
func (u Uint256) ToSlice() []byte {
|
||||
// ToSlice returns a byte slice representation of u.
|
||||
func (u Uint256) Bytes() []byte {
|
||||
b := make([]byte, uint256Size)
|
||||
for i := 0; i < uint256Size; i++ {
|
||||
b[i] = byte(u[i])
|
||||
|
@ -67,13 +43,9 @@ func (u Uint256) ToSlice() []byte {
|
|||
return b
|
||||
}
|
||||
|
||||
// ToSliceReverse returns a reversed byte slice of u.
|
||||
func (u Uint256) ToSliceReverse() []byte {
|
||||
b := make([]byte, uint256Size)
|
||||
for i, j := 0, uint256Size-1; i < j; i, j = i+1, j-1 {
|
||||
b[i], b[j] = byte(u[j]), byte(u[i])
|
||||
}
|
||||
return b
|
||||
// BytesReverse return a reversed byte representation of u.
|
||||
func (u Uint256) BytesReverse() []byte {
|
||||
return ArrayReverse(u.Bytes())
|
||||
}
|
||||
|
||||
// Equals returns true if both Uint256 values are the same.
|
||||
|
@ -83,5 +55,5 @@ func (u Uint256) Equals(other Uint256) bool {
|
|||
|
||||
// String implements the stringer interface.
|
||||
func (u Uint256) String() string {
|
||||
return hex.EncodeToString(u.ToSliceReverse())
|
||||
return hex.EncodeToString(ArrayReverse(u.Bytes()))
|
||||
}
|
||||
|
|
|
@ -1,25 +1,50 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestToArrayReverse(t *testing.T) {
|
||||
arr := []byte{0x01, 0x02, 0x03, 0x04}
|
||||
have := ToArrayReverse(arr)
|
||||
want := []byte{0x04, 0x03, 0x02, 0x01}
|
||||
if bytes.Compare(have, want) != 0 {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
func TestUint256DecodeString(t *testing.T) {
|
||||
hexStr := "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"
|
||||
val, err := Uint256DecodeString(hexStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(val)
|
||||
}
|
||||
|
||||
func TestUint256DecodeBytes(t *testing.T) {
|
||||
hexStr := "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"
|
||||
b, err := hex.DecodeString(hexStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
val, err := Uint256DecodeBytes(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if val.String() != hexStr {
|
||||
t.Fatalf("expected %s and %s to be equal", val, hexStr)
|
||||
}
|
||||
}
|
||||
|
||||
// This tests a bug that occured with arrays of size 1
|
||||
func TestToArrayReverseLen2(t *testing.T) {
|
||||
arr := []byte{0x01}
|
||||
have := ToArrayReverse(arr)
|
||||
want := []byte{0x01}
|
||||
if bytes.Compare(have, want) != 0 {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
func TestUInt256Equals(t *testing.T) {
|
||||
a := "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"
|
||||
b := "e287c5b29a1b66092be6803c59c765308ac20287e1b4977fd399da5fc8f66ab5"
|
||||
|
||||
ua, err := Uint256DecodeString(a)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ub, err := Uint256DecodeString(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ua.Equals(ub) {
|
||||
t.Fatalf("%s and %s cannot be equal", ua, ub)
|
||||
}
|
||||
if !ua.Equals(ua) {
|
||||
t.Fatalf("%s and %s must be equal", ua, ua)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ func emitInt(w *bytes.Buffer, i int64) error {
|
|||
}
|
||||
|
||||
bInt := big.NewInt(i)
|
||||
val := util.ToArrayReverse(bInt.Bytes())
|
||||
val := util.ArrayReverse(bInt.Bytes())
|
||||
return emitBytes(w, val)
|
||||
}
|
||||
|
||||
|
|
113
pkg/wallet/account.go
Normal file
113
pkg/wallet/account.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package wallet
|
||||
|
||||
import "github.com/anthdm/neo-go/pkg/util"
|
||||
|
||||
// Account represents a NEO account. It holds the private and public key
|
||||
// along with some metadata.
|
||||
type Account struct {
|
||||
// NEO private key.
|
||||
privateKey *PrivateKey
|
||||
|
||||
// NEO public key.
|
||||
publicKey []byte
|
||||
|
||||
// Account import file.
|
||||
wif string
|
||||
|
||||
// NEO public addresss.
|
||||
Address string `json:"address"`
|
||||
|
||||
// Encrypted WIF of the account also known as the key.
|
||||
EncryptedWIF string `json:"key"`
|
||||
|
||||
// Label is a label the user had made for this account.
|
||||
Label string `json:"label"`
|
||||
|
||||
// contract is a Contract object which describes the details of the contract.
|
||||
// This field can be null (for watch-only address).
|
||||
Contract *Contract `json:"contract"`
|
||||
|
||||
// Indicates whether the account is locked by the user.
|
||||
// the client shouldn't spend the funds in a locked account.
|
||||
Locked bool `json:"lock"`
|
||||
|
||||
// Indicates whether the account is the default change account.
|
||||
Default bool `json:"isDefault"`
|
||||
}
|
||||
|
||||
// Contract represents a subset of the smartcontract to embedd in the
|
||||
// Account so it's NEP-6 compliant.
|
||||
type Contract struct {
|
||||
// Script hash of the contract deployed on the blockchain.
|
||||
Script util.Uint160 `json:"script"`
|
||||
|
||||
// A list of parameters used deploying this contract.
|
||||
Parameters []interface{} `json:"parameters"`
|
||||
|
||||
// Indicates whether the contract has been deployed to the blockchain.
|
||||
Deployed bool `json:"deployed"`
|
||||
}
|
||||
|
||||
// NewAccount creates a new Account with a random generated PrivateKey.
|
||||
func NewAccount() (*Account, error) {
|
||||
priv, err := NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newAccountFromPrivateKey(priv)
|
||||
}
|
||||
|
||||
// DecryptAccount decrypt the encryptedWIF with the given passphrase and
|
||||
// return the decrypted Account.
|
||||
func DecryptAccount(encryptedWIF, passphrase string) (*Account, error) {
|
||||
wif, err := NEP2Decrypt(encryptedWIF, passphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewAccountFromWIF(wif)
|
||||
}
|
||||
|
||||
// Encrypt encrypts the wallet's PrivateKey with the given passphrase
|
||||
// under the NEP-2 standard.
|
||||
func (a *Account) Encrypt(passphrase string) error {
|
||||
wif, err := NEP2Encrypt(a.privateKey, passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.EncryptedWIF = wif
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewAccountFromWIF creates a new Account from the given WIF.
|
||||
func NewAccountFromWIF(wif string) (*Account, error) {
|
||||
privKey, err := NewPrivateKeyFromWIF(wif)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newAccountFromPrivateKey(privKey)
|
||||
}
|
||||
|
||||
// newAccountFromPrivateKey created a wallet from the given PrivateKey.
|
||||
func newAccountFromPrivateKey(p *PrivateKey) (*Account, error) {
|
||||
pubKey, err := p.PublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubAddr, err := p.Address()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wif, err := p.WIF()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a := &Account{
|
||||
publicKey: pubKey,
|
||||
privateKey: p,
|
||||
Address: pubAddr,
|
||||
wif: wif,
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
51
pkg/wallet/account_test.go
Normal file
51
pkg/wallet/account_test.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewAccount(t *testing.T) {
|
||||
for _, testCase := range testKeyCases {
|
||||
acc, err := NewAccountFromWIF(testCase.wif)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
compareFields(t, testCase, acc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptAccount(t *testing.T) {
|
||||
for _, testCase := range testKeyCases {
|
||||
acc, err := DecryptAccount(testCase.encryptedWif, testCase.passphrase)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
compareFields(t, testCase, acc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFromWif(t *testing.T) {
|
||||
for _, testCase := range testKeyCases {
|
||||
acc, err := NewAccountFromWIF(testCase.wif)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
compareFields(t, testCase, acc)
|
||||
}
|
||||
}
|
||||
|
||||
func compareFields(t *testing.T, tk testKey, acc *Account) {
|
||||
if want, have := tk.address, acc.Address; want != have {
|
||||
t.Fatalf("expected %s got %s", want, have)
|
||||
}
|
||||
if want, have := tk.wif, acc.wif; want != have {
|
||||
t.Fatalf("expected %s got %s", want, have)
|
||||
}
|
||||
if want, have := tk.publicKey, hex.EncodeToString(acc.publicKey); want != have {
|
||||
t.Fatalf("expected %s got %s", want, have)
|
||||
}
|
||||
if want, have := tk.privateKey, acc.privateKey.String(); want != have {
|
||||
t.Fatalf("expected %s got %s", want, have)
|
||||
}
|
||||
}
|
165
pkg/wallet/nep2.go
Normal file
165
pkg/wallet/nep2.go
Normal file
|
@ -0,0 +1,165 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
// NEP-2 standard implementation for encrypting and decrypting wallets.
|
||||
|
||||
// NEP-2 specified parameters used for cryptography.
|
||||
const (
|
||||
n = 16384
|
||||
r = 8
|
||||
p = 8
|
||||
keyLen = 64
|
||||
nepFlag = 0xe0
|
||||
)
|
||||
|
||||
var nepHeader = []byte{0x01, 0x42}
|
||||
|
||||
type scryptParams struct {
|
||||
N int `json:"n"`
|
||||
R int `json:"r"`
|
||||
P int `json:"p"`
|
||||
}
|
||||
|
||||
func newScryptParams() scryptParams {
|
||||
return scryptParams{
|
||||
N: n,
|
||||
R: r,
|
||||
P: p,
|
||||
}
|
||||
}
|
||||
|
||||
// NEP2Encrypt encrypts a the PrivateKey using a given passphrase
|
||||
// under the NEP-2 standard.
|
||||
func NEP2Encrypt(priv *PrivateKey, passphrase string) (s string, err error) {
|
||||
address, err := priv.Address()
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
addrHash := hashAddress(address)[0:4]
|
||||
// Normalize the passphrase according to the NFC standard.
|
||||
phraseNorm := norm.NFC.Bytes([]byte(passphrase))
|
||||
derivedKey, err := scrypt.Key(phraseNorm, addrHash, n, r, p, keyLen)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
derivedKey1 := derivedKey[:32]
|
||||
derivedKey2 := derivedKey[32:]
|
||||
xr := xor(priv.Bytes(), derivedKey1)
|
||||
|
||||
encrypted, err := crypto.AESEncrypt(derivedKey2, xr)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.Write(nepHeader)
|
||||
buf.WriteByte(nepFlag)
|
||||
buf.Write(addrHash)
|
||||
buf.Write(encrypted)
|
||||
|
||||
if buf.Len() != 39 {
|
||||
return s, fmt.Errorf("invalid buffer length: expecting 39 bytes got %d", buf.Len())
|
||||
}
|
||||
|
||||
return crypto.Base58CheckEncode(buf.Bytes()), nil
|
||||
}
|
||||
|
||||
// NEP2Decrypt decrypts an encrypted key using a given passphrase
|
||||
// under the NEP-2 standard.
|
||||
func NEP2Decrypt(key, passphrase string) (s string, err error) {
|
||||
b, err := crypto.Base58CheckDecode(key)
|
||||
if err != nil {
|
||||
return s, nil
|
||||
}
|
||||
if err := validateNEP2Format(b); err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
addrHash := b[3:7]
|
||||
// Normalize the passphrase according to the NFC standard.
|
||||
phraseNorm := norm.NFC.Bytes([]byte(passphrase))
|
||||
derivedKey, err := scrypt.Key(phraseNorm, addrHash, n, r, p, keyLen)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
derivedKey1 := derivedKey[:32]
|
||||
derivedKey2 := derivedKey[32:]
|
||||
encryptedBytes := b[7:]
|
||||
|
||||
decrypted, err := crypto.AESDecrypt(encryptedBytes, derivedKey2)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
privBytes := xor(decrypted, derivedKey1)
|
||||
|
||||
// Rebuild the private key.
|
||||
privKey, err := NewPrivateKeyFromBytes(privBytes)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
if !compareAddressHash(privKey, addrHash) {
|
||||
return s, errors.New("password mismatch")
|
||||
}
|
||||
|
||||
return privKey.WIF()
|
||||
}
|
||||
|
||||
func compareAddressHash(priv *PrivateKey, hash []byte) bool {
|
||||
address, err := priv.Address()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
addrHash := hashAddress(address)[0:4]
|
||||
return bytes.Compare(addrHash, hash) == 0
|
||||
}
|
||||
|
||||
func validateNEP2Format(b []byte) error {
|
||||
if len(b) != 39 {
|
||||
return fmt.Errorf("invalid length: expecting 39 got %d", len(b))
|
||||
}
|
||||
if b[0] != 0x01 {
|
||||
return fmt.Errorf("invalid byte sequence: expecting 0x01 got 0x%02x", b[0])
|
||||
}
|
||||
if b[1] != 0x42 {
|
||||
return fmt.Errorf("invalid byte sequence: expecting 0x42 got 0x%02x", b[1])
|
||||
}
|
||||
if b[2] != 0xe0 {
|
||||
return fmt.Errorf("invalid byte sequence: expecting 0xe0 got 0x%02x", b[2])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func xor(a, b []byte) []byte {
|
||||
if len(a) != len(b) {
|
||||
panic("cannot XOR non equal length arrays")
|
||||
}
|
||||
dst := make([]byte, len(a))
|
||||
for i := 0; i < len(dst); i++ {
|
||||
dst[i] = a[i] ^ b[i]
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func hashAddress(addr string) []byte {
|
||||
sha := sha256.New()
|
||||
sha.Write([]byte(addr))
|
||||
hash := sha.Sum(nil)
|
||||
sha.Reset()
|
||||
sha.Write(hash)
|
||||
return sha.Sum(nil)
|
||||
}
|
193
pkg/wallet/private_key.go
Normal file
193
pkg/wallet/private_key.go
Normal file
|
@ -0,0 +1,193 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||
"github.com/anthdm/rfc6979"
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
)
|
||||
|
||||
// PrivateKey represents a NEO private key.
|
||||
type PrivateKey struct {
|
||||
b []byte
|
||||
}
|
||||
|
||||
func NewPrivateKey() (*PrivateKey, error) {
|
||||
c := crypto.NewEllipticCurve()
|
||||
b := make([]byte, c.N.BitLen()/8+8)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := new(big.Int).SetBytes(b)
|
||||
d.Mod(d, new(big.Int).Sub(c.N, big.NewInt(1)))
|
||||
d.Add(d, big.NewInt(1))
|
||||
|
||||
p := &PrivateKey{b: d.Bytes()}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// NewPrivateKeyFromHex returns a PrivateKey created from the
|
||||
// given hex string.
|
||||
func NewPrivateKeyFromHex(str string) (*PrivateKey, error) {
|
||||
b, err := hex.DecodeString(str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewPrivateKeyFromBytes(b)
|
||||
}
|
||||
|
||||
// NewPrivateKeyFromBytes returns a NEO PrivateKey from the given byte slice.
|
||||
func NewPrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
|
||||
if len(b) != 32 {
|
||||
return nil, fmt.Errorf(
|
||||
"invalid byte length: expected %d bytes got %d", 32, len(b),
|
||||
)
|
||||
}
|
||||
return &PrivateKey{b}, nil
|
||||
}
|
||||
|
||||
// PublicKey derives the public key from the private key.
|
||||
func (p *PrivateKey) PublicKey() ([]byte, error) {
|
||||
var (
|
||||
c = crypto.NewEllipticCurve()
|
||||
q = new(big.Int).SetBytes(p.b)
|
||||
)
|
||||
|
||||
point := c.ScalarBaseMult(q)
|
||||
if !c.IsOnCurve(point) {
|
||||
return nil, errors.New("failed to derive public key using elliptic curve")
|
||||
}
|
||||
|
||||
bx := point.X.Bytes()
|
||||
padded := append(
|
||||
bytes.Repeat(
|
||||
[]byte{0x00},
|
||||
32-len(bx),
|
||||
),
|
||||
bx...,
|
||||
)
|
||||
|
||||
prefix := []byte{0x03}
|
||||
if point.Y.Bit(0) == 0 {
|
||||
prefix = []byte{0x02}
|
||||
}
|
||||
b := append(prefix, padded...)
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// NewPrivateKeyFromWIF returns a NEO PrivateKey from the given
|
||||
// WIF (wallet import format).
|
||||
func NewPrivateKeyFromWIF(wif string) (*PrivateKey, error) {
|
||||
w, err := WIFDecode(wif, WIFVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return w.PrivateKey, nil
|
||||
}
|
||||
|
||||
// WIF returns the (wallet import format) of the PrivateKey.
|
||||
// Good documentation about this process can be found here:
|
||||
// https://en.bitcoin.it/wiki/Wallet_import_format
|
||||
func (p *PrivateKey) WIF() (string, error) {
|
||||
return WIFEncode(p.b, WIFVersion, true)
|
||||
}
|
||||
|
||||
// Address derives the public NEO address that is coupled with the private key, and
|
||||
// returns it as a string.
|
||||
func (p *PrivateKey) Address() (string, error) {
|
||||
b, err := p.Signature()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b = append([]byte{0x17}, b...)
|
||||
|
||||
sha := sha256.New()
|
||||
sha.Write(b)
|
||||
hash := sha.Sum(nil)
|
||||
|
||||
sha.Reset()
|
||||
sha.Write(hash)
|
||||
hash = sha.Sum(nil)
|
||||
|
||||
b = append(b, hash[0:4]...)
|
||||
|
||||
address := crypto.Base58Encode(b)
|
||||
|
||||
return address, nil
|
||||
}
|
||||
|
||||
// Signature creates the signature using the private key.
|
||||
func (p *PrivateKey) Signature() ([]byte, error) {
|
||||
b, err := p.PublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b = append([]byte{0x21}, b...)
|
||||
b = append(b, 0xAC)
|
||||
|
||||
sha := sha256.New()
|
||||
sha.Write(b)
|
||||
hash := sha.Sum(nil)
|
||||
|
||||
ripemd := ripemd160.New()
|
||||
ripemd.Reset()
|
||||
ripemd.Write(hash)
|
||||
hash = ripemd.Sum(nil)
|
||||
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
// Sign signs arbitrary length data using the private key.
|
||||
func (p *PrivateKey) Sign(data []byte) ([]byte, error) {
|
||||
var (
|
||||
privateKey = p.ecdsa()
|
||||
digest = sha256.Sum256(data)
|
||||
)
|
||||
|
||||
r, s, err := rfc6979.SignECDSA(privateKey, digest[:], sha256.New)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params := privateKey.Curve.Params()
|
||||
curveOrderByteSize := params.P.BitLen() / 8
|
||||
rBytes, sBytes := r.Bytes(), s.Bytes()
|
||||
signature := make([]byte, curveOrderByteSize*2)
|
||||
copy(signature[curveOrderByteSize-len(rBytes):], rBytes)
|
||||
copy(signature[curveOrderByteSize*2-len(sBytes):], sBytes)
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// ecsda converts the key to a usable ecsda.PrivateKey for signing data.
|
||||
func (p *PrivateKey) ecdsa() *ecdsa.PrivateKey {
|
||||
priv := new(ecdsa.PrivateKey)
|
||||
priv.PublicKey.Curve = elliptic.P256()
|
||||
priv.D = new(big.Int).SetBytes(p.b)
|
||||
priv.PublicKey.X, priv.PublicKey.Y = priv.PublicKey.Curve.ScalarBaseMult(p.b)
|
||||
return priv
|
||||
}
|
||||
|
||||
// String implements the stringer interface.
|
||||
func (p *PrivateKey) String() string {
|
||||
return hex.EncodeToString(p.b)
|
||||
}
|
||||
|
||||
// Bytes returns the underlying bytes of the PrivateKey.
|
||||
func (p *PrivateKey) Bytes() []byte {
|
||||
return p.b
|
||||
}
|
74
pkg/wallet/private_key_test.go
Normal file
74
pkg/wallet/private_key_test.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package wallet
|
||||
|
||||
import "testing"
|
||||
|
||||
type testKey struct {
|
||||
address,
|
||||
privateKey,
|
||||
publicKey,
|
||||
wif,
|
||||
passphrase,
|
||||
encryptedWif string
|
||||
}
|
||||
|
||||
var testKeyCases = []testKey{
|
||||
{
|
||||
address: "ALq7AWrhAueN6mJNqk6FHJjnsEoPRytLdW",
|
||||
privateKey: "7d128a6d096f0c14c3a25a2b0c41cf79661bfcb4a8cc95aaaea28bde4d732344",
|
||||
publicKey: "02028a99826edc0c97d18e22b6932373d908d323aa7f92656a77ec26e8861699ef",
|
||||
wif: "L1QqQJnpBwbsPGAuutuzPTac8piqvbR1HRjrY5qHup48TBCBFe4g",
|
||||
passphrase: "city of zion",
|
||||
encryptedWif: "6PYLHmDf6AjF4AsVtosmxHuPYeuyJL3SLuw7J1U8i7HxKAnYNsp61HYRfF",
|
||||
},
|
||||
{
|
||||
address: "ALfnhLg7rUyL6Jr98bzzoxz5J7m64fbR4s",
|
||||
privateKey: "9ab7e154840daca3a2efadaf0df93cd3a5b51768c632f5433f86909d9b994a69",
|
||||
publicKey: "031d8e1630ce640966967bc6d95223d21f44304133003140c3b52004dc981349c9",
|
||||
wif: "L2QTooFoDFyRFTxmtiVHt5CfsXfVnexdbENGDkkrrgTTryiLsPMG",
|
||||
passphrase: "我的密码",
|
||||
encryptedWif: "6PYWVp3xfgvnuNKP7ZavSViYvvim2zuzx9Q33vuWZr8aURiKeJ6Zm7BfPQ",
|
||||
},
|
||||
{
|
||||
address: "AVf4UGKevVrMR1j3UkPsuoYKSC4ocoAkKx",
|
||||
privateKey: "3edee7036b8fd9cef91de47386b191dd76db2888a553e7736bb02808932a915b",
|
||||
publicKey: "02232ce8d2e2063dce0451131851d47421bfc4fc1da4db116fca5302c0756462fa",
|
||||
wif: "KyKvWLZsNwBJx5j9nurHYRwhYfdQUu9tTEDsLCUHDbYBL8cHxMiG",
|
||||
passphrase: "MyL33tP@33w0rd",
|
||||
encryptedWif: "6PYNoc1EG5J38MTqGN9Anphfdd6UwbS4cpFCzHhrkSKBBbV1qkbJJZQnkn",
|
||||
},
|
||||
}
|
||||
|
||||
func TestPrivateKey(t *testing.T) {
|
||||
for _, testCase := range testKeyCases {
|
||||
privKey, err := NewPrivateKeyFromHex(testCase.privateKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
address, err := privKey.Address()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, have := testCase.address, address; want != have {
|
||||
t.Fatalf("expected %s got %s", want, have)
|
||||
}
|
||||
wif, err := privKey.WIF()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, have := testCase.wif, wif; want != have {
|
||||
t.Fatalf("expected %s got %s", want, have)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrivateKeyFromWIF(t *testing.T) {
|
||||
for _, testCase := range testKeyCases {
|
||||
key, err := NewPrivateKeyFromWIF(testCase.wif)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, have := testCase.privateKey, key.String(); want != have {
|
||||
t.Fatalf("expected %s got %s", want, have)
|
||||
}
|
||||
}
|
||||
}
|
64
pkg/wallet/public_key.go
Normal file
64
pkg/wallet/public_key.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package wallet
|
||||
|
||||
//// PublicKey represent a NEO public key.
|
||||
//type PublicKey struct {
|
||||
// crypto.EllipticCurvePoint
|
||||
//}
|
||||
//
|
||||
//// Bytes returns a byteslice representation of the PublicKey.
|
||||
//func (p *PublicKey) Bytes() []byte {
|
||||
// b := p.X.Bytes()
|
||||
// padding := append(
|
||||
// bytes.Repeat(
|
||||
// []byte{0x00},
|
||||
// 32-len(b),
|
||||
// ),
|
||||
// b...,
|
||||
// )
|
||||
//
|
||||
// prefix := []byte{0x03}
|
||||
// if p.Y.Bit(0) == 0 {
|
||||
// prefix = []byte{0x02}
|
||||
// }
|
||||
//
|
||||
// return append(prefix, padding...)
|
||||
//}
|
||||
//
|
||||
//// Signature creates the signature of the PublicKey.
|
||||
//func (p *PublicKey) Signature() []byte {
|
||||
// b := p.Bytes()
|
||||
//
|
||||
// b = append([]byte{0x21}, b...)
|
||||
// b = append(b, 0xAC)
|
||||
//
|
||||
// sha256H := sha256.New()
|
||||
// sha256H.Write(b)
|
||||
// hash := sha256H.Sum(nil)
|
||||
//
|
||||
// ripemd160H := ripemd160.New()
|
||||
// ripemd160H.Write(hash)
|
||||
// return ripemd160H.Sum(nil)
|
||||
//}
|
||||
//
|
||||
//// PublicAddress derives the public NEO address that is coupled with the private key,
|
||||
//// and returns it as a string.
|
||||
//func (p *PublicKey) PublicAddress() string {
|
||||
// var (
|
||||
// b = p.Signature()
|
||||
// ver byte = 0x17
|
||||
// )
|
||||
//
|
||||
// b = append([]byte{ver}, b...)
|
||||
//
|
||||
// sha256H := sha256.New()
|
||||
// sha256H.Write(b)
|
||||
// hash := sha256H.Sum(nil)
|
||||
//
|
||||
// sha256H.Reset()
|
||||
// sha256H.Write(hash)
|
||||
// hash = sha256H.Sum(nil)
|
||||
//
|
||||
// b = append(b, hash[0:4]...)
|
||||
//
|
||||
// return crypto.Base58Encode(b)
|
||||
//}
|
21
pkg/wallet/transfer_output.go
Normal file
21
pkg/wallet/transfer_output.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/anthdm/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// TransferOutput respresents the output of a transaction.
|
||||
type TransferOutput struct {
|
||||
// The asset identifier. This should be of type Uint256.
|
||||
// NEO governing token: c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b
|
||||
// NEO gas: 602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7
|
||||
AssetID util.Uint256
|
||||
|
||||
// Value of the transfer
|
||||
Value *big.Int
|
||||
|
||||
// ScriptHash of the transfer.
|
||||
ScriptHash util.Uint160
|
||||
}
|
117
pkg/wallet/wallet.go
Normal file
117
pkg/wallet/wallet.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
// The current version of neo-go wallet implementations.
|
||||
walletVersion = "1.0"
|
||||
)
|
||||
|
||||
// Wallet respresents a NEO (NEP-2, NEP-6) compliant wallet.
|
||||
type Wallet struct {
|
||||
// Version of the wallet, used for later upgrades.
|
||||
Version string `json:"version"`
|
||||
|
||||
// A list of accounts which decribes the details of each account
|
||||
// in the wallet.
|
||||
Accounts []*Account `json:"accounts"`
|
||||
|
||||
Scrypt scryptParams `json:"scrypt"`
|
||||
|
||||
// Extra metadata can be used for storing abritrary data.
|
||||
// This field can be empty.
|
||||
Extra interface{} `json:"extra"`
|
||||
|
||||
// Path where the wallet file is located..
|
||||
path string
|
||||
|
||||
// ReadWriter for reading and writing wallet data.
|
||||
rw io.ReadWriter
|
||||
}
|
||||
|
||||
// NewWallet creates a new NEO wallet at the given location.
|
||||
func NewWallet(location string) (*Wallet, error) {
|
||||
file, err := os.Create(location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newWallet(file), nil
|
||||
}
|
||||
|
||||
// NewWalletFromFile creates a Wallet from the given wallet file path
|
||||
func NewWalletFromFile(path string) (*Wallet, error) {
|
||||
file, err := os.OpenFile(path, os.O_RDWR, os.ModeAppend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wall := &Wallet{
|
||||
rw: file,
|
||||
path: file.Name(),
|
||||
}
|
||||
if err := json.NewDecoder(file).Decode(wall); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wall, nil
|
||||
}
|
||||
|
||||
func newWallet(rw io.ReadWriter) *Wallet {
|
||||
var path string
|
||||
if f, ok := rw.(*os.File); ok {
|
||||
path = f.Name()
|
||||
}
|
||||
return &Wallet{
|
||||
Version: walletVersion,
|
||||
Accounts: []*Account{},
|
||||
Scrypt: newScryptParams(),
|
||||
rw: rw,
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// CreatAccount generates a new account for the end user and ecrypts
|
||||
// the private key with the given passphrase.
|
||||
func (w *Wallet) CreateAccount(name, passphrase string) error {
|
||||
acc, err := NewAccount()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acc.Label = name
|
||||
if err := acc.Encrypt(passphrase); err != nil {
|
||||
return err
|
||||
}
|
||||
w.AddAccount(acc)
|
||||
return w.Save()
|
||||
}
|
||||
|
||||
// AddAccount adds an existing Account to the wallet.
|
||||
func (w *Wallet) AddAccount(acc *Account) {
|
||||
w.Accounts = append(w.Accounts, acc)
|
||||
}
|
||||
|
||||
// Path returns the location of the wallet on the filesystem.
|
||||
func (w *Wallet) Path() string {
|
||||
return w.path
|
||||
}
|
||||
|
||||
// Save saves the wallet data. It's the internal io.ReadWriter
|
||||
// that is responsible for saving the data. This can
|
||||
// be a buffer, file, etc..
|
||||
func (w *Wallet) Save() error {
|
||||
return json.NewEncoder(w.rw).Encode(w)
|
||||
}
|
||||
|
||||
// JSON outputs a pretty JSON representation of the wallet.
|
||||
func (w *Wallet) JSON() ([]byte, error) {
|
||||
return json.MarshalIndent(w, " ", " ")
|
||||
}
|
||||
|
||||
// Close closes the internal rw if its an io.ReadCloser.
|
||||
func (w *Wallet) Close() {
|
||||
if rc, ok := w.rw.(io.ReadCloser); ok {
|
||||
rc.Close()
|
||||
}
|
||||
}
|
8
pkg/wallet/wallet_test.go
Normal file
8
pkg/wallet/wallet_test.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewWallet(t *testing.T) {
|
||||
}
|
92
pkg/wallet/wif.go
Normal file
92
pkg/wallet/wif.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||
)
|
||||
|
||||
const (
|
||||
// The WIF network version used to decode and encode WIF keys.
|
||||
WIFVersion = 0x80
|
||||
)
|
||||
|
||||
// WIF represents a wallet import format.
|
||||
type WIF struct {
|
||||
// Version of the wallet import format. Default to 0x80.
|
||||
Version byte
|
||||
|
||||
// Bool to determine if the WIF is compressed or not.
|
||||
Compressed bool
|
||||
|
||||
// A reference to the PrivateKey which this WIF is created from.
|
||||
PrivateKey *PrivateKey
|
||||
|
||||
// The string representation of the WIF.
|
||||
S string
|
||||
}
|
||||
|
||||
// WIFEncode encodes the given private key into a WIF string.
|
||||
func WIFEncode(key []byte, version byte, compressed bool) (s string, err error) {
|
||||
if version == 0x00 {
|
||||
version = WIFVersion
|
||||
}
|
||||
if len(key) != 32 {
|
||||
return s, fmt.Errorf("invalid private key length: %d", len(key))
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteByte(version)
|
||||
buf.Write(key)
|
||||
if compressed {
|
||||
buf.WriteByte(0x01)
|
||||
}
|
||||
|
||||
s = crypto.Base58CheckEncode(buf.Bytes())
|
||||
return
|
||||
}
|
||||
|
||||
// WIFDecode decoded the given WIF string into a WIF struct.
|
||||
func WIFDecode(wif string, version byte) (*WIF, error) {
|
||||
b, err := crypto.Base58CheckDecode(wif)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if version == 0x00 {
|
||||
version = WIFVersion
|
||||
}
|
||||
if b[0] != version {
|
||||
return nil, fmt.Errorf("invalid WIF version got %d, expected %d", b[0], version)
|
||||
}
|
||||
|
||||
// Derive the PrivateKey.
|
||||
privKey, err := NewPrivateKeyFromBytes(b[1:33])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w := &WIF{
|
||||
Version: version,
|
||||
PrivateKey: privKey,
|
||||
S: wif,
|
||||
}
|
||||
|
||||
// This is an uncompressed WIF
|
||||
if len(b) == 33 {
|
||||
w.Compressed = false
|
||||
return w, nil
|
||||
}
|
||||
|
||||
if len(b) != 34 {
|
||||
return nil, fmt.Errorf("invalid WIF length: %d expecting 34", len(b))
|
||||
}
|
||||
|
||||
// Check the compression flag.
|
||||
if b[33] != 0x01 {
|
||||
return nil, fmt.Errorf("invalid compression flag %d expecting %d", b[34], 0x01)
|
||||
}
|
||||
|
||||
w.Compressed = true
|
||||
return w, nil
|
||||
}
|
64
pkg/wallet/wif_test.go
Normal file
64
pkg/wallet/wif_test.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type wifTestCase struct {
|
||||
wif string
|
||||
compressed bool
|
||||
privateKey string
|
||||
version byte
|
||||
}
|
||||
|
||||
var wifTestCases = []wifTestCase{
|
||||
{
|
||||
wif: "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn",
|
||||
compressed: true,
|
||||
privateKey: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
version: 0x80,
|
||||
},
|
||||
{
|
||||
wif: "5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf",
|
||||
compressed: false,
|
||||
privateKey: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
version: 0x80,
|
||||
},
|
||||
{
|
||||
wif: "KxhEDBQyyEFymvfJD96q8stMbJMbZUb6D1PmXqBWZDU2WvbvVs9o",
|
||||
compressed: true,
|
||||
privateKey: "2bfe58ab6d9fd575bdc3a624e4825dd2b375d64ac033fbc46ea79dbab4f69a3e",
|
||||
version: 0x80,
|
||||
},
|
||||
}
|
||||
|
||||
func TestWIFEncodeDecode(t *testing.T) {
|
||||
for _, testCase := range wifTestCases {
|
||||
b, err := hex.DecodeString(testCase.privateKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wif, err := WIFEncode(b, testCase.version, testCase.compressed)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, have := testCase.wif, wif; want != have {
|
||||
t.Fatalf("expected %s got %s", want, have)
|
||||
}
|
||||
|
||||
WIF, err := WIFDecode(wif, testCase.version)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, have := testCase.privateKey, WIF.PrivateKey.String(); want != have {
|
||||
t.Fatalf("expected %s got %s", want, have)
|
||||
}
|
||||
if want, have := testCase.compressed, WIF.Compressed; want != have {
|
||||
t.Fatalf("expected %v got %v", want, have)
|
||||
}
|
||||
if want, have := testCase.version, WIF.Version; want != have {
|
||||
t.Fatalf("expected %d got %d", want, have)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue