mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-05 21:56:50 +00:00
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:
parent
c28366eb50
commit
fa16c30cf3
49
cmd/kaspawallet/libkaspawallet/bip32/base58/alphabet.go
Normal file
49
cmd/kaspawallet/libkaspawallet/bip32/base58/alphabet.go
Normal 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,
|
||||
}
|
75
cmd/kaspawallet/libkaspawallet/bip32/base58/base58.go
Normal file
75
cmd/kaspawallet/libkaspawallet/bip32/base58/base58.go
Normal 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)
|
||||
}
|
98
cmd/kaspawallet/libkaspawallet/bip32/base58/base58_test.go
Normal file
98
cmd/kaspawallet/libkaspawallet/bip32/base58/base58_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
52
cmd/kaspawallet/libkaspawallet/bip32/base58/base58check.go
Normal file
52
cmd/kaspawallet/libkaspawallet/bip32/base58/base58check.go
Normal 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
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
17
cmd/kaspawallet/libkaspawallet/bip32/base58/cov_report.sh
Normal file
17
cmd/kaspawallet/libkaspawallet/bip32/base58/cov_report.sh
Normal 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
|
29
cmd/kaspawallet/libkaspawallet/bip32/base58/doc.go
Normal file
29
cmd/kaspawallet/libkaspawallet/bip32/base58/doc.go
Normal 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
|
71
cmd/kaspawallet/libkaspawallet/bip32/base58/example_test.go
Normal file
71
cmd/kaspawallet/libkaspawallet/bip32/base58/example_test.go
Normal 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
|
||||
}
|
46
cmd/kaspawallet/libkaspawallet/bip32/bip32.go
Normal file
46
cmd/kaspawallet/libkaspawallet/bip32/bip32.go
Normal 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()
|
||||
}
|
420
cmd/kaspawallet/libkaspawallet/bip32/bip32_test.go
Normal file
420
cmd/kaspawallet/libkaspawallet/bip32/bip32_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
152
cmd/kaspawallet/libkaspawallet/bip32/child_key_derivation.go
Normal file
152
cmd/kaspawallet/libkaspawallet/bip32/child_key_derivation.go
Normal 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
|
||||
}
|
108
cmd/kaspawallet/libkaspawallet/bip32/extended_key.go
Normal file
108
cmd/kaspawallet/libkaspawallet/bip32/extended_key.go
Normal 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)
|
||||
}
|
58
cmd/kaspawallet/libkaspawallet/bip32/hash.go
Normal file
58
cmd/kaspawallet/libkaspawallet/bip32/hash.go
Normal 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)
|
||||
}
|
60
cmd/kaspawallet/libkaspawallet/bip32/path.go
Normal file
60
cmd/kaspawallet/libkaspawallet/bip32/path.go
Normal 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
|
||||
}
|
148
cmd/kaspawallet/libkaspawallet/bip32/serialization.go
Normal file
148
cmd/kaspawallet/libkaspawallet/bip32/serialization.go
Normal 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
|
||||
}
|
137
cmd/kaspawallet/libkaspawallet/bip32/version.go
Normal file
137
cmd/kaspawallet/libkaspawallet/bip32/version.go
Normal 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
|
||||
}
|
@ -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
1
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
|
||||
|
16
go.sum
16
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=
|
||||
|
Loading…
x
Reference in New Issue
Block a user