diff --git a/cmd/kaspawallet/libkaspawallet/bip32/base58/alphabet.go b/cmd/kaspawallet/libkaspawallet/bip32/base58/alphabet.go new file mode 100644 index 000000000..6bb39fef1 --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/base58/alphabet.go @@ -0,0 +1,49 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +// AUTOGENERATED by genalphabet.go; do not edit. + +package base58 + +const ( + // alphabet is the modified base58 alphabet used by Bitcoin. + alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + + alphabetIdx0 = '1' +) + +var b58 = [256]byte{ + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 255, 255, 255, 255, 255, 255, + 255, 9, 10, 11, 12, 13, 14, 15, + 16, 255, 17, 18, 19, 20, 21, 255, + 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 255, 255, 255, 255, 255, + 255, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 255, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/base58/base58.go b/cmd/kaspawallet/libkaspawallet/bip32/base58/base58.go new file mode 100644 index 000000000..19a72de2c --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/base58/base58.go @@ -0,0 +1,75 @@ +// Copyright (c) 2013-2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package base58 + +import ( + "math/big" +) + +//go:generate go run genalphabet.go + +var bigRadix = big.NewInt(58) +var bigZero = big.NewInt(0) + +// Decode decodes a modified base58 string to a byte slice. +func Decode(b string) []byte { + answer := big.NewInt(0) + j := big.NewInt(1) + + scratch := new(big.Int) + for i := len(b) - 1; i >= 0; i-- { + tmp := b58[b[i]] + if tmp == 255 { + return []byte("") + } + scratch.SetInt64(int64(tmp)) + scratch.Mul(j, scratch) + answer.Add(answer, scratch) + j.Mul(j, bigRadix) + } + + tmpval := answer.Bytes() + + var numZeros int + for numZeros = 0; numZeros < len(b); numZeros++ { + if b[numZeros] != alphabetIdx0 { + break + } + } + flen := numZeros + len(tmpval) + val := make([]byte, flen) + copy(val[numZeros:], tmpval) + + return val +} + +// Encode encodes a byte slice to a modified base58 string. +func Encode(b []byte) string { + x := new(big.Int) + x.SetBytes(b) + + answer := make([]byte, 0, len(b)*136/100) + for x.Cmp(bigZero) > 0 { + mod := new(big.Int) + x.DivMod(x, bigRadix, mod) + answer = append(answer, alphabet[mod.Int64()]) + } + + // leading zero bytes + for _, i := range b { + if i != 0 { + break + } + answer = append(answer, alphabetIdx0) + } + + // reverse + alen := len(answer) + for i := 0; i < alen/2; i++ { + answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i] + } + + return string(answer) +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/base58/base58_test.go b/cmd/kaspawallet/libkaspawallet/bip32/base58/base58_test.go new file mode 100644 index 000000000..eb72d36b8 --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/base58/base58_test.go @@ -0,0 +1,98 @@ +// Copyright (c) 2013-2017 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package base58_test + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/btcsuite/btcutil/base58" +) + +var stringTests = []struct { + in string + out string +}{ + {"", ""}, + {" ", "Z"}, + {"-", "n"}, + {"0", "q"}, + {"1", "r"}, + {"-1", "4SU"}, + {"11", "4k8"}, + {"abc", "ZiCa"}, + {"1234598760", "3mJr7AoUXx2Wqd"}, + {"abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f"}, + {"00000000000000000000000000000000000000000000000000000000000000", "3sN2THZeE9Eh9eYrwkvZqNstbHGvrxSAM7gXUXvyFQP8XvQLUqNCS27icwUeDT7ckHm4FUHM2mTVh1vbLmk7y"}, +} + +var invalidStringTests = []struct { + in string + out string +}{ + {"0", ""}, + {"O", ""}, + {"I", ""}, + {"l", ""}, + {"3mJr0", ""}, + {"O3yxU", ""}, + {"3sNI", ""}, + {"4kl8", ""}, + {"0OIl", ""}, + {"!@#$%^&*()-_=+~`", ""}, +} + +var hexTests = []struct { + in string + out string +}{ + {"61", "2g"}, + {"626262", "a3gV"}, + {"636363", "aPEr"}, + {"73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"}, + {"00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"}, + {"516b6fcd0f", "ABnLTmg"}, + {"bf4f89001e670274dd", "3SEo3LWLoPntC"}, + {"572e4794", "3EFU7m"}, + {"ecac89cad93923c02321", "EJDM8drfXA6uyA"}, + {"10c8511e", "Rt5zm"}, + {"00000000000000000000", "1111111111"}, +} + +func TestBase58(t *testing.T) { + // Encode tests + for x, test := range stringTests { + tmp := []byte(test.in) + if res := base58.Encode(tmp); res != test.out { + t.Errorf("Encode test #%d failed: got: %s want: %s", + x, res, test.out) + continue + } + } + + // Decode tests + for x, test := range hexTests { + b, err := hex.DecodeString(test.in) + if err != nil { + t.Errorf("hex.DecodeString failed failed #%d: got: %s", x, test.in) + continue + } + if res := base58.Decode(test.out); !bytes.Equal(res, b) { + t.Errorf("Decode test #%d failed: got: %q want: %q", + x, res, test.in) + continue + } + } + + // Decode with invalid input + for x, test := range invalidStringTests { + if res := base58.Decode(test.in); string(res) != test.out { + t.Errorf("Decode invalidString test #%d failed: got: %q want: %q", + x, res, test.out) + continue + } + } +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/base58/base58bench_test.go b/cmd/kaspawallet/libkaspawallet/bip32/base58/base58bench_test.go new file mode 100644 index 000000000..2ab8fcade --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/base58/base58bench_test.go @@ -0,0 +1,35 @@ +// Copyright (c) 2013-2014 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package base58_test + +import ( + "bytes" + "testing" + + "github.com/btcsuite/btcutil/base58" +) + +func BenchmarkBase58Encode(b *testing.B) { + b.StopTimer() + data := bytes.Repeat([]byte{0xff}, 5000) + b.SetBytes(int64(len(data))) + b.StartTimer() + + for i := 0; i < b.N; i++ { + base58.Encode(data) + } +} + +func BenchmarkBase58Decode(b *testing.B) { + b.StopTimer() + data := bytes.Repeat([]byte{0xff}, 5000) + encoded := base58.Encode(data) + b.SetBytes(int64(len(encoded))) + b.StartTimer() + + for i := 0; i < b.N; i++ { + base58.Decode(encoded) + } +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/base58/base58check.go b/cmd/kaspawallet/libkaspawallet/bip32/base58/base58check.go new file mode 100644 index 000000000..7cdafeeec --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/base58/base58check.go @@ -0,0 +1,52 @@ +// Copyright (c) 2013-2014 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package base58 + +import ( + "crypto/sha256" + "errors" +) + +// ErrChecksum indicates that the checksum of a check-encoded string does not verify against +// the checksum. +var ErrChecksum = errors.New("checksum error") + +// ErrInvalidFormat indicates that the check-encoded string has an invalid format. +var ErrInvalidFormat = errors.New("invalid format: version and/or checksum bytes missing") + +// checksum: first four bytes of sha256^2 +func checksum(input []byte) (cksum [4]byte) { + h := sha256.Sum256(input) + h2 := sha256.Sum256(h[:]) + copy(cksum[:], h2[:4]) + return +} + +// CheckEncode prepends a version byte and appends a four byte checksum. +func CheckEncode(input []byte, version byte) string { + b := make([]byte, 0, 1+len(input)+4) + b = append(b, version) + b = append(b, input[:]...) + cksum := checksum(b) + b = append(b, cksum[:]...) + return Encode(b) +} + +// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum. +func CheckDecode(input string) (result []byte, version byte, err error) { + decoded := Decode(input) + if len(decoded) < 5 { + return nil, 0, ErrInvalidFormat + } + version = decoded[0] + var cksum [4]byte + copy(cksum[:], decoded[len(decoded)-4:]) + if checksum(decoded[:len(decoded)-4]) != cksum { + return nil, 0, ErrChecksum + } + payload := decoded[1 : len(decoded)-4] + result = append(result, payload...) + return +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/base58/base58check_test.go b/cmd/kaspawallet/libkaspawallet/bip32/base58/base58check_test.go new file mode 100644 index 000000000..21087cf91 --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/base58/base58check_test.go @@ -0,0 +1,66 @@ +// Copyright (c) 2013-2014 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package base58_test + +import ( + "testing" + + "github.com/btcsuite/btcutil/base58" +) + +var checkEncodingStringTests = []struct { + version byte + in string + out string +}{ + {20, "", "3MNQE1X"}, + {20, " ", "B2Kr6dBE"}, + {20, "-", "B3jv1Aft"}, + {20, "0", "B482yuaX"}, + {20, "1", "B4CmeGAC"}, + {20, "-1", "mM7eUf6kB"}, + {20, "11", "mP7BMTDVH"}, + {20, "abc", "4QiVtDjUdeq"}, + {20, "1234598760", "ZmNb8uQn5zvnUohNCEPP"}, + {20, "abcdefghijklmnopqrstuvwxyz", "K2RYDcKfupxwXdWhSAxQPCeiULntKm63UXyx5MvEH2"}, + {20, "00000000000000000000000000000000000000000000000000000000000000", "bi1EWXwJay2udZVxLJozuTb8Meg4W9c6xnmJaRDjg6pri5MBAxb9XwrpQXbtnqEoRV5U2pixnFfwyXC8tRAVC8XxnjK"}, +} + +func TestBase58Check(t *testing.T) { + for x, test := range checkEncodingStringTests { + // test encoding + if res := base58.CheckEncode([]byte(test.in), test.version); res != test.out { + t.Errorf("CheckEncode test #%d failed: got %s, want: %s", x, res, test.out) + } + + // test decoding + res, version, err := base58.CheckDecode(test.out) + if err != nil { + t.Errorf("CheckDecode test #%d failed with err: %v", x, err) + } else if version != test.version { + t.Errorf("CheckDecode test #%d failed: got version: %d want: %d", x, version, test.version) + } else if string(res) != test.in { + t.Errorf("CheckDecode test #%d failed: got: %s want: %s", x, res, test.in) + } + } + + // test the two decoding failure cases + // case 1: checksum error + _, _, err := base58.CheckDecode("3MNQE1Y") + if err != base58.ErrChecksum { + t.Error("Checkdecode test failed, expected ErrChecksum") + } + // case 2: invalid formats (string lengths below 5 mean the version byte and/or the checksum + // bytes are missing). + testString := "" + for len := 0; len < 4; len++ { + // make a string of length `len` + _, _, err = base58.CheckDecode(testString) + if err != base58.ErrInvalidFormat { + t.Error("Checkdecode test failed, expected ErrInvalidFormat") + } + } + +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/base58/cov_report.sh b/cmd/kaspawallet/libkaspawallet/bip32/base58/cov_report.sh new file mode 100644 index 000000000..307f05b76 --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/base58/cov_report.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# This script uses gocov to generate a test coverage report. +# The gocov tool my be obtained with the following command: +# go get github.com/axw/gocov/gocov +# +# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH. + +# Check for gocov. +type gocov >/dev/null 2>&1 +if [ $? -ne 0 ]; then + echo >&2 "This script requires the gocov tool." + echo >&2 "You may obtain it with the following command:" + echo >&2 "go get github.com/axw/gocov/gocov" + exit 1 +fi +gocov test | gocov report diff --git a/cmd/kaspawallet/libkaspawallet/bip32/base58/doc.go b/cmd/kaspawallet/libkaspawallet/bip32/base58/doc.go new file mode 100644 index 000000000..9a2c0e6e3 --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/base58/doc.go @@ -0,0 +1,29 @@ +// Copyright (c) 2014 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +/* +Package base58 provides an API for working with modified base58 and Base58Check +encodings. + +Modified Base58 Encoding + +Standard base58 encoding is similar to standard base64 encoding except, as the +name implies, it uses a 58 character alphabet which results in an alphanumeric +string and allows some characters which are problematic for humans to be +excluded. Due to this, there can be various base58 alphabets. + +The modified base58 alphabet used by Bitcoin, and hence this package, omits the +0, O, I, and l characters that look the same in many fonts and are therefore +hard to humans to distinguish. + +Base58Check Encoding Scheme + +The Base58Check encoding scheme is primarily used for Bitcoin addresses at the +time of this writing, however it can be used to generically encode arbitrary +byte arrays into human-readable strings along with a version byte that can be +used to differentiate the same payload. For Bitcoin addresses, the extra +version is used to differentiate the network of otherwise identical public keys +which helps prevent using an address intended for one network on another. +*/ +package base58 diff --git a/cmd/kaspawallet/libkaspawallet/bip32/base58/example_test.go b/cmd/kaspawallet/libkaspawallet/bip32/base58/example_test.go new file mode 100644 index 000000000..230a78496 --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/base58/example_test.go @@ -0,0 +1,71 @@ +// Copyright (c) 2014 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package base58_test + +import ( + "fmt" + + "github.com/btcsuite/btcutil/base58" +) + +// This example demonstrates how to decode modified base58 encoded data. +func ExampleDecode() { + // Decode example modified base58 encoded data. + encoded := "25JnwSn7XKfNQ" + decoded := base58.Decode(encoded) + + // Show the decoded data. + fmt.Println("Decoded Data:", string(decoded)) + + // Output: + // Decoded Data: Test data +} + +// This example demonstrates how to encode data using the modified base58 +// encoding scheme. +func ExampleEncode() { + // Encode example data with the modified base58 encoding scheme. + data := []byte("Test data") + encoded := base58.Encode(data) + + // Show the encoded data. + fmt.Println("Encoded Data:", encoded) + + // Output: + // Encoded Data: 25JnwSn7XKfNQ +} + +// This example demonstrates how to decode Base58Check encoded data. +func ExampleCheckDecode() { + // Decode an example Base58Check encoded data. + encoded := "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" + decoded, version, err := base58.CheckDecode(encoded) + if err != nil { + fmt.Println(err) + return + } + + // Show the decoded data. + fmt.Printf("Decoded data: %x\n", decoded) + fmt.Println("Version Byte:", version) + + // Output: + // Decoded data: 62e907b15cbf27d5425399ebf6f0fb50ebb88f18 + // Version Byte: 0 +} + +// This example demonstrates how to encode data using the Base58Check encoding +// scheme. +func ExampleCheckEncode() { + // Encode example data with the Base58Check encoding scheme. + data := []byte("Test data") + encoded := base58.CheckEncode(data, 0) + + // Show the encoded data. + fmt.Println("Encoded Data:", encoded) + + // Output: + // Encoded Data: 182iP79GRURMp7oMHDU +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/bip32.go b/cmd/kaspawallet/libkaspawallet/bip32/bip32.go new file mode 100644 index 000000000..f3a7a3474 --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/bip32.go @@ -0,0 +1,46 @@ +package bip32 + +import "crypto/rand" + +// GenerateSeed generates seed that can be used to initialize a master key. +func GenerateSeed() ([]byte, error) { + randBytes := make([]byte, 32) + _, err := rand.Read(randBytes) + if err != nil { + return nil, err + } + + return randBytes, nil +} + +// NewMasterWithPath returns a new master key based on the given seed and version, with a derivation +// to the given path. +func NewMasterWithPath(seed []byte, version [4]byte, pathString string) (*ExtendedKey, error) { + masterKey, err := NewMaster(seed, version) + if err != nil { + return nil, err + } + + return masterKey.DeriveFromPath(pathString) +} + +// NewPublicMasterWithPath returns a new public master key based on the given seed and version, with a derivation +// to the given path. +func NewPublicMasterWithPath(seed []byte, version [4]byte, pathString string) (*ExtendedKey, error) { + masterKey, err := NewMaster(seed, version) + if err != nil { + return nil, err + } + + path, err := parsePath(pathString) + if err != nil { + return nil, err + } + + descendantKey, err := masterKey.path(path) + if err != nil { + return nil, err + } + + return descendantKey.Public() +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/bip32_test.go b/cmd/kaspawallet/libkaspawallet/bip32/bip32_test.go new file mode 100644 index 000000000..757c65c86 --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/bip32_test.go @@ -0,0 +1,420 @@ +package bip32 + +import ( + "encoding/hex" + "math/rand" + "strconv" + "strings" + "testing" +) + +func TestBIP32SpecVectors(t *testing.T) { + type testPath struct { + path string + extendedPublicKey string + extendedPrivateKey string + } + + type testVector struct { + seed string + version [4]byte + paths []testPath + } + + // test vectors are copied from https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Test_Vectors + testVectors := []testVector{ + { + seed: "000102030405060708090a0b0c0d0e0f", + version: BitcoinMainnetPrivate, + paths: []testPath{ + { + path: "m", + extendedPublicKey: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", + extendedPrivateKey: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + }, + { + path: "m/0'", + extendedPublicKey: "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", + extendedPrivateKey: "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", + }, + { + path: "m/0'/1", + extendedPublicKey: "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", + extendedPrivateKey: "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", + }, + { + path: "m/0'/1/2'", + extendedPublicKey: "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", + extendedPrivateKey: "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", + }, + { + path: "m/0'/1/2'/2", + extendedPublicKey: "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", + extendedPrivateKey: "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", + }, + { + path: "m/0'/1/2'/2/1000000000", + extendedPublicKey: "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", + extendedPrivateKey: "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", + }, + }, + }, + { + seed: "000102030405060708090a0b0c0d0e0f", + version: KaspaMainnetPrivate, + paths: []testPath{ + { + path: "m", + extendedPublicKey: "kpub2C2CKMtB3F5r4LEGRnS3o73omeQB3KJ5QfAzC5R3t9bpChBEZNitvn92JYeCTMtnR7oE1im7DhsxGqV72JErXFG9G3YnTHRnZPkGZLFE6PZ", + extendedPrivateKey: "kprv5y2qurMHCsXYqr9oKku3Ry75DcZgdraE3SFPPh1SKp4qKtr61qQeNypYTGztwUUiVauHWmjxaQXeUKHxj4QCuDG4ULpZHkvBoH9XX19ynXm", + }, + { + path: "m/0'", + extendedPublicKey: "kpub2EHcK5Be8WCqCwMydYJgg99v6TxXRPn66GbtAAoArLo6ZyUQycFz3vVS5pCuCfoKRL5nsxJXxLx3FETEyKyEb8isTgM3NbL15KsprxXRXYP", + extendedPrivateKey: "kprv61JFuZekJ8eXzTHWXWmgK1DBYS831w4Ej3gHMnPZJ1G7hB9GS4wjW8AxEYMEMBrgCdnyt54pxmNXC5KgNegPhHLaYDVhXid5WHnNxE7Nir6", + }, + { + path: "m/0'/1", + extendedPublicKey: "kpub2GTjWrjXXD5u3PQRMoCZGt3a9qwdRRWP2bGikSZynybJoWyYhQgJ1VPfVtfUccWfP3hqfNke4wSWqYC4Sf98GnYoktBtrELGi4Qc9xmGTUP", + extendedPrivateKey: "kprv63UP7MCdgqXbpuKxFmfYuk6qbp791xnXfNM7x4ANEe4KvieQ9sN3Th5BebYHx7dieiYfgtfG3UKwL1quVzUNUSq23zTRbUPwB66kV2rWPC8", + }, + { + path: "m/0'/1/2'", + extendedPublicKey: "kpub2K51ZPZPE5wJuZCWcPbvdt5iNzp9gy6NN8WPzms8xqxkDNAfWAWiuvwb3urK4UwyjZoaGkjFSt1VHsLM9kgfLEheLnA2wBPxRkKkFDqc9zP", + extendedPrivateKey: "kprv665f9t2VPiP1h583WN4vGk8ypxyfHWNWzuaoCPTXQWRmLZqWxdCUN8d7CdkuvM9DABa4HMcBTt9qZDaf61PZbYGgQc1ykQdsnMqy7fTCNrm", + }, + { + path: "m/0'/1/2'/2", + extendedPublicKey: "kpub2MJQPpgLQZcHz2gEJep1XPF2Tp6tKZQZocPhFjPcHHXMaTo2ZwD67WQWjEqhUH6iCsvkQmDCVcubrHgMF47s3qAuFZiDmNHnSSEbPpuRWiZ", + extendedPrivateKey: "kprv68K3zK9SaC3zmYbmCdH1AFJHunGPv6giSPU6TLyziwzNhfTt2PtqZi62sxANP1YeDyhkuGqkNhc12QV7HRvunvrior75JVTawLK8d8zN34Z", + }, + { + path: "m/0'/1/2'/2/1000000000", + extendedPublicKey: "kpub2P2AsWHaXgzVWNTgRCNjq6F2G3gC94DbbrnFW1mkVMurHbCR6MTkNcZaN4keKYBRgaDHv7912pcCSi5NLuchu6L2878JZqsRFPrWduDKq9i", + extendedPrivateKey: "kprv6A2pTzkghKSCHtPDKAqjTxJHi1qhjbVkEdrehdN8w2NsQnsGYp9VppF6WowaHvfiqP71gdphDk982aVUpVwdutWG9LsJRQDJDfsVNMbtSap", + }, + }, + }, + { + seed: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", + version: BitcoinMainnetPrivate, + paths: []testPath{ + { + path: "m", + extendedPublicKey: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", + extendedPrivateKey: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", + }, + { + path: "m/0", + extendedPublicKey: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", + extendedPrivateKey: "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", + }, + { + path: "m/0/2147483647'", + extendedPublicKey: "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", + extendedPrivateKey: "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", + }, + { + path: "m/0/2147483647'/1", + extendedPublicKey: "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", + extendedPrivateKey: "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", + }, + { + path: "m/0/2147483647'/1/2147483646'", + extendedPublicKey: "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", + extendedPrivateKey: "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", + }, + { + path: "m/0/2147483647'/1/2147483646'/2", + extendedPublicKey: "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", + extendedPrivateKey: "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", + }, + }, + }, + { + seed: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", + version: KaspaMainnetPrivate, + paths: []testPath{ + { + path: "m", + extendedPublicKey: "kpub2C2CKMtB3F5r3wjbXwWLFJma1qYbiwYu6ExiV29srXigVgCRjXVqMgJceejBAcFkKg31vPRGcnPCzdDL9VA1fAG67ykFHmvSsmRNqKZg1po", + extendedPrivateKey: "kprv5y2qurMHCsXYqTf8RuyKtApqToi7KUq3j237gdkGJCBhcssHBzBaosz8oLmx7z2ojdeiG4CQrWZqZr24mUnuWaapvktoS6pvNXmkszbHsFE", + }, + { + path: "m/0", + extendedPublicKey: "kpub2FHwb5a8XFuvaDKtfitDK7B6NoHrRv3BeQi5eqBKvwaeBeeQJnquWWssE7h4xhGBXzXBncR21sEB9ne22drRzkvNQ2UvC84q1FY3GVzjZr1", + extendedPrivateKey: "kprv62JbBa3EgtMdMjFRZhMCwyEMpmTN2TKLHBnUrSmiNc3fJrKFmFXexiZPNr3km3se9HtYA4c9HfyxvMetKmHxSokDvwJrpazfVwgKFEAdr1L", + }, + { + path: "m/0/2147483647'", + extendedPublicKey: "kpub2GSzqgbeuA62k5Y56AsrnSremYWkyQCsjZncaE66agM2dwsrvgGDiafTqVwBiRsHKWSjSTGdK5empTWMoYLYiuNzw76yYrKqsdoe7KSjW9n", + extendedPrivateKey: "kprv63TeSB4m4nXjXbTbz9LrRJuvDWgGZwV2NLs1mqgV2Lp3m9YiP8wyAnLyzCXjVJc83XDRw5onLgV5MbPf48u627BnMfYCb6ivHj1r1gJwAAq", + }, + { + path: "m/0/2147483647'/1", + extendedPublicKey: "kpub2KFyFhab4oPDqhDD9q2RkPnt75PG5b8941HURHkRtZhUJmk2EBnvcV3qgJ8KWJZZuguHH6MrxCxbuFmNiSmVzEquXPJpmPm3oQUbMkjZU7h", + extendedPrivateKey: "kprv66GcrC3hERpvdD8k3oVRPFr9Z3Ymg8QHgnMscuLpLEAVRyQsgeUg4gjMpzjMX1opMUa8gNtAkEAHgJAp72RU2b15VS51SChJmXSaVHSHVgJ", + }, + { + path: "m/0/2147483647'/1/2147483646'", + extendedPublicKey: "kpub2LS1AfWwgCLw8eSotJqy7uV51ord8Zke5i1Mx1SqjKxim84xKriw91QwJxFphg61s8Yv5bRZzpHTYtvmQKt1hbYMoHdKKgrTfdZAtem6FS7", + extendedPrivateKey: "kprv67Sem9z3qpndvANLnHJxkmYLTn28j72niV5m9d3EAzRjtKjonKQgbD6TThTk9SC6u3rpzCfA8bjsVRGBcyKxiRgFKNcKaQiw77T6Z6V751r", + }, + { + path: "m/0/2147483647'/1/2147483646'/2", + extendedPublicKey: "kpub2Mo386jTCNfAsudhcNyf6es3QsPjNtfijsdFMnoLN7pJqKQXVVehKaMwPML6qFSiPBm9MWvytXJT3KzGERZv1rPwSTTQG49CLvkMZGaHgA1", + extendedPrivateKey: "kprv68ogibCZN16sfRZEWMSejWvJrqZEyRwsNeheZQPionHKxX5NwxLSmn3TY78kJTHAwMiZGxHyahaZXy9hMHhBmQQy8E7pdpreoUnedk17vmK", + }, + }, + }, + { + seed: "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be", + version: BitcoinMainnetPrivate, + paths: []testPath{ + { + path: "m", + extendedPublicKey: "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13", + extendedPrivateKey: "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6", + }, + { + path: "m/0'", + extendedPublicKey: "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y", + extendedPrivateKey: "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L", + }, + }, + }, + { + seed: "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be", + version: KaspaMainnetPrivate, + paths: []testPath{ + { + path: "m", + extendedPublicKey: "kpub2C2CKMtB3F5r31Bm4L18TJ2btUshUoiAoajGtKBS8DoUTvRhPfjoJwY98eG9zCqPVknskPJH1TD4RvrEzCCT5VvEFDeU2LNHfUw5MkWwVFF", + extendedPrivateKey: "kprv5y2qurMHCsXYpX7HxJU86A5sLT3D5LzKSMog5vmpZtGVb86Yr8RYm9DfHLW851G8pLKTpytWkwJYvVdNzuwLJ465T3TSdYAtfFS7Xx2owSo", + }, + { + path: "m/0'", + extendedPublicKey: "kpub2EPQ4KiJicTCEYHAHULdWYnGaqV5df85D4yDhYsH4XiqiwZ9yAWfPQKrSN6fiZS8h8HiXM41rQQZ4PnavS8dekCAvKbMaBs69fHz2AFgp7S", + extendedPrivateKey: "kprv61Q3epBQtEtu24ChBSod9QqY2oebECQDqr3cuATfWCBrr9E1RdCQqc1Nb6rUQxb4GUxsqvgPQfw1a3GXa8X63pHhtBNVNhShnGPmVHW2UAU", + }, + }, + }, + { + seed: "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be", + version: KaspaTestnetPrivate, + paths: []testPath{ + { + path: "m", + extendedPublicKey: "ktub1vi816jr1DomD4Qct6C1n3qZ98kvP4TCgq21QUVkJ1xTADT74WfStAQAv5NA5ACsLYRLiAEs2YMz91LoWXh6YY6bFd6BidfhFE7dxhSVK7H", + extendedPrivateKey: "ktrv5himbbCxArFTzaL9n4f1Qutpb6vRybjMKc6Qc668jgRUHR7xWyMCLN5h4mc89xdcf7wvnkq6n2TUda7wXFRym6GSTSuAKqUJEzcg8smnbs9", + }, + { + path: "m/0'", + extendedPublicKey: "ktub1y5Kk4ZygbB7QbW27EXWqJbDqVNJXus76KFxDiBbEKspREaZe1SJxdBtDoCfoWocXuvBV7zbsVZUmUH9SmdH7nNXvj35GVAVjQUYd41EFiN", + extendedPrivateKey: "ktrv5k5yLZ35rDcpC7RZ1CzWUAeVHTXp8T9Fj6LMRKmyfzLqYSFR6U84QpsQNXxUVuxY7GbLohcyRm5wH7m66U1jWrU4tapD4zk7N1aL6C3rnp7", + }, + }, + }, + { + seed: "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be", + version: KaspaDevnetPrivate, + paths: []testPath{ + { + path: "m", + extendedPublicKey: "kdub4zZ57oeuxodRE6ZjEtX6KnUpww7hSaHbRg745QUF8GSe3Y6EKZa6tkjuJvCw8cTYni4dWNgh3tr5HoER7gTzpEyQcM4VNJmxyu9H2k8C3SL", + extendedPrivateKey: "kdrv8mZiiJ828S581cVG8rz5xeY6PuHD37Zk4TBTH24dZvufAjm5n2FrLxRRTcSuDQtJ7HbDayGvoNwZnN1Z8QCt2o9FpAsTyWaZyfeKCvnUDsn", + }, + { + path: "m/0'", + extendedPublicKey: "kdub52vGrmV3eAzmRdf8U2rbP3EVeHj5bRhVqALzteA64aN1JZDgu4LxyDXcce3Sry4Hz5ZUHLSRtr3ZvGAm3vQBPVFMHT1NvAGmU5WBhDEnN5e", + extendedPrivateKey: "kdrv8ovvTFx9ooSUD9afN1Kb1uHm6FtbBxyeTwRQ6FkUWEq2RktYMX2iRRD8mNoFZNDDZSEdbv4oT7a2RuehhcndnZLtFJnWifrP6gbyAN52eWX", + }, + }, + }, + { + seed: "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be", + version: KaspaSimnetPrivate, + paths: []testPath{ + { + path: "m", + extendedPublicKey: "ksub8Dp9tLuSVjDpAnKQ1sEyyZfrLiYhUQ7jd5TD92tgeX7mTQdyCwZYDNuhiN2WC23BwhGKgKvoJaWSPERp5gSuV4h2QEt1JwHyrSCjoEf5VHR", + extendedPrivateKey: "ksrv3h87jvsCMMY9HKHc8DaPaA1jmh2mK4vq3mrRrWrPEFKNcvPCaYjx2tHqYCMLkgu1qonn9ScvgKMgeUG5pkgVdEgB579ytyAQBLrnezdttFb", + }, + { + path: "m/0'", + extendedPublicKey: "ksub8GBMdJjaB6bANKQoF1aV2pRX35A5dFXe2Zh9xGaXaq38iRmRnSLQHqhR25s1vNdw94mATHgY9Xhw1hNA1vP64Jxy5LptrnnnLcZeTbX9Sng", + extendedPrivateKey: "ksrv3jVKUthL2iuVUrP1MMutdQmQU3e9TvLjTG6NfkYEAZEjswWfA3Wp7M5Yqxhh6eDwHxSCAPQoL3z9J1uEPyGFNzsoWF52e8SDJMpScP8xuRw", + }, + }, + }, + } + + for i, vector := range testVectors { + seed, err := hex.DecodeString(vector.seed) + if err != nil { + t.Fatalf("DecodeString: %+v", err) + } + + masterKey, err := NewMaster(seed, vector.version) + if err != nil { + t.Fatalf("NewMaster: %+v", err) + } + + for j, path := range vector.paths { + extendedPrivateKey, err := masterKey.DeriveFromPath(path.path) + if err != nil { + t.Fatalf("Path: %+v", err) + } + + if extendedPrivateKey.String() != path.extendedPrivateKey { + t.Fatalf("Test (%d, %d): expected extended private key %s but got %s", i, j, path.extendedPrivateKey, extendedPrivateKey.String()) + } + + decodedExtendedPrivateKey, err := DeserializeExtendedPrivateKey(extendedPrivateKey.String()) + if err != nil { + t.Fatalf("DeserializeExtendedPrivateKey: %+v", err) + } + + if extendedPrivateKey.String() != decodedExtendedPrivateKey.String() { + t.Fatalf("Test (%d, %d): deserializing and serializing the extended private key didn't preserve the data", i, j) + } + + extendedPublicKey, err := extendedPrivateKey.Public() + if err != nil { + t.Fatalf("Public: %+v", err) + } + + if extendedPublicKey.String() != path.extendedPublicKey { + t.Fatalf("Test (%d, %d): expected extended public key %s but got %s", i, j, path.extendedPublicKey, extendedPublicKey.String()) + } + + decodedExtendedPublicKey, err := DeserializeExtendedPrivateKey(extendedPublicKey.String()) + if err != nil { + t.Fatalf("DeserializeExtendedPublicKey: %+v", err) + } + + if extendedPublicKey.String() != decodedExtendedPublicKey.String() { + t.Fatalf("Test (%d, %d): deserializing and serializing the ext pub didn't preserve the data", i, j) + } + } + } +} + +// TestExtendedKey_DeriveFromPath checks that path that derive from extended private key and extended +// public key lead to the same public keys. +func TestExtendedKey_DeriveFromPath(t *testing.T) { + r := rand.New(rand.NewSource(0)) + seed, err := GenerateSeed() + if err != nil { + t.Fatalf("GenerateSeed: %+v", err) + } + + master, err := NewMaster(seed, KaspaMainnetPrivate) + if err != nil { + t.Fatalf("GenerateSeed: %+v", err) + } + + masterPublic, err := master.Public() + if err != nil { + t.Fatalf("Public: %+v", err) + } + + for i := 0; i < 10; i++ { + numIndexes := 1 + r.Intn(100) + indexes := make([]string, numIndexes) + for i := 0; i < numIndexes; i++ { + index := r.Intn(hardenedIndexStart) + indexes[i] = strconv.Itoa(int(index)) + } + + indexesStr := strings.Join(indexes, "/") + pathPrivate := "m/" + indexesStr + pathPublic := "M/" + indexesStr + + extendedPrivateKey, err := master.DeriveFromPath(pathPrivate) + if err != nil { + t.Fatalf("Path: %+v", err) + } + + extendedPublicKeyFromPrivateKey, err := extendedPrivateKey.Public() + if err != nil { + t.Fatalf("Public: %+v", err) + } + + extendedPublicKey, err := masterPublic.DeriveFromPath(pathPublic) + if err != nil { + t.Fatalf("Path: %+v", err) + } + + if extendedPublicKeyFromPrivateKey.String() != extendedPublicKey.String() { + t.Fatalf("Path gives different result from private and public master keys") + } + } +} + +// TestPublicParentPublicChildDerivation was copied and modified from https://github.com/tyler-smith/go-bip32/blob/master/bip32_test.go +func TestPublicParentPublicChildDerivation(t *testing.T) { + // Generated using https://iancoleman.github.io/bip39/ + // Root key: + // xprv9s21ZrQH143K2Cfj4mDZBcEecBmJmawReGwwoAou2zZzG45bM6cFPJSvobVTCB55L6Ld2y8RzC61CpvadeAnhws3CHsMFhNjozBKGNgucYm + // Derivation Path m/44'/60'/0'/0: + // xprv9zy5o7z1GMmYdaeQdmabWFhUf52Ytbpe3G5hduA4SghboqWe7aDGWseN8BJy1GU72wPjkCbBE1hvbXYqpCecAYdaivxjNnBoSNxwYD4wHpW + // xpub6DxSCdWu6jKqr4isjo7bsPeDD6s3J4YVQV1JSHZg12Eagdqnf7XX4fxqyW2sLhUoFWutL7tAELU2LiGZrEXtjVbvYptvTX5Eoa4Mamdjm9u + extendedMasterPublic, err := DeserializeExtendedPrivateKey("xpub6DxSCdWu6jKqr4isjo7bsPeDD6s3J4YVQV1JSHZg12Eagdqnf7XX4fxqyW2sLhUoFWutL7tAELU2LiGZrEXtjVbvYptvTX5Eoa4Mamdjm9u") + if err != nil { + t.Fatalf("DeserializeExtendedPublicKey: %+v", err) + } + + type testChildKey struct { + pathFragment uint32 + privKey string + pubKey string + hexPubKey string + } + + expectedChildren := []testChildKey{ + {pathFragment: 0, hexPubKey: "0243187e1a2ba9ba824f5f81090650c8f4faa82b7baf93060d10b81f4b705afd46"}, + {pathFragment: 1, hexPubKey: "023790d11eb715c4320d8e31fba3a09b700051dc2cdbcce03f44b11c274d1e220b"}, + {pathFragment: 2, hexPubKey: "0302c5749c3c75cea234878ae3f4d8f65b75d584bcd7ed0943b016d6f6b59a2bad"}, + {pathFragment: 3, hexPubKey: "03f0440c94e5b14ea5b15875934597afff541bec287c6e65dc1102cafc07f69699"}, + {pathFragment: 4, hexPubKey: "026419d0d8996707605508ac44c5871edc7fe206a79ef615b74f2eea09c5852e2b"}, + {pathFragment: 5, hexPubKey: "02f63c6f195eea98bdb163c4a094260dea71d264b21234bed4df3899236e6c2298"}, + {pathFragment: 6, hexPubKey: "02d74709cd522081064858f393d009ead5a0ecd43ede3a1f57befcc942025cb5f9"}, + {pathFragment: 7, hexPubKey: "03e54bb92630c943d38bbd8a4a2e65fca7605e672d30a0e545a7198cbb60729ceb"}, + {pathFragment: 8, hexPubKey: "027e9d5acd14d39c4938697fba388cd2e8f31fc1c5dc02fafb93a10a280de85199"}, + {pathFragment: 9, hexPubKey: "02a167a9f0d57468fb6abf2f3f7967e2cadf574314753a06a9ef29bc76c54638d2"}, + + {pathFragment: 100, hexPubKey: "020db9ba00ddf68428e3f5bfe54252bbcd75b21e42f51bf3bfc4172bf0e5fa7905"}, + {pathFragment: 101, hexPubKey: "0299e3790956570737d6164e6fcda5a3daa304065ca95ba46bc73d436b84f34d46"}, + {pathFragment: 102, hexPubKey: "0202e0732c4c5d2b1036af173640e01957998cfd4f9cdaefab6ffe76eb869e2c59"}, + {pathFragment: 103, hexPubKey: "03d050adbd996c0c5d737ff638402dfbb8c08e451fef10e6d62fb57887c1ac6cb2"}, + {pathFragment: 104, hexPubKey: "038d466399e2d68b4b16043ad4d88893b3b2f84fc443368729a973df1e66f4f530"}, + {pathFragment: 105, hexPubKey: "034811e2f0c8c50440c08c2c9799b99c911c036e877e8325386ff61723ae3ffdce"}, + {pathFragment: 106, hexPubKey: "026339fd5842921888e711a6ba9104a5f0c94cc0569855273cf5faefdfbcd3cc29"}, + {pathFragment: 107, hexPubKey: "02833705c1069fab2aa92c6b0dac27807290d72e9f52378d493ac44849ca003b22"}, + {pathFragment: 108, hexPubKey: "032d2639bde1eb7bdf8444bd4f6cc26a9d1bdecd8ea15fac3b992c3da68d9d1df5"}, + {pathFragment: 109, hexPubKey: "02479c6d4a64b93a2f4343aa862c938fbc658c99219dd7bebb4830307cbd76c9e9"}, + } + + for i, child := range expectedChildren { + extendedPublicKey, err := extendedMasterPublic.Child(child.pathFragment) + if err != nil { + t.Fatalf("Child: %+v", err) + } + + publicKey, err := extendedPublicKey.PublicKey() + if err != nil { + t.Fatalf("PublicKey: %+v", err) + } + + pubKeyBytes, err := publicKey.Serialize() + if err != nil { + t.Fatalf("Serialize: %+v", err) + } + + pubKeyHex := hex.EncodeToString(pubKeyBytes[:]) + if child.hexPubKey != pubKeyHex { + t.Fatalf("Test #%d: expected public key %s but got %s", i, child.hexPubKey, pubKeyHex) + } + } +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/child_key_derivation.go b/cmd/kaspawallet/libkaspawallet/bip32/child_key_derivation.go new file mode 100644 index 000000000..e92927ccd --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/child_key_derivation.go @@ -0,0 +1,152 @@ +package bip32 + +import ( + "encoding/binary" + "github.com/kaspanet/go-secp256k1" + "github.com/pkg/errors" +) + +const hardenedIndexStart = 0x80000000 + +// NewMaster returns a new extended private key based on the given seed and version +func NewMaster(seed []byte, version [4]byte) (*ExtendedKey, error) { + mac := newHMACWriter([]byte("Bitcoin seed")) + mac.InfallibleWrite(seed) + I := mac.Sum(nil) + + var iL, iR [32]byte + copy(iL[:], I[:32]) + copy(iR[:], I[32:]) + + privateKey, err := secp256k1.DeserializeECDSAPrivateKeyFromSlice(iL[:]) + if err != nil { + return nil, err + } + + return &ExtendedKey{ + privateKey: privateKey, + Version: version, + Depth: 0, + ParentFingerprint: [4]byte{}, + ChildNumber: 0, + ChainCode: iR, + }, nil +} + +func isHardened(i uint32) bool { + return i >= hardenedIndexStart +} + +// Child return the i'th derived child of extKey. +func (extKey *ExtendedKey) Child(i uint32) (*ExtendedKey, error) { + I, err := extKey.calcI(i) + if err != nil { + return nil, err + } + + var iL, iR [32]byte + copy(iL[:], I[:32]) + copy(iR[:], I[32:]) + + fingerPrint, err := extKey.calcFingerprint() + if err != nil { + return nil, err + } + + childExt := &ExtendedKey{ + Version: extKey.Version, + Depth: extKey.Depth + 1, + ParentFingerprint: fingerPrint, + ChildNumber: i, + ChainCode: iR, + } + + if extKey.privateKey != nil { + childExt.privateKey, err = privateKeyAdd(extKey.privateKey, iL) + if err != nil { + return nil, err + } + } else { + publicKey, err := extKey.PublicKey() + if err != nil { + return nil, err + } + + childExt.publicKey, err = pointAdd(publicKey, iL) + if err != nil { + return nil, err + } + } + + return childExt, nil +} + +func (extKey *ExtendedKey) calcFingerprint() ([4]byte, error) { + publicKey, err := extKey.PublicKey() + if err != nil { + return [4]byte{}, err + } + + serializedPoint, err := publicKey.Serialize() + if err != nil { + return [4]byte{}, err + } + + hash := hash160(serializedPoint[:]) + var fingerprint [4]byte + copy(fingerprint[:], hash[:4]) + return fingerprint, nil +} + +func privateKeyAdd(k *secp256k1.ECDSAPrivateKey, tweak [32]byte) (*secp256k1.ECDSAPrivateKey, error) { + kCopy := *k + err := kCopy.Add(tweak) + if err != nil { + return nil, err + } + + return &kCopy, nil +} + +func (extKey *ExtendedKey) calcI(i uint32) ([]byte, error) { + if isHardened(i) && !extKey.IsPrivate() { + return nil, errors.Errorf("Cannot calculate hardened child for public key") + } + + mac := newHMACWriter(extKey.ChainCode[:]) + if isHardened(i) { + mac.InfallibleWrite([]byte{0x00}) + mac.InfallibleWrite(extKey.privateKey.Serialize()[:]) + } else { + publicKey, err := extKey.PublicKey() + if err != nil { + return nil, err + } + + serializedPublicKey, err := publicKey.Serialize() + if err != nil { + return nil, errors.Wrap(err, "error serializing public key") + } + + mac.InfallibleWrite(serializedPublicKey[:]) + } + + mac.InfallibleWrite(serializeUint32(i)) + return mac.Sum(nil), nil +} + +func serializeUint32(v uint32) []byte { + serialized := make([]byte, 4) + binary.BigEndian.PutUint32(serialized, v) + return serialized +} + +func pointAdd(point *secp256k1.ECDSAPublicKey, tweak [32]byte) (*secp256k1.ECDSAPublicKey, error) { + pointCopy := *point + err := pointCopy.Add(tweak) + if err != nil { + return nil, err + } + + return &pointCopy, nil +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/extended_key.go b/cmd/kaspawallet/libkaspawallet/bip32/extended_key.go new file mode 100644 index 000000000..a422fb0c3 --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/extended_key.go @@ -0,0 +1,108 @@ +package bip32 + +import ( + "github.com/btcsuite/btcutil/base58" + "github.com/kaspanet/go-secp256k1" + "github.com/pkg/errors" +) + +// ExtendedKey is a bip32 extended key +type ExtendedKey struct { + privateKey *secp256k1.ECDSAPrivateKey + publicKey *secp256k1.ECDSAPublicKey + Version [4]byte + Depth uint8 + ParentFingerprint [4]byte + ChildNumber uint32 + ChainCode [32]byte +} + +// PrivateKey returns the ECDSA private key associated with the extended key +func (extKey *ExtendedKey) PrivateKey() *secp256k1.ECDSAPrivateKey { + return extKey.privateKey +} + +// PublicKey returns the ECDSA public key associated with the extended key +func (extKey *ExtendedKey) PublicKey() (*secp256k1.ECDSAPublicKey, error) { + if extKey.publicKey != nil { + return extKey.publicKey, nil + } + + publicKey, err := extKey.privateKey.ECDSAPublicKey() + if err != nil { + return nil, err + } + + extKey.publicKey = publicKey + return publicKey, nil +} + +// IsPrivate returns whether the extended key is private +func (extKey *ExtendedKey) IsPrivate() bool { + return extKey.privateKey != nil +} + +// Public returns public version of the extended key +func (extKey *ExtendedKey) Public() (*ExtendedKey, error) { + if !extKey.IsPrivate() { + return extKey, nil + } + + publicKey, err := extKey.PublicKey() + if err != nil { + return nil, errors.Wrap(err, "error calculating publicKey") + } + + version, err := toPublicVersion(extKey.Version) + if err != nil { + return nil, err + } + + return &ExtendedKey{ + publicKey: publicKey, + Version: version, + Depth: extKey.Depth, + ParentFingerprint: extKey.ParentFingerprint, + ChildNumber: extKey.ChildNumber, + ChainCode: extKey.ChainCode, + }, nil +} + +// DeriveFromPath returns the extended key derived from the given path +func (extKey *ExtendedKey) DeriveFromPath(pathString string) (*ExtendedKey, error) { + path, err := parsePath(pathString) + if err != nil { + return nil, err + } + + return extKey.path(path) +} + +func (extKey *ExtendedKey) path(path *path) (*ExtendedKey, error) { + if path.isPrivate && !extKey.IsPrivate() { + return nil, errors.Errorf("extended public keys cannot handle a private path") + } + + descendantExtKey := extKey + for _, index := range path.indexes { + var err error + descendantExtKey, err = descendantExtKey.Child(index) + if err != nil { + return nil, err + } + } + + if !path.isPrivate { + return descendantExtKey.Public() + } + + return descendantExtKey, nil +} + +func (extKey *ExtendedKey) String() string { + serialized, err := extKey.serialize() + if err != nil { + panic(errors.Wrap(err, "error serializing key")) + } + return base58.Encode(serialized) +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/hash.go b/cmd/kaspawallet/libkaspawallet/bip32/hash.go new file mode 100644 index 000000000..e6f0f756f --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/hash.go @@ -0,0 +1,58 @@ +package bip32 + +import ( + "bytes" + "crypto/hmac" + "crypto/sha256" + "crypto/sha512" + "github.com/pkg/errors" + "golang.org/x/crypto/ripemd160" + "hash" +) + +func newHMACWriter(key []byte) hmacWriter { + return hmacWriter{ + Hash: hmac.New(sha512.New, key), + } +} + +type hmacWriter struct { + hash.Hash +} + +func (hw hmacWriter) InfallibleWrite(p []byte) { + _, err := hw.Write(p) + if err != nil { + panic(errors.Wrap(err, "writing to hmac should never fail")) + } +} + +func calcChecksum(data []byte) []byte { + return doubleSha256(data)[:checkSumLen] +} + +func doubleSha256(data []byte) []byte { + inner := sha256.Sum256(data) + outer := sha256.Sum256(inner[:]) + return outer[:] +} + +// validateChecksum validates that the last checkSumLen bytes of the +// given data are its valid checksum. +func validateChecksum(data []byte) error { + checksum := data[len(data)-checkSumLen:] + expectedChecksum := calcChecksum(data[:len(data)-checkSumLen]) + if !bytes.Equal(expectedChecksum, checksum) { + return errors.Errorf("expected checksum %x but got %x", expectedChecksum, checksum) + } + + return nil +} + +func hash160(data []byte) []byte { + sha := sha256.New() + ripe := ripemd160.New() + sha.Write(data) + ripe.Write(sha.Sum(nil)) + return ripe.Sum(nil) +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/path.go b/cmd/kaspawallet/libkaspawallet/bip32/path.go new file mode 100644 index 000000000..8e1350438 --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/path.go @@ -0,0 +1,60 @@ +package bip32 + +import ( + "github.com/pkg/errors" + "strconv" + "strings" +) + +type path struct { + isPrivate bool + indexes []uint32 +} + +func parsePath(pathString string) (*path, error) { + parts := strings.Split(pathString, "/") + isPrivate := false + switch parts[0] { + case "m": + isPrivate = true + case "M": + isPrivate = false + default: + return nil, errors.Errorf("%s is an invalid extended key type", parts[0]) + } + + indexParts := parts[1:] + indexes := make([]uint32, len(indexParts)) + for i, part := range indexParts { + var err error + indexes[i], err = parseIndex(part) + if err != nil { + return nil, err + } + } + + return &path{ + isPrivate: isPrivate, + indexes: indexes, + }, nil +} + +func parseIndex(indexString string) (uint32, error) { + const isHardenedSuffix = "'" + isHardened := strings.HasSuffix(indexString, isHardenedSuffix) + trimmedIndexString := strings.TrimSuffix(indexString, isHardenedSuffix) + index, err := strconv.Atoi(trimmedIndexString) + if err != nil { + return 0, err + } + + if index >= hardenedIndexStart { + return 0, errors.Errorf("max index value is %d but got %d", hardenedIndexStart, index) + } + + if isHardened { + return uint32(index) + hardenedIndexStart, nil + } + + return uint32(index), nil +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/serialization.go b/cmd/kaspawallet/libkaspawallet/bip32/serialization.go new file mode 100644 index 000000000..bb11f0b53 --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/serialization.go @@ -0,0 +1,148 @@ +package bip32 + +import ( + "encoding/binary" + "github.com/kaspanet/go-secp256k1" + "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/bip32/base58" + "github.com/pkg/errors" +) + +const ( + versionSerializationLen = 4 + depthSerializationLen = 1 + fingerprintSerializationLen = 4 + childNumberSerializationLen = 4 + chainCodeSerializationLen = 32 + keySerializationLen = 33 + checkSumLen = 4 +) + +const extendedKeySerializationLen = versionSerializationLen + + depthSerializationLen + + fingerprintSerializationLen + + childNumberSerializationLen + + chainCodeSerializationLen + + keySerializationLen + + checkSumLen + +// DeserializeExtendedPrivateKey deserialized the given base58 string and returns an extended key +func DeserializeExtendedPrivateKey(extKeyString string) (*ExtendedKey, error) { + serializedBytes := base58.Decode(extKeyString) + return deserializeExtendedPrivateKey(serializedBytes) +} + +func deserializeExtendedPrivateKey(serialized []byte) (*ExtendedKey, error) { + if len(serialized) != extendedKeySerializationLen { + return nil, errors.Errorf("key length must be %d bytes but got %d", extendedKeySerializationLen, len(serialized)) + } + + err := validateChecksum(serialized) + if err != nil { + return nil, err + } + + extKey := &ExtendedKey{} + + copy(extKey.Version[:], serialized[:versionSerializationLen]) + extKey.Depth = serialized[versionSerializationLen] + copy(extKey.ParentFingerprint[:], serialized[versionSerializationLen+depthSerializationLen:]) + extKey.ChildNumber = binary.BigEndian.Uint32( + serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen:], + ) + copy( + extKey.ChainCode[:], + serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen+childNumberSerializationLen:], + ) + + isPrivate := isPrivateVersion(extKey.Version) + if isPrivate { + privateKeyPadding := serialized[versionSerializationLen+ + depthSerializationLen+ + fingerprintSerializationLen+ + childNumberSerializationLen+ + chainCodeSerializationLen] + if privateKeyPadding != 0 { + return nil, errors.Errorf("expected 0 padding for private key but got %d", privateKeyPadding) + } + + extKey.privateKey, err = secp256k1.DeserializeECDSAPrivateKeyFromSlice(serialized[versionSerializationLen+ + depthSerializationLen+ + fingerprintSerializationLen+ + childNumberSerializationLen+ + chainCodeSerializationLen+1 : versionSerializationLen+ + depthSerializationLen+ + fingerprintSerializationLen+ + childNumberSerializationLen+ + chainCodeSerializationLen+ + keySerializationLen]) + if err != nil { + return nil, err + } + } else { + extKey.publicKey, err = secp256k1.DeserializeECDSAPubKey(serialized[versionSerializationLen+ + depthSerializationLen+ + fingerprintSerializationLen+ + childNumberSerializationLen+ + chainCodeSerializationLen : versionSerializationLen+ + depthSerializationLen+ + fingerprintSerializationLen+ + childNumberSerializationLen+ + chainCodeSerializationLen+ + keySerializationLen]) + if err != nil { + return nil, err + } + } + + return extKey, nil +} + +func (extKey *ExtendedKey) serialize() ([]byte, error) { + var serialized [extendedKeySerializationLen]byte + copy(serialized[:], extKey.Version[:]) + serialized[versionSerializationLen] = extKey.Depth + copy(serialized[versionSerializationLen+depthSerializationLen:], extKey.ParentFingerprint[:]) + binary.BigEndian.PutUint32( + serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen:], + extKey.ChildNumber, + ) + copy( + serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen+childNumberSerializationLen:], + extKey.ChainCode[:], + ) + + if extKey.IsPrivate() { + serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen+childNumberSerializationLen+chainCodeSerializationLen] = 0 + copy( + serialized[versionSerializationLen+ + depthSerializationLen+ + fingerprintSerializationLen+ + childNumberSerializationLen+ + chainCodeSerializationLen+ + 1:], + extKey.privateKey.Serialize()[:], + ) + } else { + publicKey, err := extKey.PublicKey() + if err != nil { + return nil, err + } + + serializedPublicKey, err := publicKey.Serialize() + if err != nil { + return nil, err + } + + copy( + serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen+childNumberSerializationLen+chainCodeSerializationLen:], + serializedPublicKey[:], + ) + } + + checkSum := doubleSha256(serialized[:len(serialized)-checkSumLen]) + copy( + serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen+childNumberSerializationLen+chainCodeSerializationLen+keySerializationLen:], + checkSum, + ) + return serialized[:], nil +} diff --git a/cmd/kaspawallet/libkaspawallet/bip32/version.go b/cmd/kaspawallet/libkaspawallet/bip32/version.go new file mode 100644 index 000000000..7d050cb5f --- /dev/null +++ b/cmd/kaspawallet/libkaspawallet/bip32/version.go @@ -0,0 +1,137 @@ +package bip32 + +import "github.com/pkg/errors" + +// BitcoinMainnetPrivate is the version that is used for +// bitcoin mainnet bip32 private extended keys. +// Ecnodes to xprv in base58. +var BitcoinMainnetPrivate = [4]byte{ + 0x04, + 0x88, + 0xad, + 0xe4, +} + +// BitcoinMainnetPublic is the version that is used for +// bitcoin mainnet bip32 public extended keys. +// Ecnodes to xpub in base58. +var BitcoinMainnetPublic = [4]byte{ + 0x04, + 0x88, + 0xb2, + 0x1e, +} + +// KaspaMainnetPrivate is the version that is used for +// kaspa mainnet bip32 private extended keys. +// Ecnodes to xprv in base58. +var KaspaMainnetPrivate = [4]byte{ + 0x03, + 0x8f, + 0x2e, + 0xf4, +} + +// KaspaMainnetPublic is the version that is used for +// kaspa mainnet bip32 public extended keys. +// Ecnodes to kpub in base58. +var KaspaMainnetPublic = [4]byte{ + 0x03, + 0x8f, + 0x33, + 0x2e, +} + +// KaspaTestnetPrivate is the version that is used for +// kaspa testnet bip32 public extended keys. +// Ecnodes to ktrv in base58. +var KaspaTestnetPrivate = [4]byte{ + 0x03, + 0x90, + 0x9e, + 0x07, +} + +// KaspaTestnetPublic is the version that is used for +// kaspa testnet bip32 public extended keys. +// Ecnodes to ktub in base58. +var KaspaTestnetPublic = [4]byte{ + 0x03, + 0x90, + 0xa2, + 0x41, +} + +// KaspaDevnetPrivate is the version that is used for +// kaspa devnet bip32 public extended keys. +// Ecnodes to kdrv in base58. +var KaspaDevnetPrivate = [4]byte{ + 0x03, + 0x8b, + 0x3d, + 0x80, +} + +// KaspaDevnetPublic is the version that is used for +// kaspa devnet bip32 public extended keys. +// Ecnodes to xdub in base58. +var KaspaDevnetPublic = [4]byte{ + 0x03, + 0x8b, + 0x41, + 0xba, +} + +// KaspaSimnetPrivate is the version that is used for +// kaspa simnet bip32 public extended keys. +// Ecnodes to ksrv in base58. +var KaspaSimnetPrivate = [4]byte{ + 0x03, + 0x90, + 0x42, + 0x42, +} + +// KaspaSimnetPublic is the version that is used for +// kaspa simnet bip32 public extended keys. +// Ecnodes to xsub in base58. +var KaspaSimnetPublic = [4]byte{ + 0x03, + 0x90, + 0x46, + 0x7d, +} + +func toPublicVersion(version [4]byte) ([4]byte, error) { + switch version { + case BitcoinMainnetPrivate: + return BitcoinMainnetPublic, nil + case KaspaMainnetPrivate: + return KaspaMainnetPublic, nil + case KaspaTestnetPrivate: + return KaspaTestnetPublic, nil + case KaspaDevnetPrivate: + return KaspaDevnetPublic, nil + case KaspaSimnetPrivate: + return KaspaSimnetPublic, nil + } + + return [4]byte{}, errors.Errorf("unknown version %x", version) +} + +func isPrivateVersion(version [4]byte) bool { + switch version { + case BitcoinMainnetPrivate: + return true + case KaspaMainnetPrivate: + return true + case KaspaTestnetPrivate: + return true + case KaspaDevnetPrivate: + return true + case KaspaSimnetPrivate: + return true + } + + return false +} diff --git a/domain/consensus/processes/difficultymanager/difficultymanager.go b/domain/consensus/processes/difficultymanager/difficultymanager.go index 0a584507e..793368ccc 100644 --- a/domain/consensus/processes/difficultymanager/difficultymanager.go +++ b/domain/consensus/processes/difficultymanager/difficultymanager.go @@ -78,6 +78,9 @@ func (dm *difficultyManager) genesisBits(stagingArea *model.StagingArea) (uint32 func (dm *difficultyManager) StageDAADataAndReturnRequiredDifficulty( stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (uint32, error) { + onEnd := logger.LogAndMeasureExecutionTime(log, "StageDAADataAndReturnRequiredDifficulty") + defer onEnd() + // Fetch window of dag.difficultyAdjustmentWindowSize + 1 so we can have dag.difficultyAdjustmentWindowSize block intervals targetsWindow, windowHashes, err := dm.blockWindow(stagingArea, blockHash, dm.difficultyAdjustmentWindowSize+1) if err != nil { diff --git a/go.mod b/go.mod index 7e07356ca..d6f439559 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/kaspanet/kaspad go 1.16 require ( + github.com/btcsuite/btcutil v1.0.2 github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd github.com/btcsuite/winsvc v1.0.0 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index 1e3d1b39f..a74d9ee2f 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,22 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= @@ -39,6 +49,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= @@ -47,6 +58,7 @@ github.com/kaspanet/go-muhash v0.0.4 h1:CQrm1RTJpQy+h4ZFjj9qq42K5fmA5QTGifzb47p4 github.com/kaspanet/go-muhash v0.0.4/go.mod h1:10bPW5mO1vNHPSejaAh9ZTtLZE16jzEvgaP7f3Q5s/8= github.com/kaspanet/go-secp256k1 v0.0.5 h1:WQqb65tyr8amsBkj337BVH3PTVWCrmufb68aTGpK3mM= github.com/kaspanet/go-secp256k1 v0.0.5/go.mod h1:cFbxhxKkxqHX5eIwUGKARkph19PehipDPJejWB+H0jM= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -57,7 +69,9 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 h1:gzMM0EjIYiRmJI3+jBdFuoynZlpxa2JQZsolKu09BXo= golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -69,6 +83,7 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -78,6 +93,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=