diff --git a/Gopkg.lock b/Gopkg.lock index 0693251d5..4da50e70d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -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 diff --git a/Gopkg.toml b/Gopkg.toml index 5f647402b..bd0ec9ce2 100644 --- a/Gopkg.toml +++ b/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" diff --git a/VERSION b/VERSION index 66333910a..3f46c4d18 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.18.0 +0.19.0 \ No newline at end of file diff --git a/cli/main.go b/cli/main.go index e4b3e8fb3..8b4db6d7d 100644 --- a/cli/main.go +++ b/cli/main.go @@ -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) diff --git a/cli/server/server.go b/cli/server/server.go index a13681b72..cce204982 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -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", diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go new file mode 100644 index 000000000..209e6e340 --- /dev/null +++ b/cli/wallet/wallet.go @@ -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("") +} diff --git a/pkg/core/block.go b/pkg/core/block.go index bbd6b97d8..ed1739cd0 100644 --- a/pkg/core/block.go +++ b/pkg/core/block.go @@ -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 } diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 551d9692f..39a04e3c0 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -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...) } diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 0f095e5fa..6ac7cfda8 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -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{}}} diff --git a/pkg/crypto/aes256.go b/pkg/crypto/aes256.go new file mode 100644 index 000000000..bb0c97e96 --- /dev/null +++ b/pkg/crypto/aes256.go @@ -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:] + } +} diff --git a/pkg/crypto/base58.go b/pkg/crypto/base58.go new file mode 100644 index 000000000..07c4aeb47 --- /dev/null +++ b/pkg/crypto/base58.go @@ -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) +} diff --git a/pkg/crypto/elliptic_curve.go b/pkg/crypto/elliptic_curve.go new file mode 100644 index 000000000..40a6938f6 --- /dev/null +++ b/pkg/crypto/elliptic_curve.go @@ -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 +} diff --git a/pkg/crypto/modular_arithmetic.go b/pkg/crypto/modular_arithmetic.go new file mode 100644 index 000000000..54e02b263 --- /dev/null +++ b/pkg/crypto/modular_arithmetic.go @@ -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 +} diff --git a/pkg/network/server.go b/pkg/network/server.go index 66b7303ab..f7dfa93da 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -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), diff --git a/pkg/smartcontract/contract.go b/pkg/smartcontract/contract.go new file mode 100644 index 000000000..2b13035c5 --- /dev/null +++ b/pkg/smartcontract/contract.go @@ -0,0 +1,5 @@ +package smartcontract + +// Contract represents a NEO smartcontract. +type Contract struct { +} diff --git a/pkg/util/array.go b/pkg/util/array.go new file mode 100644 index 000000000..b6636f81a --- /dev/null +++ b/pkg/util/array.go @@ -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 +} diff --git a/pkg/util/array_test.go b/pkg/util/array_test.go new file mode 100644 index 000000000..8f587cef9 --- /dev/null +++ b/pkg/util/array_test.go @@ -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) + } +} diff --git a/pkg/util/uint160.go b/pkg/util/uint160.go index 529678fa8..f6bd0b9a5 100644 --- a/pkg/util/uint160.go +++ b/pkg/util/uint160.go @@ -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 } diff --git a/pkg/util/uint160_test.go b/pkg/util/uint160_test.go new file mode 100644 index 000000000..c7d868219 --- /dev/null +++ b/pkg/util/uint160_test.go @@ -0,0 +1 @@ +package util diff --git a/pkg/util/uint256.go b/pkg/util/uint256.go index 0f8c03439..d082d97cf 100644 --- a/pkg/util/uint256.go +++ b/pkg/util/uint256.go @@ -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())) } diff --git a/pkg/util/uint256_test.go b/pkg/util/uint256_test.go index 5229c138d..1a965c025 100644 --- a/pkg/util/uint256_test.go +++ b/pkg/util/uint256_test.go @@ -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) } } diff --git a/pkg/vm/compiler/emit.go b/pkg/vm/compiler/emit.go index 476855561..8d04156f2 100644 --- a/pkg/vm/compiler/emit.go +++ b/pkg/vm/compiler/emit.go @@ -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) } diff --git a/pkg/wallet/account.go b/pkg/wallet/account.go new file mode 100644 index 000000000..1c4ca3a73 --- /dev/null +++ b/pkg/wallet/account.go @@ -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 +} diff --git a/pkg/wallet/account_test.go b/pkg/wallet/account_test.go new file mode 100644 index 000000000..1c04ad9a2 --- /dev/null +++ b/pkg/wallet/account_test.go @@ -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) + } +} diff --git a/pkg/wallet/nep2.go b/pkg/wallet/nep2.go new file mode 100644 index 000000000..135049c30 --- /dev/null +++ b/pkg/wallet/nep2.go @@ -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) +} diff --git a/pkg/wallet/private_key.go b/pkg/wallet/private_key.go new file mode 100644 index 000000000..42ba4e483 --- /dev/null +++ b/pkg/wallet/private_key.go @@ -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 +} diff --git a/pkg/wallet/private_key_test.go b/pkg/wallet/private_key_test.go new file mode 100644 index 000000000..1c3961a94 --- /dev/null +++ b/pkg/wallet/private_key_test.go @@ -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) + } + } +} diff --git a/pkg/wallet/public_key.go b/pkg/wallet/public_key.go new file mode 100644 index 000000000..b8f9ec71c --- /dev/null +++ b/pkg/wallet/public_key.go @@ -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) +//} diff --git a/pkg/wallet/transfer_output.go b/pkg/wallet/transfer_output.go new file mode 100644 index 000000000..a6de5be14 --- /dev/null +++ b/pkg/wallet/transfer_output.go @@ -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 +} diff --git a/pkg/wallet/wallet.go b/pkg/wallet/wallet.go new file mode 100644 index 000000000..6211d0e32 --- /dev/null +++ b/pkg/wallet/wallet.go @@ -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() + } +} diff --git a/pkg/wallet/wallet_test.go b/pkg/wallet/wallet_test.go new file mode 100644 index 000000000..93b08c118 --- /dev/null +++ b/pkg/wallet/wallet_test.go @@ -0,0 +1,8 @@ +package wallet + +import ( + "testing" +) + +func TestNewWallet(t *testing.T) { +} diff --git a/pkg/wallet/wif.go b/pkg/wallet/wif.go new file mode 100644 index 000000000..97f179256 --- /dev/null +++ b/pkg/wallet/wif.go @@ -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 +} diff --git a/pkg/wallet/wif_test.go b/pkg/wallet/wif_test.go new file mode 100644 index 000000000..2705147b0 --- /dev/null +++ b/pkg/wallet/wif_test.go @@ -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) + } + } +}