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:
Anthony De Meulemeester 2018-03-02 16:24:09 +01:00 committed by GitHub
parent 942650dd8b
commit f3f6662fc9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 1754 additions and 87 deletions

52
Gopkg.lock generated
View file

@ -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

View file

@ -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"

View file

@ -1 +1 @@
0.18.0
0.19.0

View file

@ -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)

View file

@ -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
View 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("")
}

View file

@ -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
}

View file

@ -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...)
}

View file

@ -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
View 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
View 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)
}

View 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
}

View 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
}

View file

@ -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),

View file

@ -0,0 +1,5 @@
package smartcontract
// Contract represents a NEO smartcontract.
type Contract struct {
}

14
pkg/util/array.go Normal file
View 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
View 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)
}
}

View file

@ -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
View file

@ -0,0 +1 @@
package util

View file

@ -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()))
}

View file

@ -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)
}
}

View file

@ -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
View 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
}

View 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
View 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
View 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
}

View 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
View 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)
//}

View 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
View 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()
}
}

View file

@ -0,0 +1,8 @@
package wallet
import (
"testing"
)
func TestNewWallet(t *testing.T) {
}

92
pkg/wallet/wif.go Normal file
View 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
View 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)
}
}
}