From 47b51fcc19aac6fcac38612d2ec2aae017e554ff Mon Sep 17 00:00:00 2001 From: Apisit Toompakdee Date: Tue, 20 Feb 2018 11:33:08 +0900 Subject: [PATCH] in one place --- .gitignore | 1 + build.sh | 7 + neoutils/btckey/btckey.go | 651 +++++++++++++++++++++++++++++++ neoutils/btckey/btckey_test.go | 527 +++++++++++++++++++++++++ neoutils/btckey/elliptic.go | 292 ++++++++++++++ neoutils/btckey/elliptic_test.go | 225 +++++++++++ neoutils/encryption.go | 53 +++ neoutils/encryption_test.go | 92 +++++ neoutils/neowallet.go | 138 +++++++ neoutils/neowallet_test.go | 84 ++++ neoutils/network.go | 154 ++++++++ neoutils/network_test.go | 42 ++ neoutils/sss/LICENSE | 21 + neoutils/sss/README.md | 11 + neoutils/sss/gf256.go | 81 ++++ neoutils/sss/gf256_test.go | 35 ++ neoutils/sss/polynomial.go | 67 ++++ neoutils/sss/polynomial_test.go | 89 +++++ neoutils/sss/sss.go | 102 +++++ neoutils/sss/sss_test.go | 32 ++ neoutils/utils.go | 80 ++++ neoutils/utils_test.go | 44 +++ 22 files changed, 2828 insertions(+) create mode 100644 .gitignore create mode 100644 build.sh create mode 100644 neoutils/btckey/btckey.go create mode 100644 neoutils/btckey/btckey_test.go create mode 100644 neoutils/btckey/elliptic.go create mode 100644 neoutils/btckey/elliptic_test.go create mode 100644 neoutils/encryption.go create mode 100644 neoutils/encryption_test.go create mode 100644 neoutils/neowallet.go create mode 100644 neoutils/neowallet_test.go create mode 100644 neoutils/network.go create mode 100644 neoutils/network_test.go create mode 100644 neoutils/sss/LICENSE create mode 100644 neoutils/sss/README.md create mode 100644 neoutils/sss/gf256.go create mode 100644 neoutils/sss/gf256_test.go create mode 100644 neoutils/sss/polynomial.go create mode 100644 neoutils/sss/polynomial_test.go create mode 100644 neoutils/sss/sss.go create mode 100644 neoutils/sss/sss_test.go create mode 100644 neoutils/utils.go create mode 100644 neoutils/utils_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea1472e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +output/ diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..7946231 --- /dev/null +++ b/build.sh @@ -0,0 +1,7 @@ +#Android NDK is required. https://developer.android.com/ndk/guides/index.html +#to build for Android you need to run the command below +#gomobile init -ndk ~/Library/Android/sdk/ndk-bundle/ +#if you are using go 1.9.4 and ran into the issue related to #cgo CFLAGS: -fmodules +#here is the workaround: export CGO_CFLAGS_ALLOW='-fmodules|-fblocks' +gomobile bind -target=ios -o=output/ios/neoutils.framework github.com/o3labs/neo-utils/neoutils +ANDROID_HOME=/Users/apisit/Library/Android/sdk gomobile bind -target=android -o=output/android/neoutils.aar github.com/o3labs/neo-utils/neoutils diff --git a/neoutils/btckey/btckey.go b/neoutils/btckey/btckey.go new file mode 100644 index 0000000..ab5f789 --- /dev/null +++ b/neoutils/btckey/btckey.go @@ -0,0 +1,651 @@ +/* btckeygenie v1.0.0 + * https://github.com/vsergeev/btckeygenie + * License: MIT + */ + +package btckey + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "math/big" + "strings" + + "github.com/apisit/rfc6979" + "golang.org/x/crypto/ripemd160" +) + +/******************************************************************************/ +/* ECDSA Keypair Generation */ +/******************************************************************************/ + +var secp256r1 EllipticCurve + +func init() { + /* See Certicom's SEC2 2.7.1, pg.15 */ + /* secp256k1 elliptic curve parameters */ + // secp256k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16) + // secp256k1.A, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000000", 16) + // secp256k1.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16) + // secp256k1.G.X, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16) + // secp256k1.G.Y, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16) + // secp256k1.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) + // secp256k1.H, _ = new(big.Int).SetString("01", 16) + + //NEO uses SECP256R1 + secp256r1.P, _ = new(big.Int).SetString("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16) //Q + secp256r1.A, _ = new(big.Int).SetString("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16) + secp256r1.B, _ = new(big.Int).SetString("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16) + secp256r1.G.X, _ = new(big.Int).SetString("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16) + secp256r1.G.Y, _ = new(big.Int).SetString("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16) + secp256r1.N, _ = new(big.Int).SetString("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16) + secp256r1.H, _ = new(big.Int).SetString("01", 16) +} + +// PublicKey represents a Bitcoin public key. +type PublicKey struct { + Point +} + +// PrivateKey represents a Bitcoin private key. +type PrivateKey struct { + PublicKey + D *big.Int +} + +// derive derives a Bitcoin public key from a Bitcoin private key. +func (priv *PrivateKey) derive() (pub *PublicKey) { + /* See Certicom's SEC1 3.2.1, pg.23 */ + + /* Derive public key from Q = d*G */ + Q := secp256r1.ScalarBaseMult(priv.D) + + /* Check that Q is on the curve */ + if !secp256r1.IsOnCurve(Q) { + panic("Catastrophic math logic failure in public key derivation.") + } + + priv.X = Q.X + priv.Y = Q.Y + + return &priv.PublicKey +} + +// GenerateKey generates a public and private key pair using random source rand. +func GenerateKey(rand io.Reader) (priv PrivateKey, err error) { + /* See Certicom's SEC1 3.2.1, pg.23 */ + /* See NSA's Suite B Implementer’s Guide to FIPS 186-3 (ECDSA) A.1.1, pg.18 */ + + /* Select private key d randomly from [1, n) */ + + /* Read N bit length random bytes + 64 extra bits */ + b := make([]byte, secp256r1.N.BitLen()/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return priv, fmt.Errorf("Reading random reader: %v", err) + } + + d := new(big.Int).SetBytes(b) + + /* Mod n-1 to shift d into [0, n-1) range */ + d.Mod(d, new(big.Int).Sub(secp256r1.N, big.NewInt(1))) + /* Add one to shift d to [1, n) range */ + d.Add(d, big.NewInt(1)) + + priv.D = d + + /* Derive public key from private key */ + priv.derive() + + return priv, nil +} + +/******************************************************************************/ +/* Base-58 Encode/Decode */ +/******************************************************************************/ + +// b58encode encodes a byte slice b into a base-58 encoded string. +func b58encode(b []byte) (s string) { + /* See https://en.bitcoin.it/wiki/Base58Check_encoding */ + + const BITCOIN_BASE58_TABLE = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + + /* Convert big endian bytes to big int */ + x := new(big.Int).SetBytes(b) + + /* Initialize */ + r := new(big.Int) + m := big.NewInt(58) + zero := big.NewInt(0) + s = "" + + /* Convert big int to string */ + for x.Cmp(zero) > 0 { + /* x, r = (x / 58, x % 58) */ + x.QuoRem(x, m, r) + /* Prepend ASCII character */ + s = string(BITCOIN_BASE58_TABLE[r.Int64()]) + s + } + + return s +} + +// b58decode decodes a base-58 encoded string into a byte slice b. +func b58decode(s string) (b []byte, err error) { + /* See https://en.bitcoin.it/wiki/Base58Check_encoding */ + + const BITCOIN_BASE58_TABLE = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + + /* Initialize */ + x := big.NewInt(0) + m := big.NewInt(58) + + /* Convert string to big int */ + for i := 0; i < len(s); i++ { + b58index := strings.IndexByte(BITCOIN_BASE58_TABLE, s[i]) + if b58index == -1 { + return nil, fmt.Errorf("Invalid base-58 character encountered: '%c', index %d.", s[i], i) + } + b58value := big.NewInt(int64(b58index)) + x.Mul(x, m) + x.Add(x, b58value) + } + + /* Convert big int to big endian bytes */ + b = x.Bytes() + + return b, nil +} + +/******************************************************************************/ +/* Base-58 Check Encode/Decode */ +/******************************************************************************/ + +// b58checkencode encodes version ver and byte slice b into a base-58 check encoded string. +func b58checkencode(ver uint8, b []byte) (s string) { + /* Prepend version */ + bcpy := append([]byte{ver}, b...) + + /* Create a new SHA256 context */ + sha256_h := sha256.New() + + /* SHA256 Hash #1 */ + sha256_h.Reset() + sha256_h.Write(bcpy) + hash1 := sha256_h.Sum(nil) + + /* SHA256 Hash #2 */ + sha256_h.Reset() + sha256_h.Write(hash1) + hash2 := sha256_h.Sum(nil) + + /* Append first four bytes of hash */ + bcpy = append(bcpy, hash2[0:4]...) + + /* Encode base58 string */ + s = b58encode(bcpy) + + /* For number of leading 0's in bytes, prepend 1 */ + for _, v := range bcpy { + if v != 0 { + break + } + s = "1" + s + } + + return s +} + +// b58checkencode encodes version ver and byte slice b into a base-58 check encoded string. +func B58checkencodeNEO(ver uint8, b []byte) (s string) { + /* Prepend version */ + bcpy := append([]byte{ver}, b...) + + /* Create a new SHA256 context */ + sha256_h := sha256.New() + + /* SHA256 Hash #1 */ + sha256_h.Reset() + sha256_h.Write(bcpy) + hash1 := sha256_h.Sum(nil) + + /* SHA256 Hash #2 */ + sha256_h.Reset() + sha256_h.Write(hash1) + hash2 := sha256_h.Sum(nil) + + /* Append first four bytes of hash */ + bcpy = append(bcpy, hash2[0:4]...) + + /* Encode base58 string */ + s = b58encode(bcpy) + + // /* For number of leading 0's in bytes, prepend 1 */ + // for _, v := range bcpy { + // if v != 0 { + // break + // } + // s = "1" + s + // } + + return s +} + +// B58checkdecode decodes base-58 check encoded string s into a version ver and byte slice b. +func B58checkdecode(s string) (ver uint8, b []byte, err error) { + /* Decode base58 string */ + b, err = b58decode(s) + if err != nil { + return 0, nil, err + } + + /* Add leading zero bytes */ + for i := 0; i < len(s); i++ { + if s[i] != '1' { + break + } + b = append([]byte{0x00}, b...) + } + + /* Verify checksum */ + if len(b) < 5 { + return 0, nil, fmt.Errorf("Invalid base-58 check string: missing checksum.") + } + + /* Create a new SHA256 context */ + sha256_h := sha256.New() + + /* SHA256 Hash #1 */ + sha256_h.Reset() + sha256_h.Write(b[:len(b)-4]) + hash1 := sha256_h.Sum(nil) + + /* SHA256 Hash #2 */ + sha256_h.Reset() + sha256_h.Write(hash1) + hash2 := sha256_h.Sum(nil) + + /* Compare checksum */ + if bytes.Compare(hash2[0:4], b[len(b)-4:]) != 0 { + return 0, nil, fmt.Errorf("Invalid base-58 check string: invalid checksum.") + } + + /* Strip checksum bytes */ + b = b[:len(b)-4] + + /* Extract and strip version */ + ver = b[0] + b = b[1:] + + return ver, b, nil +} + +/******************************************************************************/ +/* Bitcoin Private Key Import/Export */ +/******************************************************************************/ + +// CheckWIF checks that string wif is a valid Wallet Import Format or Wallet Import Format Compressed string. If it is not, err is populated with the reason. +func CheckWIF(wif string) (valid bool, err error) { + /* See https://en.bitcoin.it/wiki/Wallet_import_format */ + + /* Base58 Check Decode the WIF string */ + ver, priv_bytes, err := B58checkdecode(wif) + if err != nil { + return false, err + } + + /* Check that the version byte is 0x80 */ + if ver != 0x80 { + return false, fmt.Errorf("Invalid WIF version 0x%02x, expected 0x80.", ver) + } + + /* Check that private key bytes length is 32 or 33 */ + if len(priv_bytes) != 32 && len(priv_bytes) != 33 { + return false, fmt.Errorf("Invalid private key bytes length %d, expected 32 or 33.", len(priv_bytes)) + } + + /* If the private key bytes length is 33, check that suffix byte is 0x01 (for compression) */ + if len(priv_bytes) == 33 && priv_bytes[len(priv_bytes)-1] != 0x01 { + return false, fmt.Errorf("Invalid private key bytes, unknown suffix byte 0x%02x.", priv_bytes[len(priv_bytes)-1]) + } + + return true, nil +} + +// ToBytes converts a Bitcoin private key to a 32-byte byte slice. +func (priv *PrivateKey) ToBytes() (b []byte) { + d := priv.D.Bytes() + + /* Pad D to 32 bytes */ + padded_d := append(bytes.Repeat([]byte{0x00}, 32-len(d)), d...) + + return padded_d +} + +// FromBytes converts a 32-byte byte slice to a Bitcoin private key and derives the corresponding Bitcoin public key. +func (priv *PrivateKey) FromBytes(b []byte) (err error) { + if len(b) != 32 { + return fmt.Errorf("Invalid private key bytes length %d, expected 32.", len(b)) + } + + priv.D = new(big.Int).SetBytes(b) + + /* Derive public key from private key */ + priv.derive() + + return nil +} + +// ToWIF converts a Bitcoin private key to a Wallet Import Format string. +func (priv *PrivateKey) ToWIF() (wif string) { + /* See https://en.bitcoin.it/wiki/Wallet_import_format */ + + /* Convert the private key to bytes */ + priv_bytes := priv.ToBytes() + + /* Convert bytes to base-58 check encoded string with version 0x80 */ + wif = b58checkencode(0x80, priv_bytes) + + return wif +} + +// ToWIFC converts a Bitcoin private key to a Wallet Import Format string with the public key compressed flag. +func (priv *PrivateKey) ToWIFC() (wifc string) { + /* See https://en.bitcoin.it/wiki/Wallet_import_format */ + + /* Convert the private key to bytes */ + priv_bytes := priv.ToBytes() + + /* Append 0x01 to tell Bitcoin wallet to use compressed public keys */ + priv_bytes = append(priv_bytes, []byte{0x01}...) + + /* Convert bytes to base-58 check encoded string with version 0x80 */ + wifc = b58checkencode(0x80, priv_bytes) + + return wifc +} + +// FromWIF converts a Wallet Import Format string to a Bitcoin private key and derives the corresponding Bitcoin public key. +func (priv *PrivateKey) FromWIF(wif string) (err error) { + /* See https://en.bitcoin.it/wiki/Wallet_import_format */ + + /* Base58 Check Decode the WIF string */ + ver, priv_bytes, err := B58checkdecode(wif) + if err != nil { + return err + } + + /* Check that the version byte is 0x80 */ + if ver != 0x80 { + return fmt.Errorf("Invalid WIF version 0x%02x, expected 0x80.", ver) + } + + /* If the private key bytes length is 33, check that suffix byte is 0x01 (for compression) and strip it off */ + if len(priv_bytes) == 33 { + if priv_bytes[len(priv_bytes)-1] != 0x01 { + return fmt.Errorf("Invalid private key, unknown suffix byte 0x%02x.", priv_bytes[len(priv_bytes)-1]) + } + priv_bytes = priv_bytes[0:32] + } + + /* Convert from bytes to a private key */ + err = priv.FromBytes(priv_bytes) + if err != nil { + return err + } + + /* Derive public key from private key */ + priv.derive() + + return nil +} + +/******************************************************************************/ +/* Bitcoin Public Key Import/Export */ +/******************************************************************************/ + +// ToBytes converts a Bitcoin public key to a 33-byte byte slice with point compression. +func (pub *PublicKey) ToBytes() (b []byte) { + /* See Certicom SEC1 2.3.3, pg. 10 */ + + x := pub.X.Bytes() + + /* Pad X to 32-bytes */ + padded_x := append(bytes.Repeat([]byte{0x00}, 32-len(x)), x...) + + /* Add prefix 0x02 or 0x03 depending on ylsb */ + if pub.Y.Bit(0) == 0 { + return append([]byte{0x02}, padded_x...) + } + + return append([]byte{0x03}, padded_x...) +} + +// ToBytesUncompressed converts a Bitcoin public key to a 65-byte byte slice without point compression. +func (pub *PublicKey) ToBytesUncompressed() (b []byte) { + /* See Certicom SEC1 2.3.3, pg. 10 */ + + x := pub.X.Bytes() + y := pub.Y.Bytes() + + /* Pad X and Y coordinate bytes to 32-bytes */ + padded_x := append(bytes.Repeat([]byte{0x00}, 32-len(x)), x...) + padded_y := append(bytes.Repeat([]byte{0x00}, 32-len(y)), y...) + + /* Add prefix 0x04 for uncompressed coordinates */ + return append([]byte{0x04}, append(padded_x, padded_y...)...) +} + +// FromBytes converts a byte slice (either with or without point compression) to a Bitcoin public key. +func (pub *PublicKey) FromBytes(b []byte) (err error) { + /* See Certicom SEC1 2.3.4, pg. 11 */ + + if len(b) < 33 { + return fmt.Errorf("Invalid public key bytes length %d, expected at least 33.", len(b)) + } + + if b[0] == 0x02 || b[0] == 0x03 { + /* Compressed public key */ + + if len(b) != 33 { + return fmt.Errorf("Invalid public key bytes length %d, expected 33.", len(b)) + } + + P, err := secp256r1.Decompress(new(big.Int).SetBytes(b[1:33]), uint(b[0]&0x1)) + if err != nil { + return fmt.Errorf("Invalid compressed public key bytes, decompression error: %v", err) + } + + pub.X = P.X + pub.Y = P.Y + + } else if b[0] == 0x04 { + /* Uncompressed public key */ + + if len(b) != 65 { + return fmt.Errorf("Invalid public key bytes length %d, expected 65.", len(b)) + } + + pub.X = new(big.Int).SetBytes(b[1:33]) + pub.Y = new(big.Int).SetBytes(b[33:65]) + + /* Check that the point is on the curve */ + if !secp256r1.IsOnCurve(pub.Point) { + return fmt.Errorf("Invalid public key bytes: point not on curve.") + } + + } else { + return fmt.Errorf("Invalid public key prefix byte 0x%02x, expected 0x02, 0x03, or 0x04.", b[0]) + } + + return nil +} + +func sha256Bytes(b []byte) []byte { + /* SHA256 Hash */ + sha256_h := sha256.New() + sha256_h.Reset() + sha256_h.Write(b) + return sha256_h.Sum(nil) +} + +func (pub *PublicKey) ToNeoSignature() (signature []byte) { + pub_bytes := pub.ToBytes() + + pub_bytes = append([]byte{0x21}, pub_bytes...) + pub_bytes = append(pub_bytes, 0xAC) + + /* SHA256 Hash */ + sha256_h := sha256.New() + sha256_h.Reset() + sha256_h.Write(pub_bytes) + pub_hash_1 := sha256_h.Sum(nil) + + /* RIPEMD-160 Hash */ + ripemd160_h := ripemd160.New() + ripemd160_h.Reset() + ripemd160_h.Write(pub_hash_1) + pub_hash_2 := ripemd160_h.Sum(nil) + + return pub_hash_2 +} + +// ToAddress converts a Bitcoin public key to a compressed Bitcoin address string. +func (pub *PublicKey) ToNeoAddress() (address string) { + /* See https://en.bitcoin.it/wiki/Technical_background_of_Bitcoin_addresses */ + + /* Convert the public key to bytes */ + pub_bytes := pub.ToBytes() + + pub_bytes = append([]byte{0x21}, pub_bytes...) + pub_bytes = append(pub_bytes, 0xAC) + + /* SHA256 Hash */ + sha256_h := sha256.New() + sha256_h.Reset() + sha256_h.Write(pub_bytes) + pub_hash_1 := sha256_h.Sum(nil) + + /* RIPEMD-160 Hash */ + ripemd160_h := ripemd160.New() + ripemd160_h.Reset() + ripemd160_h.Write(pub_hash_1) + pub_hash_2 := ripemd160_h.Sum(nil) + + program_hash := pub_hash_2 + + //wallet version + //program_hash = append([]byte{0x17}, program_hash...) + + // doublesha := sha256Bytes(sha256Bytes(program_hash)) + + // checksum := doublesha[0:4] + + // result := append(program_hash, checksum...) + /* Convert hash bytes to base58 check encoded sequence */ + address = B58checkencodeNEO(0x17, program_hash) + + return address +} + +// ToAddress converts a Bitcoin public key to a compressed Bitcoin address string. +func (pub *PublicKey) ToAddress() (address string) { + /* See https://en.bitcoin.it/wiki/Technical_background_of_Bitcoin_addresses */ + + /* Convert the public key to bytes */ + pub_bytes := pub.ToBytes() + + /* SHA256 Hash */ + sha256_h := sha256.New() + sha256_h.Reset() + sha256_h.Write(pub_bytes) + pub_hash_1 := sha256_h.Sum(nil) + + /* RIPEMD-160 Hash */ + ripemd160_h := ripemd160.New() + ripemd160_h.Reset() + ripemd160_h.Write(pub_hash_1) + pub_hash_2 := ripemd160_h.Sum(nil) + + /* Convert hash bytes to base58 check encoded sequence */ + address = b58checkencode(0x00, pub_hash_2) + + return address +} + +// ToAddressUncompressed converts a Bitcoin public key to an uncompressed Bitcoin address string. +func (pub *PublicKey) ToAddressUncompressed() (address string) { + /* See https://en.bitcoin.it/wiki/Technical_background_of_Bitcoin_addresses */ + + /* Convert the public key to bytes */ + pub_bytes := pub.ToBytesUncompressed() + + /* SHA256 Hash */ + sha256_h := sha256.New() + sha256_h.Reset() + sha256_h.Write(pub_bytes) + pub_hash_1 := sha256_h.Sum(nil) + + /* RIPEMD-160 Hash */ + ripemd160_h := ripemd160.New() + ripemd160_h.Reset() + ripemd160_h.Write(pub_hash_1) + pub_hash_2 := ripemd160_h.Sum(nil) + + /* Convert hash bytes to base58 check encoded sequence */ + address = b58checkencode(0x00, pub_hash_2) + + return address +} +func hex2bytes(hexstring string) (b []byte) { + b, _ = hex.DecodeString(hexstring) + return b +} + +func PrivateKeyFromHexString(key string) ecdsa.PrivateKey { + b := hex2bytes(key) + + priv := new(ecdsa.PrivateKey) + priv.PublicKey.Curve = elliptic.P256() + priv.D = new(big.Int).SetBytes(b) + priv.PublicKey.X, priv.PublicKey.Y = priv.PublicKey.Curve.ScalarBaseMult(b) + return *priv +} + +func Sign(data []byte, key string) ([]byte, error) { + + var privateKey ecdsa.PrivateKey + privateKey = PrivateKeyFromHexString(key) + 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 +} + +func Verify(publicKey []byte, signature []byte, hash []byte) bool { + pub := PublicKey{} + pub.FromBytes(publicKey) + p := &ecdsa.PublicKey{} + p.Curve = elliptic.P256() + p.X = pub.X + p.Y = pub.Y + rBytes := new(big.Int).SetBytes(signature[0:32]) + sBytes := new(big.Int).SetBytes(signature[32:64]) + return ecdsa.Verify(p, hash, rBytes, sBytes) +} diff --git a/neoutils/btckey/btckey_test.go b/neoutils/btckey/btckey_test.go new file mode 100644 index 0000000..145208b --- /dev/null +++ b/neoutils/btckey/btckey_test.go @@ -0,0 +1,527 @@ +/* btckeygenie v1.0.0 + * https://github.com/vsergeev/btckeygenie + * License: MIT + */ + +package btckey + +import ( + "bytes" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "log" + "math/big" + "testing" +) + +/******************************************************************************/ +/* Base-58 Encode/Decode */ +/******************************************************************************/ + +func TestBase58(t *testing.T) { + var b58Vectors = []struct { + bytes []byte + encoded string + }{ + {hex2bytes("4e19"), "6wi"}, + {hex2bytes("3ab7"), "5UA"}, + {hex2bytes("ae0ddc9b"), "5T3W5p"}, + {hex2bytes("65e0b4c9"), "3c3E6L"}, + {hex2bytes("25793686e9f25b6b"), "7GYJp3ZThFG"}, + {hex2bytes("94b9ac084a0d65f5"), "RspedB5CMo2"}, + } + + /* Test base-58 encoding */ + for i := 0; i < len(b58Vectors); i++ { + got := b58encode(b58Vectors[i].bytes) + if got != b58Vectors[i].encoded { + t.Fatalf("b58encode(%v): got %s, expected %s", b58Vectors[i].bytes, got, b58Vectors[i].encoded) + } + } + t.Log("success b58encode() on valid vectors") + + /* Test base-58 decoding */ + for i := 0; i < len(b58Vectors); i++ { + got, err := b58decode(b58Vectors[i].encoded) + if err != nil { + t.Fatalf("b58decode(%s): got error %v, expected %v", b58Vectors[i].encoded, err, b58Vectors[i].bytes) + } + if bytes.Compare(got, b58Vectors[i].bytes) != 0 { + t.Fatalf("b58decode(%s): got %v, expected %v", b58Vectors[i].encoded, got, b58Vectors[i].bytes) + } + } + t.Log("success b58decode() on valid vectors") + + /* Test base-58 decoding of invalid strings */ + b58InvalidVectors := []string{ + "5T3IW5p", // Invalid character I + "6Owi", // Invalid character O + } + + for i := 0; i < len(b58InvalidVectors); i++ { + got, err := b58decode(b58InvalidVectors[i]) + if err == nil { + t.Fatalf("b58decode(%s): got %v, expected error", b58InvalidVectors[i], got) + } + t.Logf("b58decode(%s): got expected err %v", b58InvalidVectors[i], err) + } + t.Log("success b58decode() on invalid vectors") +} + +/******************************************************************************/ +/* Base-58 Check Encode/Decode */ +/******************************************************************************/ + +func TestBase58Check(t *testing.T) { + var b58CheckVectors = []struct { + ver uint8 + bytes []byte + encoded string + }{ + {0x00, hex2bytes("010966776006953D5567439E5E39F86A0D273BEE"), "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM"}, + {0x00, hex2bytes("000000006006953D5567439E5E39F86A0D273BEE"), "111112LbMksD9tCRVsyW67atmDssDkHHG"}, + {0x80, hex2bytes("0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D"), "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"}, + } + + /* Test base-58 check encoding */ + for i := 0; i < len(b58CheckVectors); i++ { + got := b58checkencode(b58CheckVectors[i].ver, b58CheckVectors[i].bytes) + if got != b58CheckVectors[i].encoded { + t.Fatalf("b58checkencode(0x%02x, %v): got %s, expected %s", b58CheckVectors[i].ver, b58CheckVectors[i].bytes, got, b58CheckVectors[i].encoded) + } + } + t.Log("success b58checkencode() on valid vectors") + + /* Test base-58 check decoding */ + for i := 0; i < len(b58CheckVectors); i++ { + ver, got, err := B58checkdecode(b58CheckVectors[i].encoded) + if err != nil { + t.Fatalf("B58checkdecode(%s): got error %v, expected ver %v, bytes %v", b58CheckVectors[i].encoded, err, b58CheckVectors[i].ver, b58CheckVectors[i].bytes) + } + if ver != b58CheckVectors[i].ver || bytes.Compare(got, b58CheckVectors[i].bytes) != 0 { + t.Fatalf("B58checkdecode(%s): got ver %v, bytes %v, expected ver %v, bytes %v", b58CheckVectors[i].encoded, ver, got, b58CheckVectors[i].ver, b58CheckVectors[i].bytes) + } + } + t.Log("success B58checkdecode() on valid vectors") + + /* Test base-58 check decoding of invalid strings */ + b58CheckInvalidVectors := []string{ + "5T3IW5p", // Invalid base58 + "6wi", // Missing checksum + "6UwLL9Risc3QfPqBUvKofHmBQ7wMtjzm", // Invalid checksum + } + + for i := 0; i < len(b58CheckInvalidVectors); i++ { + ver, got, err := B58checkdecode(b58CheckInvalidVectors[i]) + if err == nil { + t.Fatalf("B58checkdecode(%s): got ver %v, bytes %v, expected error", b58CheckInvalidVectors[i], ver, got) + } + t.Logf("B58checkdecode(%s): got expected err %v", b58CheckInvalidVectors[i], err) + } + t.Log("success B58checkdecode() on invalid vectors") +} + +/******************************************************************************/ +/* Common Key Pair Test Vectors */ +/******************************************************************************/ + +var keyPairVectors = []struct { + wif string + wifc string + priv_bytes []byte + address_compressed string + address_uncompressed string + pub_bytes_compressed []byte + pub_bytes_uncompressed []byte + D *big.Int + X *big.Int + Y *big.Int +}{ + { + "5J1F7GHadZG3sCCKHCwg8Jvys9xUbFsjLnGec4H125Ny1V9nR6V", + "Kx45GeUBSMPReYQwgXiKhG9FzNXrnCeutJp4yjTd5kKxCitadm3C", + hex2bytes("18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725"), + "1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs", + "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM", + hex2bytes("0250863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B2352"), + hex2bytes("0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6"), + hex2int("18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725"), + hex2int("50863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B2352"), + hex2int("2CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6"), + }, + { + "5JbDYniwPgAn3YqPUkVvrCQdJsjjFx2rV2EYeg5CAH3wNncziMm", + "Kze2PJp755t9pFWaDUzgg9MHFtwbWyuBQgSnhHTWFwqy14NafA1S", + hex2bytes("660527765029F5F1BC6DFD5821A7FF336C10EDA391E19BB4517DB4E23E5B112F"), + "1ChaLikBC5E2uTCA7GZh9vaMQMuRt7h1yq", + "17FBpEDgirwQJTvHT6ZgSirWSCbdTB9f76", + hex2bytes("03A83B8DE893467D3A88D959C0EB4032D9CE3BF80F175D4D9E75892A3EBB8AB7E5"), + hex2bytes("04A83B8DE893467D3A88D959C0EB4032D9CE3BF80F175D4D9E75892A3EBB8AB7E5370F723328C24B7A97FE34063BA68F253FB08F8645D7C8B9A4FF98E3C29E7F0D"), + hex2int("660527765029F5F1BC6DFD5821A7FF336C10EDA391E19BB4517DB4E23E5B112F"), + hex2int("A83B8DE893467D3A88D959C0EB4032D9CE3BF80F175D4D9E75892A3EBB8AB7E5"), + hex2int("370F723328C24B7A97FE34063BA68F253FB08F8645D7C8B9A4FF98E3C29E7F0D"), + }, + { + "5KPaskZdrcPmrH3AFdpMF7FFBcYigwdrEfpBN9K5Ch4Ch6Bort4", + "L4AgX1H3fyDWxVnXqbVzMGZsbqu11J9eKLKEkKgmYbo8bbs4K9Sq", + hex2bytes("CF4DBE1ABCB061DB64CC87404AB736B6A56E8CDD40E9846144582240C5366758"), + "1DP4edYeSPAF5UkXomAFKhsXwKq59r26aY", + "1K1EJ6Zob7mr6Wye9mF1pVaU4tpDhrYMKJ", + hex2bytes("03F680556678E25084A82FA39E1B1DFD0944F7E69FDDAA4E03CE934BD6B291DCA0"), + hex2bytes("04F680556678E25084A82FA39E1B1DFD0944F7E69FDDAA4E03CE934BD6B291DCA052C10B721D34447E173721FB0151C68DE1106BADB089FB661523B8302A9097F5"), + hex2int("CF4DBE1ABCB061DB64CC87404AB736B6A56E8CDD40E9846144582240C5366758"), + hex2int("F680556678E25084A82FA39E1B1DFD0944F7E69FDDAA4E03CE934BD6B291DCA0"), + hex2int("52C10B721D34447E173721FB0151C68DE1106BADB089FB661523B8302A9097F5"), + }, + { + "5KTzSQJFWc48YdgxXJPb7BhnHu98TUd6C8CDNw6D2dq8fVfC5G8", + "L4W8X7q93fipJcwN4jkhYJEzub8survHv6kVdojz6DZSpYUmJYkM", + hex2bytes("D94F024E82D787FB38369BEA7478AA61308DC2F7080ADDF69919A881490CFF48"), + "1Hicf8AisGTeFqhNuSTw5m5UsYbHRxDxfj", + "1CqhvePnxy5ZdvuunhZ7KzaqJVrNfXAk5E", + hex2bytes("02692B035A2BB89C503E68A732596491524808BC2CC6A95061CD2CDE5151B34CD8"), + hex2bytes("04692B035A2BB89C503E68A732596491524808BC2CC6A95061CD2CDE5151B34CD8B9FBFB401C7BDA0C77F161ADE0AA54688412E591DAF2E3A652DB00A533645B24"), + hex2int("D94F024E82D787FB38369BEA7478AA61308DC2F7080ADDF69919A881490CFF48"), + hex2int("692B035A2BB89C503E68A732596491524808BC2CC6A95061CD2CDE5151B34CD8"), + hex2int("B9FBFB401C7BDA0C77F161ADE0AA54688412E591DAF2E3A652DB00A533645B24"), + }, + { + "5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsrgA9tXshp", + "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFUFqJ5Vvujp", + hex2bytes("0000000000000000000000000000000000000000000000000000000000000400"), + "1G73bvYR97QGVb8bfeX2TqvSKietBDybQC", + "17imJe7o4mpq2MMfZ328evDJQfbt6ShvxA", + hex2bytes("03241FEBB8E23CBD77D664A18F66AD6240AAEC6ECDC813B088D5B901B2E285131F"), + hex2bytes("04241FEBB8E23CBD77D664A18F66AD6240AAEC6ECDC813B088D5B901B2E285131F513378D9FF94F8D3D6C420BD13981DF8CD50FD0FBD0CB5AFABB3E66F2750026D"), + hex2int("0000000000000000000000000000000000000000000000000000000000000400"), + hex2int("241FEBB8E23CBD77D664A18F66AD6240AAEC6ECDC813B088D5B901B2E285131F"), + hex2int("513378D9FF94F8D3D6C420BD13981DF8CD50FD0FBD0CB5AFABB3E66F2750026D"), + }, +} + +var invalidPublicKeyBytesVectors = [][]byte{ + hex2bytes("0250863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23"), /* Short compressed */ + hex2bytes("0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582B"), /* Short uncompressed */ + hex2bytes("03A83B8DFF93467D3A88D959C0EB4032FFFF3BF80F175D4D9E75892A3EBB8FF7E5"), /* Invalid compressed */ + hex2bytes("02c8e337cee51ae9af3c0ef923705a0cb1b76f7e8463b3d3060a1c8d795f9630fd"), /* Invalid compressed */ +} + +var wifInvalidVectors = []string{ + "5T3IW5p", // Invalid base58 + "6wi", // Missing checksum + "6Mcb23muAxyXaSMhmB6B1mqkvLdWhtuFZmnZsxDczHRraMcNG", // Invalid checksum + "huzKTSifqNioknFPsoA7uc359rRHJQHRg42uiKn6P8Rnv5qxV5", // Invalid version byte + "yPoVP5njSzmEVK4VJGRWWAwqnwCyLPRcMm5XyrKgYUpeXtGyM", // Invalid private key byte length + "Kx45GeUBSMPReYQwgXiKhG9FzNXrnCeutJp4yjTd5kKxCj6CAKu3", // Invalid private key suffix byte +} + +func TestCheckWIF(t *testing.T) { + /* Check valid vectors */ + for i := 0; i < len(keyPairVectors); i++ { + got, err := CheckWIF(keyPairVectors[i].wif) + if got == false { + t.Fatalf("CheckWIF(%s): got false, error %v, expected true", keyPairVectors[i].wif, err) + } + got, err = CheckWIF(keyPairVectors[i].wifc) + if got == false { + t.Fatalf("CheckWIF(%s): got false, error %v, expected true", keyPairVectors[i].wifc, err) + } + } + t.Log("success CheckWIF() on valid vectors") + + /* Check invalid vectors */ + for i := 0; i < len(wifInvalidVectors); i++ { + got, err := CheckWIF(wifInvalidVectors[i]) + if got == true { + t.Fatalf("CheckWIF(%s): got true, expected false", wifInvalidVectors[i]) + } + t.Logf("CheckWIF(%s): got false, err %v", wifInvalidVectors[i], err) + } + t.Log("success CheckWIF() on invalid vectors") +} + +func TestPrivateKeyDerive(t *testing.T) { + var priv PrivateKey + + for i := 0; i < len(keyPairVectors); i++ { + priv.D = keyPairVectors[i].D + + /* Derive public key from private key */ + priv.derive() + + if priv.X.Cmp(keyPairVectors[i].X) != 0 || priv.Y.Cmp(keyPairVectors[i].Y) != 0 { + t.Fatalf("derived public key does not match test vector on index %d", i) + } + } + t.Log("success PrivateKey derive()") +} + +/******************************************************************************/ +/* Bitcoin Private Key Import/Export */ +/******************************************************************************/ + +func TestPrivateKeyFromBytes(t *testing.T) { + var priv PrivateKey + + /* Check valid vectors */ + for i := 0; i < len(keyPairVectors); i++ { + err := priv.FromBytes(keyPairVectors[i].priv_bytes) + if err != nil { + t.Fatalf("priv.FromBytes(D): got error %v, expected success on index %d", err, i) + } + if priv.D.Cmp(keyPairVectors[i].D) != 0 { + t.Fatalf("private key does not match test vector on index %d", i) + } + if priv.X.Cmp(keyPairVectors[i].X) != 0 || priv.Y.Cmp(keyPairVectors[i].Y) != 0 { + t.Fatalf("public key does not match test vector on index %d", i) + } + } + t.Log("success PrivateKey FromBytes() on valid vectors") + + /* Invalid short private key */ + err := priv.FromBytes(keyPairVectors[0].priv_bytes[0:31]) + if err == nil { + t.Fatalf("priv.FromBytes(D): got success, expected error") + } + /* Invalid long private key */ + err = priv.FromBytes(append(keyPairVectors[0].priv_bytes, []byte{0x00}...)) + if err == nil { + t.Fatalf("priv.FromBytes(D): got success, expected error") + } + + t.Log("success PrivateKey FromBytes() on invaild vectors") +} + +func TestPrivateKeyToBytes(t *testing.T) { + var priv PrivateKey + + /* Check valid vectors */ + for i := 0; i < len(keyPairVectors); i++ { + priv.D = keyPairVectors[i].D + b := priv.ToBytes() + if bytes.Compare(keyPairVectors[i].priv_bytes, b) != 0 { + t.Fatalf("private key bytes do not match test vector in index %d", i) + } + } + t.Log("success PrivateKey ToBytes()") +} + +func TestPrivateKeyFromWIF(t *testing.T) { + var priv PrivateKey + + /* Check valid vectors */ + for i := 0; i < len(keyPairVectors); i++ { + /* Import WIF */ + err := priv.FromWIF(keyPairVectors[i].wif) + if err != nil { + t.Fatalf("priv.FromWIF(%s): got error %v, expected success", keyPairVectors[i].wif, err) + } + if priv.D.Cmp(keyPairVectors[i].D) != 0 { + t.Fatalf("private key does not match test vector on index %d", i) + } + if priv.X.Cmp(keyPairVectors[i].X) != 0 || priv.Y.Cmp(keyPairVectors[i].Y) != 0 { + t.Fatalf("public key does not match test vector on index %d", i) + } + + /* Import WIFC */ + err = priv.FromWIF(keyPairVectors[i].wifc) + if err != nil { + t.Fatalf("priv.FromWIF(%s): got error %v, expected success", keyPairVectors[i].wifc, err) + } + if priv.D.Cmp(keyPairVectors[i].D) != 0 { + t.Fatalf("private key does not match test vector on index %d", i) + } + if priv.X.Cmp(keyPairVectors[i].X) != 0 || priv.Y.Cmp(keyPairVectors[i].Y) != 0 { + t.Fatalf("public key does not match test vector on index %d", i) + } + } + t.Log("success PrivateKey FromWIF() on valid vectors") + + /* Check invalid vectors */ + for i := 0; i < len(wifInvalidVectors); i++ { + err := priv.FromWIF(wifInvalidVectors[i]) + if err == nil { + t.Fatalf("priv.FromWIF(%s): got success, expected error", wifInvalidVectors[i]) + } + t.Logf("priv.FromWIF(%s): got err %v", wifInvalidVectors[i], err) + } + t.Log("success PrivateKey FromWIF() on invalid vectors") +} + +func TestPrivateKeyToWIF(t *testing.T) { + var priv PrivateKey + + /* Check valid vectors */ + for i := 0; i < len(keyPairVectors); i++ { + /* Export WIF */ + priv.D = keyPairVectors[i].D + wif := priv.ToWIF() + if wif != keyPairVectors[i].wif { + t.Fatalf("priv.ToWIF() %s != expected %s", wif, keyPairVectors[i].wif) + } + } + t.Log("success PrivateKey ToWIF()") + + /* Check valid vectors */ + for i := 0; i < len(keyPairVectors); i++ { + /* Export WIFC */ + priv.D = keyPairVectors[i].D + wifc := priv.ToWIFC() + if wifc != keyPairVectors[i].wifc { + t.Fatalf("priv.ToWIFC() %s != expected %s", wifc, keyPairVectors[i].wifc) + } + } + t.Log("success PrivateKey ToWIFC()") +} + +/******************************************************************************/ +/* Bitcoin Public Key Import/Export */ +/******************************************************************************/ + +func TestPublicKeyToBytes(t *testing.T) { + var pub PublicKey + + /* Check valid vectors */ + for i := 0; i < len(keyPairVectors); i++ { + pub.X = keyPairVectors[i].X + pub.Y = keyPairVectors[i].Y + + bytes_compressed := pub.ToBytes() + if bytes.Compare(keyPairVectors[i].pub_bytes_compressed, bytes_compressed) != 0 { + t.Fatalf("public key compressed bytes do not match test vectors on index %d", i) + } + + bytes_uncompressed := pub.ToBytesUncompressed() + if bytes.Compare(keyPairVectors[i].pub_bytes_uncompressed, bytes_uncompressed) != 0 { + t.Fatalf("public key uncompressed bytes do not match test vectors on index %d", i) + } + } + t.Log("success PublicKey ToBytes() and ToBytesUncompressed()") +} + +func TestPublicKeyFromBytes(t *testing.T) { + var pub PublicKey + + /* Check valid vectors */ + for i := 0; i < len(keyPairVectors); i++ { + err := pub.FromBytes(keyPairVectors[i].pub_bytes_compressed) + if err != nil { + t.Fatalf("pub.FromBytes(): got error %v, expected success on index %d", err, i) + } + if pub.X.Cmp(keyPairVectors[i].X) != 0 || pub.Y.Cmp(keyPairVectors[i].Y) != 0 { + t.Fatalf("public key does not match test vectors on index %d", i) + } + + err = pub.FromBytes(keyPairVectors[i].pub_bytes_uncompressed) + if err != nil { + t.Fatalf("pub.FromBytes(): got error %v, expected success on index %d", err, i) + } + if pub.X.Cmp(keyPairVectors[i].X) != 0 || pub.Y.Cmp(keyPairVectors[i].Y) != 0 { + t.Fatalf("public key does not match test vectors on index %d", i) + } + } + t.Log("success PublicKey FromBytes() on valid vectors") + + /* Check invalid vectors */ + for i := 0; i < len(invalidPublicKeyBytesVectors); i++ { + err := pub.FromBytes(invalidPublicKeyBytesVectors[i]) + if err == nil { + t.Fatal("pub.FromBytes(): got success, expected error") + } + t.Logf("pub.FromBytes(): got error %v", err) + } + t.Log("success PublicKey FromBytes() on invalid vectors") +} + +func TestToAddress(t *testing.T) { + var pub PublicKey + + /* Check valid vectors */ + for i := 0; i < len(keyPairVectors); i++ { + pub.X = keyPairVectors[i].X + pub.Y = keyPairVectors[i].Y + + address_compressed := pub.ToAddress() + if address_compressed != keyPairVectors[i].address_compressed { + t.Fatalf("public key compressed address does not match test vectors on index %d", i) + } + + address_uncompressed := pub.ToAddressUncompressed() + if address_uncompressed != keyPairVectors[i].address_uncompressed { + t.Fatalf("public key uncompressed address does not match test vectors on index %d", i) + } + } + t.Log("success PublicKey ToAddress() and ToAddressUncompressed()") +} + +/******************************************************************************/ +/* Generating Keys */ +/******************************************************************************/ + +func TestGenerateKey(t *testing.T) { + /* Generate a keypair and check public key validity */ + priv1, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("GenerateKey(): got error %v", err) + } + if !secp256r1.IsOnCurve(priv1.PublicKey.Point) { + t.Fatalf("GenerateKey(): public key not on curve") + } + + /* Generate another keypair and check public key validity */ + priv2, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("GenerateKey(): got error %v", err) + } + if !secp256r1.IsOnCurve(priv1.PublicKey.Point) { + t.Fatalf("GenerateKey(): public key not on curve") + } + + /* Compare keypair private keys */ + if priv1.D == priv2.D { + t.Fatalf("generated duplicate private keys") + } + /* Compare keypair public keys */ + if priv1.X.Cmp(priv2.X) == 0 && priv2.Y.Cmp(priv2.Y) == 0 { + t.Fatalf("generated duplicate public keys") + } + + t.Log("success GenerateKey()") +} + +func TestSignData(t *testing.T) { + + wif := "L5hbtj4mX16TnVnQTdda4znw1nvt3DB7DVxU6sP97RDVMtufQwbT" + var priv PrivateKey + err := priv.FromWIF(wif) + + raw := []byte("NmMmdf3eSJyYtqwcFiUILzXv3f") + + privateKeyString := hex.EncodeToString(priv.ToBytes()) + signature, err := Sign(raw, privateKeyString) + if err != nil { + t.Fatalf("TestSignData(): got error %v", err) + } + log.Printf("%v", signature) + log.Printf("%v", hex.EncodeToString(priv.PublicKey.ToBytes())) + log.Printf("%+v", priv) + + another, _ := GenerateKey(rand.Reader) + + pub := another.PublicKey.ToBytes() + + hash := sha256.Sum256(raw) + + valid := Verify(pub, signature, hash[:]) + log.Printf("valid = %v", valid) + // b := hex2bytes(privateKeyString) + + // priv := new(ecdsa.PrivateKey) + // priv.PublicKey.Curve = elliptic.P256() + // priv.D = new(big.Int).SetBytes(b) + // priv.PublicKey.X, priv.PublicKey.Y = priv.PublicKey.Curve.ScalarBaseMult(b) + + // valid := Verify(&p.PublicKey, signature, hash[:]) + // log.Printf("valid = %v", valid) +} diff --git a/neoutils/btckey/elliptic.go b/neoutils/btckey/elliptic.go new file mode 100644 index 0000000..7e69722 --- /dev/null +++ b/neoutils/btckey/elliptic.go @@ -0,0 +1,292 @@ +/* btckeygenie v1.0.0 + * https://github.com/vsergeev/btckeygenie + * License: MIT + */ + +package btckey + +import ( + "encoding/hex" + "errors" + "fmt" + "math/big" +) + +/* We gotta do a lot ourselves because golang's crypto/elliptic uses curves + * with a = -3 hardcoded */ + +/* See SEC2 pg.9 http://www.secg.org/collateral/sec2_final.pdf */ + +// Point represents a point on an EllipticCurve. +type Point struct { + X *big.Int + Y *big.Int +} + +/* y**2 = x**3 + a*x + b % p */ +// EllipticCurve represents the parameters of a short Weierstrass equation elliptic curve. +type EllipticCurve struct { + A *big.Int + B *big.Int + P *big.Int + G Point + N *big.Int + H *big.Int +} + +// dump dumps the bytes of a point for debugging. +func (p *Point) dump() { + fmt.Print(p.format()) +} + +// format formats the bytes of a point for debugging. +func (p *Point) format() string { + if p.X == nil && p.Y == nil { + return "(inf,inf)" + } + return fmt.Sprintf("(%s,%s)", hex.EncodeToString(p.X.Bytes()), hex.EncodeToString(p.Y.Bytes())) +} + +/*** Modular Arithmetic ***/ + +/* NOTE: Returning a new z each time below is very space inefficient, but the + * alternate accumulator based design makes the point arithmetic functions look + * absolutely hideous. I may still change this in the future. */ + +// 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 +} + +/*** Point Arithmetic on Curve ***/ + +// IsInfinity checks if point P is infinity on EllipticCurve ec. +func (ec *EllipticCurve) IsInfinity(P Point) bool { + /* We use (nil,nil) to represent O, the point at infinity. */ + + if P.X == nil && P.Y == nil { + return true + } + + return false +} + +// IsOnCurve checks if point P is on EllipticCurve ec. +func (ec *EllipticCurve) IsOnCurve(P Point) bool { + if ec.IsInfinity(P) { + return false + } + + /* y**2 = x**3 + a*x + b % p */ + lhs := mulMod(P.Y, P.Y, ec.P) + rhs := addMod( + addMod( + expMod(P.X, big.NewInt(3), ec.P), + mulMod(ec.A, P.X, ec.P), ec.P), + ec.B, ec.P) + + if lhs.Cmp(rhs) == 0 { + return true + } + + return false +} + +// Add computes R = P + Q on EllipticCurve ec. +func (ec *EllipticCurve) Add(P, Q Point) (R Point) { + /* See rules 1-5 on SEC1 pg.7 http://www.secg.org/collateral/sec1_final.pdf */ + + if ec.IsInfinity(P) && ec.IsInfinity(Q) { + /* Rule #1 Identity */ + /* R = O + O = O */ + + R.X = nil + R.Y = nil + + } else if ec.IsInfinity(P) { + /* Rule #2 Identity */ + /* R = O + Q = Q */ + + R.X = new(big.Int).Set(Q.X) + R.Y = new(big.Int).Set(Q.Y) + + } else if ec.IsInfinity(Q) { + /* Rule #2 Identity */ + /* R = P + O = P */ + + 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, ec.P).Sign() == 0 { + /* Rule #3 Identity */ + /* R = (x,y) + (x,-y) = O */ + + R.X = nil + R.Y = nil + + } else if P.X.Cmp(Q.X) == 0 && P.Y.Cmp(Q.Y) == 0 && P.Y.Sign() != 0 { + /* Rule #5 Point doubling */ + /* R = P + P */ + + /* Lambda = (3*P.X*P.X + a) / (2*P.Y) */ + num := addMod( + mulMod(big.NewInt(3), + mulMod(P.X, P.X, ec.P), ec.P), + ec.A, ec.P) + den := invMod(mulMod(big.NewInt(2), P.Y, ec.P), ec.P) + lambda := mulMod(num, den, ec.P) + + /* R.X = lambda*lambda - 2*P.X */ + R.X = subMod( + mulMod(lambda, lambda, ec.P), + mulMod(big.NewInt(2), P.X, ec.P), + ec.P) + /* R.Y = lambda*(P.X - R.X) - P.Y */ + R.Y = subMod( + mulMod(lambda, subMod(P.X, R.X, ec.P), ec.P), + P.Y, ec.P) + + } else if P.X.Cmp(Q.X) != 0 { + /* Rule #4 Point addition */ + /* R = P + Q */ + + /* Lambda = (Q.Y - P.Y) / (Q.X - P.X) */ + num := subMod(Q.Y, P.Y, ec.P) + den := invMod(subMod(Q.X, P.X, ec.P), ec.P) + lambda := mulMod(num, den, ec.P) + + /* R.X = lambda*lambda - P.X - Q.X */ + R.X = subMod( + subMod( + mulMod(lambda, lambda, ec.P), + P.X, ec.P), + Q.X, ec.P) + + /* R.Y = lambda*(P.X - R.X) - P.Y */ + R.Y = subMod( + mulMod(lambda, + subMod(P.X, R.X, ec.P), ec.P), + P.Y, ec.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 (ec *EllipticCurve) ScalarMult(k *big.Int, P Point) (Q Point) { + /* Note: this function is not constant time, due to the branching nature of + * the underlying point Add() function. */ + + /* Montgomery Ladder Point Multiplication + * + * Implementation based on pseudocode here: + * See https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder */ + + var R0 Point + var R1 Point + + R0.X = nil + R0.Y = nil + R1.X = new(big.Int).Set(P.X) + R1.Y = new(big.Int).Set(P.Y) + + for i := ec.N.BitLen() - 1; i >= 0; i-- { + if k.Bit(i) == 0 { + R1 = ec.Add(R0, R1) + R0 = ec.Add(R0, R0) + } else { + R0 = ec.Add(R0, R1) + R1 = ec.Add(R1, R1) + } + } + + return R0 +} + +// ScalarBaseMult computes Q = k * G on EllipticCurve ec. +func (ec *EllipticCurve) ScalarBaseMult(k *big.Int) (Q Point) { + return ec.ScalarMult(k, ec.G) +} + +// Decompress decompresses coordinate x and ylsb (y's least significant bit) into a Point P on EllipticCurve ec. +func (ec *EllipticCurve) Decompress(x *big.Int, ylsb uint) (P Point, err error) { + /* y**2 = x**3 + a*x + b % p */ + rhs := addMod( + addMod( + expMod(x, big.NewInt(3), ec.P), + mulMod(ec.A, x, ec.P), + ec.P), + ec.B, ec.P) + + /* y = sqrt(rhs) % p */ + y := sqrtMod(rhs, ec.P) + + /* Use -y if opposite lsb is required */ + if y.Bit(0) != (ylsb & 0x1) { + y = subMod(big.NewInt(0), y, ec.P) + } + + P.X = x + P.Y = y + + if !ec.IsOnCurve(P) { + return P, errors.New("Compressed (x, ylsb) not on curve.") + } + + return P, nil +} diff --git a/neoutils/btckey/elliptic_test.go b/neoutils/btckey/elliptic_test.go new file mode 100644 index 0000000..b074414 --- /dev/null +++ b/neoutils/btckey/elliptic_test.go @@ -0,0 +1,225 @@ +/* btckeygenie v1.0.0 + * https://github.com/vsergeev/btckeygenie + * License: MIT + */ + +package btckey + +import ( + "math/big" + "testing" +) + +var curve EllipticCurve + +func init() { + /* See SEC2 pg.9 http://www.secg.org/collateral/sec2_final.pdf */ + /* secp256k1 elliptic curve parameters */ + curve.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16) + curve.A, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000000", 16) + curve.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16) + curve.G.X, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16) + curve.G.Y, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16) + curve.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) + curve.H, _ = new(big.Int).SetString("01", 16) +} + +func hex2int(hexstring string) (v *big.Int) { + v, _ = new(big.Int).SetString(hexstring, 16) + return v +} + +func TestOnCurve(t *testing.T) { + if !curve.IsOnCurve(curve.G) { + t.Fatal("failure G on curve") + } + + t.Log("G on curve") +} + +func TestInfinity(t *testing.T) { + O := Point{nil, nil} + + /* O not on curve */ + if curve.IsOnCurve(O) { + t.Fatal("failure O on curve") + } + + /* O is infinity */ + if !curve.IsInfinity(O) { + t.Fatal("failure O not infinity on curve") + } + + t.Log("O is not on curve and is infinity") +} + +func TestPointAdd(t *testing.T) { + X := "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352" + Y := "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6" + + P := Point{hex2int(X), hex2int(Y)} + O := Point{nil, nil} + + /* R = O + O = O */ + { + R := curve.Add(O, O) + if !curve.IsInfinity(R) { + t.Fatal("failure O + O = O") + } + t.Log("success O + O = O") + } + + /* R = P + O = P */ + { + R := curve.Add(P, O) + if R.X.Cmp(P.X) != 0 || R.Y.Cmp(P.Y) != 0 { + t.Fatal("failure P + O = P") + } + t.Log("success P + O = P") + } + + /* R = O + Q = Q */ + { + R := curve.Add(O, P) + if R.X.Cmp(P.X) != 0 || R.Y.Cmp(P.Y) != 0 { + t.Fatal("failure O + Q = Q") + } + t.Log("success O + Q = Q") + } + + /* R = (x,y) + (x,-y) = O */ + { + Q := Point{P.X, subMod(big.NewInt(0), P.Y, curve.P)} + + R := curve.Add(P, Q) + if !curve.IsInfinity(R) { + t.Fatal("failure (x,y) + (x,-y) = O") + } + t.Log("success (x,y) + (x,-y) = O") + } + + /* R = P + P */ + { + PP := Point{hex2int("5dbcd5dfea550eb4fd3b5333f533f086bb5267c776e2a1a9d8e84c16a6743d82"), hex2int("8dde3986b6cbe395da64b6e95fb81f8af73f6e0cf1100555005bb4ba2a6a4a07")} + + R := curve.Add(P, P) + if R.X.Cmp(PP.X) != 0 || R.Y.Cmp(PP.Y) != 0 { + t.Fatal("failure P + P") + } + t.Log("success P + P") + } + + Q := Point{hex2int("a83b8de893467d3a88d959c0eb4032d9ce3bf80f175d4d9e75892a3ebb8ab7e5"), hex2int("370f723328c24b7a97fe34063ba68f253fb08f8645d7c8b9a4ff98e3c29e7f0d")} + PQ := Point{hex2int("fe7d540002e4355eb0ec36c217b4735495de7bd8634055ded3683b0e9da70ef1"), hex2int("fc033c1d74cb34e087a3495e505c0fc0e9e3e8297994878d89d882254ce8a9ef")} + + /* R = P + Q */ + { + R := curve.Add(P, Q) + if R.X.Cmp(PQ.X) != 0 || R.Y.Cmp(PQ.Y) != 0 { + t.Fatal("failure P + Q") + } + t.Log("success P + Q") + } + + /* R = Q + P */ + { + R := curve.Add(Q, P) + if R.X.Cmp(PQ.X) != 0 || R.Y.Cmp(PQ.Y) != 0 { + t.Fatal("failure Q + P") + } + t.Log("success Q + P") + } +} + +func TestPointScalarMult(t *testing.T) { + X := "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352" + Y := "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6" + P := Point{hex2int(X), hex2int(Y)} + + /* Q = k*P */ + { + T := Point{hex2int("87d592bfdd24adb52147fea343db93e10d0585bc66d91e365c359973c0dc7067"), hex2int("a374e206cb7c8cd1074bdf9bf6ddea135f983aaa6475c9ab3bb4c38a0046541b")} + Q := curve.ScalarMult(hex2int("14eb373700c3836404acd0820d9fa8dfa098d26177ca6e18b1c7f70c6af8fc18"), P) + if Q.X.Cmp(T.X) != 0 || Q.Y.Cmp(T.Y) != 0 { + t.Fatal("failure k*P") + } + t.Log("success k*P") + } + + /* Q = n*G = O */ + { + Q := curve.ScalarMult(curve.N, curve.G) + if !curve.IsInfinity(Q) { + t.Fatal("failure n*G = O") + } + t.Log("success n*G = O") + } +} + +func TestPointScalarBaseMult(t *testing.T) { + /* Sample Private Key */ + D := "18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725" + /* Sample Corresponding Public Key */ + X := "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352" + Y := "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6" + + P := Point{hex2int(X), hex2int(Y)} + + /* Q = d*G = P */ + Q := curve.ScalarBaseMult(hex2int(D)) + if P.X.Cmp(Q.X) != 0 || P.Y.Cmp(Q.Y) != 0 { + t.Fatal("failure Q = d*G") + } + t.Log("success Q = d*G") + + /* Q on curve */ + if !curve.IsOnCurve(Q) { + t.Fatal("failure Q on curve") + } + t.Log("success Q on curve") + + /* R = 0*G = O */ + R := curve.ScalarBaseMult(big.NewInt(0)) + if !curve.IsInfinity(R) { + t.Fatal("failure 0*G = O") + } + t.Log("success 0*G = O") +} + +func TestPointDecompress(t *testing.T) { + /* Valid points */ + var validDecompressVectors = []Point{ + {hex2int("50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"), hex2int("2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6")}, + {hex2int("a83b8de893467d3a88d959c0eb4032d9ce3bf80f175d4d9e75892a3ebb8ab7e5"), hex2int("370f723328c24b7a97fe34063ba68f253fb08f8645d7c8b9a4ff98e3c29e7f0d")}, + {hex2int("f680556678e25084a82fa39e1b1dfd0944f7e69fddaa4e03ce934bd6b291dca0"), hex2int("52c10b721d34447e173721fb0151c68de1106badb089fb661523b8302a9097f5")}, + {hex2int("241febb8e23cbd77d664a18f66ad6240aaec6ecdc813b088d5b901b2e285131f"), hex2int("513378d9ff94f8d3d6c420bd13981df8cd50fd0fbd0cb5afabb3e66f2750026d")}, + } + + for i := 0; i < len(validDecompressVectors); i++ { + P, err := curve.Decompress(validDecompressVectors[i].X, validDecompressVectors[i].Y.Bit(0)) + if err != nil { + t.Fatalf("failure decompress P, got error %v on index %d", err, i) + } + if P.X.Cmp(validDecompressVectors[i].X) != 0 || P.Y.Cmp(validDecompressVectors[i].Y) != 0 { + t.Fatalf("failure decompress P, got mismatch on index %d", i) + } + } + t.Log("success Decompress() on valid vectors") + + /* Invalid points */ + var invalidDecompressVectors = []struct { + X *big.Int + YLsb uint + }{ + {hex2int("c8e337cee51ae9af3c0ef923705a0cb1b76f7e8463b3d3060a1c8d795f9630fd"), 0}, + {hex2int("c8e337cee51ae9af3c0ef923705a0cb1b76f7e8463b3d3060a1c8d795f9630fd"), 1}, + } + + for i := 0; i < len(invalidDecompressVectors); i++ { + _, err := curve.Decompress(invalidDecompressVectors[i].X, invalidDecompressVectors[i].YLsb) + if err == nil { + t.Fatalf("failure decompress invalid P, got decompressed point on index %d", i) + } + } + t.Log("success Decompress() on invalid vectors") +} diff --git a/neoutils/encryption.go b/neoutils/encryption.go new file mode 100644 index 0000000..b1be86d --- /dev/null +++ b/neoutils/encryption.go @@ -0,0 +1,53 @@ +package neoutils + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "fmt" + "io" +) + +// Encrypt string to base64 format using AES +func Encrypt(key []byte, text string) string { + plaintext := []byte(text) + block, err := aes.NewCipher(key) + if err != nil { + return "" + } + cipherText := make([]byte, aes.BlockSize+len(plaintext)) + iv := cipherText[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return "" + } + + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(cipherText[aes.BlockSize:], plaintext) + + // Convert to base64 + return base64.URLEncoding.EncodeToString(cipherText) +} + +// Decrypt AES encrypted string in base64 format to decrypted string +func Decrypt(key []byte, encryptedText string) string { + cipherText, _ := base64.URLEncoding.DecodeString(encryptedText) + + block, err := aes.NewCipher(key) + if err != nil { + return "" + } + + if len(cipherText) < aes.BlockSize { + return "" + } + iv := cipherText[:aes.BlockSize] + cipherText = cipherText[aes.BlockSize:] + + stream := cipher.NewCFBDecrypter(block, iv) + + // XORKeyStream can work in-place if the two arguments are the same. + stream.XORKeyStream(cipherText, cipherText) + + return fmt.Sprintf("%s", cipherText) +} diff --git a/neoutils/encryption_test.go b/neoutils/encryption_test.go new file mode 100644 index 0000000..1b3ebdc --- /dev/null +++ b/neoutils/encryption_test.go @@ -0,0 +1,92 @@ +package neoutils + +import ( + "log" + "testing" +) + +func TestDecrypt(t *testing.T) { + encryptedString := "nkGfKuzVOBCxMpKZJ3ejjSYlTTP8-XnSKfoXMndPU5w=" + wif := "KxDgvEKzgSBPPfuVfw67oPQBSjidEiqTHURKSDL1R7yGaGYAeYnr" + wallet, _ := GenerateFromWIF(wif) + + decrypted := Decrypt(wallet.PrivateKey, encryptedString) + log.Printf("%v", decrypted) +} + +func TestEncryptWithAddress(t *testing.T) { + alice, err := NewWallet() + if err != nil { + log.Printf("%+v", err) + t.Fail() + return + } + + message := "Hello bob" + + encryptedText := Encrypt([]byte(alice.PublicKey), message) + log.Printf("encrypted = %v", encryptedText) +} + +func TestEncryptMessage(t *testing.T) { + alice, err := NewWallet() + if err != nil { + log.Printf("%+v", err) + t.Fail() + return + } + + bob, err := NewWallet() + if err != nil { + log.Printf("%+v", err) + t.Fail() + return + } + + randomPerson, err := NewWallet() + if err != nil { + log.Printf("%+v", err) + t.Fail() + return + } + + log.Printf(` + alice's adress = %+v\n + bob's adress = %+v\n + alice's public key = %+v\n + bob's public key = %+v\n + `, + alice.Address, + bob.Address, + bytesToHex(alice.PublicKey), + bytesToHex(bob.PublicKey), + ) + + alicesSharedSecret := alice.ComputeSharedSecret(bob.PublicKey) + bobsSharedSecret := bob.ComputeSharedSecret(alice.PublicKey) + + randomPersonSharedSecretWithAlice := randomPerson.ComputeSharedSecret(alice.PublicKey) + randomPersonSharedSecretWithBob := randomPerson.ComputeSharedSecret(bob.PublicKey) + + hexSlice := bytesToHex(alicesSharedSecret) + hexBob := bytesToHex(bobsSharedSecret) + hexRandomPerson := bytesToHex(randomPersonSharedSecretWithAlice) + hexRandomPersonWithBob := bytesToHex(randomPersonSharedSecretWithBob) + log.Printf("alice's shared secret (bob's public key) = %+v\n", hexSlice) + log.Printf("bob's shared secret (alice's public key) = %+v\n", hexBob) + log.Printf("random person's shared secret (alice's public key) = %+v\n", hexRandomPerson) + log.Printf("random person's shared secret (bob's public key) = %+v\n", hexRandomPersonWithBob) + + message := "Hello bob" + + encryptedText := Encrypt(alicesSharedSecret, message) + log.Printf("alice sent %v to bob\n", encryptedText) + log.Printf("bob sent %v to alice\n", Encrypt(bobsSharedSecret, "Hey there Alice")) + + decryptedText := Decrypt(bobsSharedSecret, encryptedText) + + log.Printf(`bob uses his shared secret to read alice's message. "%v" \n`, decryptedText) + log.Printf(`random person tries to use his shared secret between he and alice to read bob message sent by alice "%v" \n`, Decrypt(randomPersonSharedSecretWithAlice, encryptedText)) + + log.Printf(`random person tries to use his shared secret between he and bob to read bob message sent by alice "%v" \n`, Decrypt(randomPersonSharedSecretWithBob, encryptedText)) +} diff --git a/neoutils/neowallet.go b/neoutils/neowallet.go new file mode 100644 index 0000000..1a54ae1 --- /dev/null +++ b/neoutils/neowallet.go @@ -0,0 +1,138 @@ +package neoutils + +import ( + "crypto/elliptic" + "crypto/rand" + "encoding/hex" + "errors" + + "github.com/o3labs/neo-utils/neoutils/btckey" + + "github.com/o3labs/neo-utils/neoutils/sss" +) + +type Wallet struct { + PublicKey []byte + PrivateKey []byte + Address string + WIF string + HashedSignature []byte +} + +func hex2bytes(hexstring string) (b []byte) { + b, _ = hex.DecodeString(hexstring) + return b +} + +func bytesToHex(b []byte) string { + return hex.EncodeToString(b) +} + +// Generate a wallet from a private key +func GeneratePublicKeyFromPrivateKey(privateKey string) (*Wallet, error) { + pb := hex2bytes(privateKey) + var priv btckey.PrivateKey + err := priv.FromBytes(pb) + if err != nil { + return &Wallet{}, err + } + wallet := &Wallet{ + PublicKey: priv.PublicKey.ToBytes(), + PrivateKey: priv.ToBytes(), + Address: priv.ToNeoAddress(), + WIF: priv.ToWIFC(), + HashedSignature: priv.ToNeoSignature(), + } + return wallet, nil +} + +// Generate a wallet from a WIF +func GenerateFromWIF(wif string) (*Wallet, error) { + var priv btckey.PrivateKey + err := priv.FromWIF(wif) + if err != nil { + return &Wallet{}, err + } + + wallet := &Wallet{ + PublicKey: priv.PublicKey.ToBytes(), + PrivateKey: priv.ToBytes(), + Address: priv.ToNeoAddress(), + WIF: priv.ToWIFC(), + HashedSignature: priv.ToNeoSignature(), + } + return wallet, nil +} + +// Create a new wallet. +func NewWallet() (*Wallet, error) { + + priv, err := btckey.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + + wallet := &Wallet{ + PublicKey: priv.PublicKey.ToBytes(), + PrivateKey: priv.ToBytes(), + Address: priv.ToNeoAddress(), + WIF: priv.ToWIFC(), + HashedSignature: priv.ToNeoSignature(), + } + return wallet, nil +} + +//Shared Secret with 2 parts. +type SharedSecret struct { + First []byte + Second []byte +} + +// Generate Shamir shared secret to SharedSecret struct. +func GenerateShamirSharedSecret(secret string) (*SharedSecret, error) { + n := byte(2) // create 2 shares + k := byte(2) // require 2 of them to combine + + shares, err := sss.Split(n, k, []byte(secret)) // split into 30 shares + if err != nil { + return nil, err + } + return &SharedSecret{ + First: shares[byte(1)], + Second: shares[byte(2)], + }, nil +} + +// Recover the secret from shared secrets. +func RecoverFromSharedSecret(first []byte, second []byte) (string, error) { + s := SharedSecret{ + First: []byte(first), + Second: []byte(second), + } + if len(s.First) != len(s.Second) { + return "", errors.New("Invalid shared secret") + } + k := byte(2) + // select a random subset of the total shares + subset := make(map[byte][]byte, k) + subset[byte(1)] = s.First + subset[byte(2)] = s.Second + + // combine two shares and recover the secret + recovered := string(sss.Combine(subset)) + return recovered, nil +} + +//Compute shared secret using ECDH +func (w *Wallet) ComputeSharedSecret(publicKey []byte) []byte { + pKey := btckey.PublicKey{} + pKey.FromBytes(publicKey) + curve := elliptic.P256() + x, _ := curve.ScalarMult(pKey.X, pKey.Y, w.PrivateKey) + return x.Bytes() +} + +// Sign data using ECDSA with a private key +func Sign(data []byte, key string) ([]byte, error) { + return btckey.Sign(data, key) +} diff --git a/neoutils/neowallet_test.go b/neoutils/neowallet_test.go new file mode 100644 index 0000000..3b9b40c --- /dev/null +++ b/neoutils/neowallet_test.go @@ -0,0 +1,84 @@ +package neoutils + +import ( + "encoding/hex" + "fmt" + "log" + "testing" + + "github.com/o3labs/neo-utils/neoutils/sss" +) + +func TestGenKey(t *testing.T) { + privateKey := "0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D" + wallet, _ := GeneratePublicKeyFromPrivateKey(privateKey) + + log.Printf("%+v", wallet) +} + +func TestGenFromWIF(t *testing.T) { + wif := "KzULqzStT2tseGnqogXnTLG5NCT1YXa3F9Wp1Kdv9xMxFhvV6H2A" + wallet, err := GenerateFromWIF(wif) + if err != nil { + log.Printf("%+v", err) + t.Fail() + } + + log.Printf("private key %+v", hex.EncodeToString(wallet.PrivateKey)) + log.Printf("public key %+v (%d)", hex.EncodeToString(wallet.PublicKey), len(wallet.PublicKey)) + log.Printf("wallet%+v", wallet) + log.Printf("wallet address %+v %d", wallet.Address, len(wallet.Address)) +} + +func TestSSS(t *testing.T) { + secret := "well hello there!" // our secret + n := byte(2) // create 30 shares + k := byte(2) // require 2 of them to combine + + shares, err := sss.Split(n, k, []byte(secret)) // split into 30 shares + if err != nil { + fmt.Println(err) + return + } + + // select a random subset of the total shares + subset := make(map[byte][]byte, k) + for x, y := range shares { // just iterate since maps are randomized + subset[x] = y + if len(subset) == int(k) { + break + } + } + + // combine two shares and recover the secret + recovered := string(sss.Combine(subset)) + if secret != recovered { + t.Fail() + return + } + fmt.Println(recovered) +} + +func TestGenerateSSS(t *testing.T) { + sharedSecret, err := GenerateShamirSharedSecret("0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D") + if err != nil { + t.Fail() + return + } + recovered, err := RecoverFromSharedSecret(sharedSecret.First, sharedSecret.Second) + + fmt.Printf("%v\n%v\n%v", bytesToHex(sharedSecret.First), bytesToHex(sharedSecret.Second), recovered) +} + +func TestRecoverFromString(t *testing.T) { + + first := hex2bytes("b636f0821f65399cd0a2334ab6b229bfb8dce9fe569c795d14ec6177c7eba62be80668fdd6fe743286e6aec5b856ca9a15d7c9b0c82d06fa80f51a920d32ff90") + second := hex2bytes("27a9ad57f40fb176f305a3cdb429043c31f39921fae93de578059b2b5602040504c998bb7bb22eae441d815e37f5dce9e5fdc233dd03c3bc503d6d69d9a7b6f7") + + recovered, err := RecoverFromSharedSecret(first, second) + if err != nil { + t.Fail() + return + } + fmt.Printf("%v", recovered) +} diff --git a/neoutils/network.go b/neoutils/network.go new file mode 100644 index 0000000..071637e --- /dev/null +++ b/neoutils/network.go @@ -0,0 +1,154 @@ +package neoutils + +import ( + "encoding/json" + "net" + "net/http" + "sort" + "strings" + "time" +) + +type customTransport struct { + rtp http.RoundTripper + dialer *net.Dialer + connStart time.Time + connEnd time.Time + reqStart time.Time + reqEnd time.Time +} + +func newTransport() *customTransport { + + tr := &customTransport{ + dialer: &net.Dialer{ + Timeout: 5 * time.Second, //keep timeout low + KeepAlive: 5 * time.Second, + }, + } + tr.rtp = &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: tr.dial, + TLSHandshakeTimeout: 10 * time.Second, + ResponseHeaderTimeout: 1 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + return tr +} + +func (tr *customTransport) RoundTrip(r *http.Request) (*http.Response, error) { + tr.reqStart = time.Now() + resp, err := tr.rtp.RoundTrip(r) + tr.reqEnd = time.Now() + return resp, err +} + +func (tr *customTransport) dial(network, addr string) (net.Conn, error) { + tr.connStart = time.Now() + cn, err := tr.dialer.Dial(network, addr) + tr.connEnd = time.Now() + return cn, err +} + +func (tr *customTransport) ReqDuration() time.Duration { + return tr.Duration() - tr.ConnDuration() +} + +func (tr *customTransport) ConnDuration() time.Duration { + return tr.connEnd.Sub(tr.connStart) +} + +func (tr *customTransport) Duration() time.Duration { + return tr.reqEnd.Sub(tr.reqStart) +} + +type BlockCountResponse struct { + Jsonrpc string `json:"jsonrpc"` + ID int `json:"id"` + Result int `json:"result"` + ResponseTime int64 `json:"-"` +} + +func fetchSeedNode(url string) *BlockCountResponse { + //instead of using default http client. we use a transport one here. + //because we need to mearure the time. Request, Response and total duration. + //to select the best node among the nodes that has the highest blockcount by picking the least latency node. + transport := newTransport() + client := http.Client{Transport: transport} + payload := strings.NewReader(" {\"jsonrpc\": \"2.0\", \"method\": \"getblockcount\", \"params\": [], \"id\": 3}") + res, err := client.Post(url, "application/json", payload) + if err != nil || res == nil { + return nil + } + defer res.Body.Close() + blockResponse := BlockCountResponse{} + err = json.NewDecoder(res.Body).Decode(&blockResponse) + if err != nil { + return nil + } + blockResponse.ResponseTime = transport.ReqDuration().Nanoseconds() + return &blockResponse +} + +type FetchSeedRequest struct { + Response *BlockCountResponse + URL string +} + +type SeedNodeResponse struct { + URL string + BlockCount int + ResponseTime int64 //milliseconds +} + +type NodeList struct { + URL []string +} + +//go mobile bind does not support slice parameters...yet +//https://github.com/golang/go/issues/12113 + +func SelectBestSeedNode(commaSeparatedURLs string) *SeedNodeResponse { + urls := strings.Split(commaSeparatedURLs, ",") + ch := make(chan *FetchSeedRequest, len(urls)) + fetchedList := []string{} + listHighestNodes := []SeedNodeResponse{} + for _, url := range urls { + go func(url string) { + res := fetchSeedNode(url) + ch <- &FetchSeedRequest{res, url} + }(url) + } + + for { + select { + case request := <-ch: + if request.Response != nil { + listHighestNodes = append(listHighestNodes, SeedNodeResponse{ + URL: request.URL, + BlockCount: request.Response.Result, + ResponseTime: request.Response.ResponseTime / int64(time.Millisecond), + }) + } + + fetchedList = append(fetchedList, request.URL) + if len(fetchedList) == len(urls) { + + if len(listHighestNodes) == 0 { + return nil + } + //using sort.SliceStable to sort min response time first + sort.SliceStable(listHighestNodes, func(i, j int) bool { + return listHighestNodes[i].ResponseTime < listHighestNodes[j].ResponseTime + }) + //using sort.SliceStable to sort block count and preserve the sorted position + sort.SliceStable(listHighestNodes, func(i, j int) bool { + return listHighestNodes[i].BlockCount > listHighestNodes[j].BlockCount + }) + + return &listHighestNodes[0] + } + } + } + return nil +} diff --git a/neoutils/network_test.go b/neoutils/network_test.go new file mode 100644 index 0000000..f1f2af1 --- /dev/null +++ b/neoutils/network_test.go @@ -0,0 +1,42 @@ +package neoutils + +import ( + "log" + "strings" + "testing" +) + +func TestFetchBlockCount(t *testing.T) { + url := "http://seed2.neo.org:10332/" + res := FetchSeedNode(url) + log.Printf("%v", res) +} + +func TestFetchDownNodeBlockCount(t *testing.T) { + url := "http://seed1.cityofzion.io:8080" + res := FetchSeedNode(url) + log.Printf("%v", res) +} + +func TestBestNode(t *testing.T) { + urls := []string{ + "http://seed1.neo.org:10332", + "http://seed2.neo.org:10332", + "http://seed3.neo.org:10332", + "http://seed4.neo.org:10332", + "http://seed5.neo.org:10332", + "http://seed1.cityofzion.io:8080", + "http://seed2.cityofzion.io:8080", + "http://seed3.cityofzion.io:8080", + "http://seed4.cityofzion.io:8080", + "http://seed5.cityofzion.io:8080", + "http://node1.o3.network:10332", + "http://node2.o3.network:10332", + } + commaSeparated := strings.Join(urls, ",") + best := SelectBestSeedNode(commaSeparated) + if best != nil { + log.Printf("best node %+v %v %vms", best.URL, best.BlockCount, best.ResponseTime) + } + +} diff --git a/neoutils/sss/LICENSE b/neoutils/sss/LICENSE new file mode 100644 index 0000000..f9835c2 --- /dev/null +++ b/neoutils/sss/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Coda Hale + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/neoutils/sss/README.md b/neoutils/sss/README.md new file mode 100644 index 0000000..f659012 --- /dev/null +++ b/neoutils/sss/README.md @@ -0,0 +1,11 @@ +# sss (Shamir's Secret Sharing) + +[![Build Status](https://travis-ci.org/codahale/sss.png?branch=master)](https://travis-ci.org/codahale/sss) + +A pure Go implementation of +[Shamir's Secret Sharing algorithm](http://en.wikipedia.org/wiki/Shamir's_Secret_Sharing) +over GF(2^8). + +Inspired by @hbs's [Python implementation](https://github.com/hbs/PySSSS). + +For documentation, check [godoc](http://godoc.org/github.com/codahale/sss). diff --git a/neoutils/sss/gf256.go b/neoutils/sss/gf256.go new file mode 100644 index 0000000..bff81ae --- /dev/null +++ b/neoutils/sss/gf256.go @@ -0,0 +1,81 @@ +package sss + +func mul(e, a byte) byte { + if e == 0 || a == 0 { + return 0 + } + return exp[(int(log[e])+int(log[a]))%255] +} + +func div(e, a byte) byte { + if a == 0 { + panic("div by zero") + } + + if e == 0 { + return 0 + } + + p := (int(log[e]) - int(log[a])) % 255 + if p < 0 { + p += 255 + } + + return exp[p] +} + +const ( + fieldSize = 256 // 2^8 +) + +var ( + // 0x11b prime polynomial and 0x03 as generator + exp = [fieldSize]byte{ + 0x01, 0x03, 0x05, 0x0f, 0x11, 0x33, 0x55, 0xff, 0x1a, 0x2e, 0x72, 0x96, + 0xa1, 0xf8, 0x13, 0x35, 0x5f, 0xe1, 0x38, 0x48, 0xd8, 0x73, 0x95, 0xa4, + 0xf7, 0x02, 0x06, 0x0a, 0x1e, 0x22, 0x66, 0xaa, 0xe5, 0x34, 0x5c, 0xe4, + 0x37, 0x59, 0xeb, 0x26, 0x6a, 0xbe, 0xd9, 0x70, 0x90, 0xab, 0xe6, 0x31, + 0x53, 0xf5, 0x04, 0x0c, 0x14, 0x3c, 0x44, 0xcc, 0x4f, 0xd1, 0x68, 0xb8, + 0xd3, 0x6e, 0xb2, 0xcd, 0x4c, 0xd4, 0x67, 0xa9, 0xe0, 0x3b, 0x4d, 0xd7, + 0x62, 0xa6, 0xf1, 0x08, 0x18, 0x28, 0x78, 0x88, 0x83, 0x9e, 0xb9, 0xd0, + 0x6b, 0xbd, 0xdc, 0x7f, 0x81, 0x98, 0xb3, 0xce, 0x49, 0xdb, 0x76, 0x9a, + 0xb5, 0xc4, 0x57, 0xf9, 0x10, 0x30, 0x50, 0xf0, 0x0b, 0x1d, 0x27, 0x69, + 0xbb, 0xd6, 0x61, 0xa3, 0xfe, 0x19, 0x2b, 0x7d, 0x87, 0x92, 0xad, 0xec, + 0x2f, 0x71, 0x93, 0xae, 0xe9, 0x20, 0x60, 0xa0, 0xfb, 0x16, 0x3a, 0x4e, + 0xd2, 0x6d, 0xb7, 0xc2, 0x5d, 0xe7, 0x32, 0x56, 0xfa, 0x15, 0x3f, 0x41, + 0xc3, 0x5e, 0xe2, 0x3d, 0x47, 0xc9, 0x40, 0xc0, 0x5b, 0xed, 0x2c, 0x74, + 0x9c, 0xbf, 0xda, 0x75, 0x9f, 0xba, 0xd5, 0x64, 0xac, 0xef, 0x2a, 0x7e, + 0x82, 0x9d, 0xbc, 0xdf, 0x7a, 0x8e, 0x89, 0x80, 0x9b, 0xb6, 0xc1, 0x58, + 0xe8, 0x23, 0x65, 0xaf, 0xea, 0x25, 0x6f, 0xb1, 0xc8, 0x43, 0xc5, 0x54, + 0xfc, 0x1f, 0x21, 0x63, 0xa5, 0xf4, 0x07, 0x09, 0x1b, 0x2d, 0x77, 0x99, + 0xb0, 0xcb, 0x46, 0xca, 0x45, 0xcf, 0x4a, 0xde, 0x79, 0x8b, 0x86, 0x91, + 0xa8, 0xe3, 0x3e, 0x42, 0xc6, 0x51, 0xf3, 0x0e, 0x12, 0x36, 0x5a, 0xee, + 0x29, 0x7b, 0x8d, 0x8c, 0x8f, 0x8a, 0x85, 0x94, 0xa7, 0xf2, 0x0d, 0x17, + 0x39, 0x4b, 0xdd, 0x7c, 0x84, 0x97, 0xa2, 0xfd, 0x1c, 0x24, 0x6c, 0xb4, + 0xc7, 0x52, 0xf6, 0x01, + } + log = [fieldSize]byte{ + 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1a, 0xc6, 0x4b, 0xc7, 0x1b, 0x68, + 0x33, 0xee, 0xdf, 0x03, 0x64, 0x04, 0xe0, 0x0e, 0x34, 0x8d, 0x81, 0xef, + 0x4c, 0x71, 0x08, 0xc8, 0xf8, 0x69, 0x1c, 0xc1, 0x7d, 0xc2, 0x1d, 0xb5, + 0xf9, 0xb9, 0x27, 0x6a, 0x4d, 0xe4, 0xa6, 0x72, 0x9a, 0xc9, 0x09, 0x78, + 0x65, 0x2f, 0x8a, 0x05, 0x21, 0x0f, 0xe1, 0x24, 0x12, 0xf0, 0x82, 0x45, + 0x35, 0x93, 0xda, 0x8e, 0x96, 0x8f, 0xdb, 0xbd, 0x36, 0xd0, 0xce, 0x94, + 0x13, 0x5c, 0xd2, 0xf1, 0x40, 0x46, 0x83, 0x38, 0x66, 0xdd, 0xfd, 0x30, + 0xbf, 0x06, 0x8b, 0x62, 0xb3, 0x25, 0xe2, 0x98, 0x22, 0x88, 0x91, 0x10, + 0x7e, 0x6e, 0x48, 0xc3, 0xa3, 0xb6, 0x1e, 0x42, 0x3a, 0x6b, 0x28, 0x54, + 0xfa, 0x85, 0x3d, 0xba, 0x2b, 0x79, 0x0a, 0x15, 0x9b, 0x9f, 0x5e, 0xca, + 0x4e, 0xd4, 0xac, 0xe5, 0xf3, 0x73, 0xa7, 0x57, 0xaf, 0x58, 0xa8, 0x50, + 0xf4, 0xea, 0xd6, 0x74, 0x4f, 0xae, 0xe9, 0xd5, 0xe7, 0xe6, 0xad, 0xe8, + 0x2c, 0xd7, 0x75, 0x7a, 0xeb, 0x16, 0x0b, 0xf5, 0x59, 0xcb, 0x5f, 0xb0, + 0x9c, 0xa9, 0x51, 0xa0, 0x7f, 0x0c, 0xf6, 0x6f, 0x17, 0xc4, 0x49, 0xec, + 0xd8, 0x43, 0x1f, 0x2d, 0xa4, 0x76, 0x7b, 0xb7, 0xcc, 0xbb, 0x3e, 0x5a, + 0xfb, 0x60, 0xb1, 0x86, 0x3b, 0x52, 0xa1, 0x6c, 0xaa, 0x55, 0x29, 0x9d, + 0x97, 0xb2, 0x87, 0x90, 0x61, 0xbe, 0xdc, 0xfc, 0xbc, 0x95, 0xcf, 0xcd, + 0x37, 0x3f, 0x5b, 0xd1, 0x53, 0x39, 0x84, 0x3c, 0x41, 0xa2, 0x6d, 0x47, + 0x14, 0x2a, 0x9e, 0x5d, 0x56, 0xf2, 0xd3, 0xab, 0x44, 0x11, 0x92, 0xd9, + 0x23, 0x20, 0x2e, 0x89, 0xb4, 0x7c, 0xb8, 0x26, 0x77, 0x99, 0xe3, 0xa5, + 0x67, 0x4a, 0xed, 0xde, 0xc5, 0x31, 0xfe, 0x18, 0x0d, 0x63, 0x8c, 0x80, + 0xc0, 0xf7, 0x70, 0x07, + } +) diff --git a/neoutils/sss/gf256_test.go b/neoutils/sss/gf256_test.go new file mode 100644 index 0000000..fe4f2ea --- /dev/null +++ b/neoutils/sss/gf256_test.go @@ -0,0 +1,35 @@ +package sss + +import ( + "testing" +) + +func TestMul(t *testing.T) { + if v, want := mul(90, 21), byte(254); v != want { + t.Errorf("Was %v, but expected %v", v, want) + } +} + +func TestDiv(t *testing.T) { + if v, want := div(90, 21), byte(189); v != want { + t.Errorf("Was %v, but expected %v", v, want) + } +} + +func TestDivZero(t *testing.T) { + if v, want := div(0, 2), byte(0); v != want { + t.Errorf("Was %v, but expected %v", v, want) + } +} + +func TestDivByZero(t *testing.T) { + defer func() { + m := recover() + if m != "div by zero" { + t.Error(m) + } + }() + + div(2, 0) + t.Error("Shouldn't have been able to divide those") +} diff --git a/neoutils/sss/polynomial.go b/neoutils/sss/polynomial.go new file mode 100644 index 0000000..b8b183a --- /dev/null +++ b/neoutils/sss/polynomial.go @@ -0,0 +1,67 @@ +package sss + +import "io" + +// the degree of the polynomial +func degree(p []byte) int { + return len(p) - 1 +} + +// evaluate the polynomial at the given point +func eval(p []byte, x byte) (result byte) { + // Horner's scheme + for i := 1; i <= len(p); i++ { + result = mul(result, x) ^ p[len(p)-i] + } + return +} + +// generates a random n-degree polynomial w/ a given x-intercept +func generate(degree byte, x byte, rand io.Reader) ([]byte, error) { + result := make([]byte, degree+1) + result[0] = x + + buf := make([]byte, degree-1) + if _, err := io.ReadFull(rand, buf); err != nil { + return nil, err + } + + for i := byte(1); i < degree; i++ { + result[i] = buf[i-1] + } + + // the Nth term can't be zero, or else it's a (N-1) degree polynomial + for { + buf = make([]byte, 1) + if _, err := io.ReadFull(rand, buf); err != nil { + return nil, err + } + + if buf[0] != 0 { + result[degree] = buf[0] + return result, nil + } + } +} + +// an input/output pair +type pair struct { + x, y byte +} + +// Lagrange interpolation +func interpolate(points []pair, x byte) (value byte) { + for i, a := range points { + weight := byte(1) + for j, b := range points { + if i != j { + top := x ^ b.x + bottom := a.x ^ b.x + factor := div(top, bottom) + weight = mul(weight, factor) + } + } + value = value ^ mul(weight, a.y) + } + return +} diff --git a/neoutils/sss/polynomial_test.go b/neoutils/sss/polynomial_test.go new file mode 100644 index 0000000..2783b6a --- /dev/null +++ b/neoutils/sss/polynomial_test.go @@ -0,0 +1,89 @@ +package sss + +import ( + "bytes" + "testing" +) + +var ( + p = []byte{1, 0, 2, 3} + p2 = []byte{70, 32, 6} +) + +func TestDegree(t *testing.T) { + if v, want := degree(p), 3; v != want { + t.Errorf("Was %v, but expected %v", v, want) + } +} + +func TestEval(t *testing.T) { + if v, want := eval(p, 2), byte(17); v != want { + t.Errorf("Was %v, but expected %v", v, want) + } +} + +func TestGenerate(t *testing.T) { + b := []byte{1, 2, 3} + + expected := []byte{10, 1, 2, 3} + actual, err := generate(3, 10, bytes.NewReader(b)) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(actual, expected) { + t.Errorf("Was %v, but expected %v", actual, expected) + } +} + +func TestGenerateEOF(t *testing.T) { + b := []byte{1} + + p, err := generate(3, 10, bytes.NewReader(b)) + if p != nil { + t.Errorf("Was %v, but expected an error", p) + } + + if err == nil { + t.Error("No error returned") + } +} + +func TestGeneratePolyEOFFullSize(t *testing.T) { + b := []byte{1, 2, 0, 0, 0, 0} + + p, err := generate(3, 10, bytes.NewReader(b)) + if p != nil { + t.Errorf("Was %v, but xpected an error", p) + } + + if err == nil { + t.Error("No error returned") + } +} + +func TestGenerateFullSize(t *testing.T) { + b := []byte{1, 2, 0, 4} + + expected := []byte{10, 1, 2, 4} + actual, err := generate(3, 10, bytes.NewReader(b)) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(actual, expected) { + t.Errorf("Was %v but expected %v", actual, expected) + } +} + +func TestInterpolate(t *testing.T) { + in := []pair{ + pair{x: 1, y: 1}, + pair{x: 2, y: 2}, + pair{x: 3, y: 3}, + } + + if v, want := interpolate(in, 0), byte(0); v != want { + t.Errorf("Was %v, but expected %v", v, want) + } +} diff --git a/neoutils/sss/sss.go b/neoutils/sss/sss.go new file mode 100644 index 0000000..3961a59 --- /dev/null +++ b/neoutils/sss/sss.go @@ -0,0 +1,102 @@ +// Package sss implements Shamir's Secret Sharing algorithm over GF(2^8). +// +// Shamir's Secret Sharing algorithm allows you to securely share a secret with +// N people, allowing the recovery of that secret if K of those people combine +// their shares. +// +// It begins by encoding a secret as a number (e.g., 42), and generating N +// random polynomial equations of degree K-1 which have an X-intercept equal to +// the secret. Given K=3, the following equations might be generated: +// +// f1(x) = 78x^2 + 19x + 42 +// f2(x) = 128x^2 + 171x + 42 +// f3(x) = 121x^2 + 3x + 42 +// f4(x) = 91x^2 + 95x + 42 +// etc. +// +// These polynomials are then evaluated for values of X > 0: +// +// f1(1) = 139 +// f2(2) = 896 +// f3(3) = 1140 +// f4(4) = 1783 +// etc. +// +// These (x, y) pairs are the shares given to the parties. In order to combine +// shares to recover the secret, these (x, y) pairs are used as the input points +// for Lagrange interpolation, which produces a polynomial which matches the +// given points. This polynomial can be evaluated for f(0), producing the secret +// value--the common x-intercept for all the generated polynomials. +// +// If fewer than K shares are combined, the interpolated polynomial will be +// wrong, and the result of f(0) will not be the secret. +// +// This package constructs polynomials over the field GF(2^8) for each byte of +// the secret, allowing for fast splitting and combining of anything which can +// be encoded as bytes. +// +// This package has not been audited by cryptography or security professionals. +package sss + +import ( + "crypto/rand" + "errors" +) + +var ( + // ErrInvalidCount is returned when the count parameter is invalid. + ErrInvalidCount = errors.New("N must be >= K") + // ErrInvalidThreshold is returned when the threshold parameter is invalid. + ErrInvalidThreshold = errors.New("K must be > 1") +) + +// Split the given secret into N shares of which K are required to recover the +// secret. Returns a map of share IDs (1-255) to shares. +func Split(n, k byte, secret []byte) (map[byte][]byte, error) { + if k <= 1 { + return nil, ErrInvalidThreshold + } + + if n < k { + return nil, ErrInvalidCount + } + + shares := make(map[byte][]byte, n) + + for _, b := range secret { + p, err := generate(k-1, b, rand.Reader) + if err != nil { + return nil, err + } + + for x := byte(1); x <= n; x++ { + shares[x] = append(shares[x], eval(p, x)) + } + } + + return shares, nil +} + +// Combine the given shares into the original secret. +// +// N.B.: There is no way to know whether the returned value is, in fact, the +// original secret. +func Combine(shares map[byte][]byte) []byte { + var secret []byte + for _, v := range shares { + secret = make([]byte, len(v)) + break + } + + points := make([]pair, len(shares)) + for i := range secret { + p := 0 + for k, v := range shares { + points[p] = pair{x: k, y: v[i]} + p++ + } + secret[i] = interpolate(points, 0) + } + + return secret +} diff --git a/neoutils/sss/sss_test.go b/neoutils/sss/sss_test.go new file mode 100644 index 0000000..57846ee --- /dev/null +++ b/neoutils/sss/sss_test.go @@ -0,0 +1,32 @@ +package sss + +import ( + "fmt" +) + +func Example() { + secret := "well hello there!" // our secret + n := byte(30) // create 30 shares + k := byte(2) // require 2 of them to combine + + shares, err := Split(n, k, []byte(secret)) // split into 30 shares + if err != nil { + fmt.Println(err) + return + } + + // select a random subset of the total shares + subset := make(map[byte][]byte, k) + for x, y := range shares { // just iterate since maps are randomized + subset[x] = y + if len(subset) == int(k) { + break + } + } + + // combine two shares and recover the secret + recovered := string(Combine(subset)) + fmt.Println(recovered) + + // Output: well hello there! +} diff --git a/neoutils/utils.go b/neoutils/utils.go new file mode 100644 index 0000000..3c92250 --- /dev/null +++ b/neoutils/utils.go @@ -0,0 +1,80 @@ +package neoutils + +import ( + "encoding/hex" + "fmt" + "math/big" + + "github.com/o3labs/neo-utils/neoutils/btckey" +) + +func ReverseBytes(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 +} + +// Simple hex string to bytes +func HexTobytes(hexstring string) (b []byte) { + b, _ = hex.DecodeString(hexstring) + return b +} + +// Simple bytes to Hex +func BytesToHex(b []byte) string { + return hex.EncodeToString(b) +} + +// Convert script hash to NEO address +func ScriptHashToNEOAddress(scriptHash string) string { + b := HexTobytes(scriptHash) + //script hash from rpc or anything is always in big endian + //to convert to a proper neo address + //we need to reverse it first + address := btckey.B58checkencodeNEO(0x17, ReverseBytes(b)) + return address +} + +// Convert NEO address to script hash +func NEOAddressToScriptHash(neoAddress string) string { + v, b, _ := btckey.B58checkdecode(neoAddress) + if v != 0x17 { + return "" + } + //reverse from little endian to big endian + return fmt.Sprintf("%x", ReverseBytes(b)) +} + +// Validate NEO address +func ValidateNEOAddress(address string) bool { + //NEO address version is 23 + //https://github.com/neo-project/neo/blob/427a3cd08f61a33e98856e4b4312b8147708105a/neo/protocol.json#L4 + ver, _, err := btckey.B58checkdecode(address) + if err != nil { + return false + } + if ver != 23 { + return false + } + return true +} + +// Convert byte array to big int +func ConvertByteArrayToBigInt(hexString string) *big.Int { + b, err := hex.DecodeString(hexString) + if err != nil { + return nil + } + reversed := ReverseBytes(b) + reversedHex := hex.EncodeToString(reversed) + v, _ := new(big.Int).SetString(reversedHex, 16) + return v +} diff --git a/neoutils/utils_test.go b/neoutils/utils_test.go new file mode 100644 index 0000000..51e5424 --- /dev/null +++ b/neoutils/utils_test.go @@ -0,0 +1,44 @@ +package neoutils + +import ( + "log" + "testing" +) + +func TestScriptHashToNEOAddress(t *testing.T) { + hash := "e9eed8dc39332032dc22e5d6e86332c50327ba23" + address := ScriptHashToNEOAddress(hash) + + scripthash := NEOAddressToScriptHash(address) + log.Printf("address = %v result = %s", address, scripthash) + + if scripthash != hash { + t.Fail() + } +} + +func TestNEOAddressToScriptHash(t *testing.T) { + hash := NEOAddressToScriptHash("APYB8TgR8K3rAMfYt2cCfQj3zV2Rt1oTPe") + log.Printf("%v", hash) +} + +func TestValidateNEOAddress(t *testing.T) { + valid := ValidateNEOAddress("APYB8TgR8K3rAMfYt2cCfQj3zV2Rt1oTPe") + if valid == false { + t.Fail() + } +} + +func TestValidateNEOAddressInvalidAddress(t *testing.T) { + valid := ValidateNEOAddress("APYB8TgR8K3rAMfYt2cCfQj3zV2Rt1oTPe1") + if valid == true { + t.Fail() + } +} + +func TestConverting(t *testing.T) { + hex := "00e1f505" + value := ConvertByteArrayToBigInt(hex) + + log.Printf("%v", value) +}