Implement bip32 (#1676)

* Implement bip32

* Unite private and public extended keys

* Change variable names

* Change test name and add comment

* Rename var name

* Rename ckd.go to child_key_derivation.go

* Rename ser32 -> serializeUint32

* Add PrivateKey method

* Rename Path -> DeriveFromPath

* Add comment to validateChecksum

* Remove redundant condition from parsePath

* Rename Fingerprint->ParentFingerprint

* Merge hardened and non-hardened paths in calcI

* Change fingerPrintFromPoint to method

* Move hash160 to hash.go

* Fix a bug in calcI

* Simplify doubleSha256

* Remove slice end bound

* Split long line

* Change KaspaMainnetPrivate/public to represent kprv/kpub

* Add comments

* Fix comment

* Copy base58 library to kaspad

* Add versions for all networks

* Change versions to hex

* Add comments

Co-authored-by: Svarog <feanorr@gmail.com>
Co-authored-by: Elichai Turkel <elichai.turkel@gmail.com>
This commit is contained in:
Ori Newman 2021-04-28 15:27:16 +03:00 committed by GitHub
parent c28366eb50
commit fa16c30cf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1641 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

1
go.mod
View File

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

16
go.sum
View File

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