-
Notifications
You must be signed in to change notification settings - Fork 286
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Cherrypick child key derivation (#140) * Cherrypick child_key_derivation from SwingbyProtocol https://github.com/SwingbyProtocol/tss-lib/pull/6/files#diff-e663957d1112b8c89bb7a782fe1cebe0d5e4d84a17861ae5af5cc0b59d1dbf56 * Add serialization to child key derivation add test case from github.com/btcsuite/hdkeychain * Making ec as parameter in key_derivation_util * Add version string to extendedkey, to comply with BIP32 * specify curve in NewExtendedKey * pass curve in DeriveChildKey * amend the HD cherry pick not to break the existing API of NewLocalParty * FillBytes not available in go 1.13 Fix test using FillBytes Co-authored-by: ycen <[email protected]> Co-authored-by: FitzLu <[email protected]>
- Loading branch information
1 parent
bde1ac3
commit cbfa6cf
Showing
9 changed files
with
608 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,257 @@ | ||
// Copyright © Swingby | ||
|
||
package ckd | ||
|
||
import ( | ||
"bytes" | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"crypto/hmac" | ||
"crypto/sha256" | ||
"crypto/sha512" | ||
"encoding/binary" | ||
"errors" | ||
"hash" | ||
"math/big" | ||
|
||
"github.com/bnb-chain/tss-lib/common" | ||
"github.com/bnb-chain/tss-lib/crypto" | ||
"github.com/btcsuite/btcd/btcec" | ||
"github.com/btcsuite/btcutil/base58" | ||
"golang.org/x/crypto/ripemd160" | ||
) | ||
|
||
type ExtendedKey struct { | ||
ecdsa.PublicKey | ||
Depth uint8 | ||
ChildIndex uint32 | ||
ChainCode []byte // 32 bytes | ||
ParentFP []byte // parent fingerprint | ||
Version []byte | ||
} | ||
|
||
// For more information about child key derivation see https://github.com/binance-chain/tss-lib/issues/104 | ||
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki . | ||
// The functions below do not implement the full BIP-32 specification. As mentioned in the Jira ticket above, | ||
// we only use non-hardened derived keys. | ||
|
||
const ( | ||
|
||
// HardenedKeyStart hardened key starts. | ||
HardenedKeyStart = 0x80000000 // 2^31 | ||
|
||
// max Depth | ||
maxDepth = 1<<8 - 1 | ||
|
||
PubKeyBytesLenCompressed = 33 | ||
|
||
pubKeyCompressed byte = 0x2 | ||
|
||
serializedKeyLen = 78 | ||
|
||
// MinSeedBytes is the minimum number of bytes allowed for a seed to | ||
// a master node. | ||
MinSeedBytes = 16 // 128 bits | ||
|
||
// MaxSeedBytes is the maximum number of bytes allowed for a seed to | ||
// a master node. | ||
MaxSeedBytes = 64 // 512 bits | ||
) | ||
|
||
// Extended public key serialization, defined in BIP32 | ||
func (k *ExtendedKey) String() string { | ||
// version(4) || depth(1) || parentFP (4) || childinde(4) || chaincode (32) || key(33) || checksum(4) | ||
var childNumBytes [4]byte | ||
binary.BigEndian.PutUint32(childNumBytes[:], k.ChildIndex) | ||
|
||
serializedBytes := make([]byte, 0, serializedKeyLen+4) | ||
serializedBytes = append(serializedBytes, k.Version...) | ||
serializedBytes = append(serializedBytes, k.Depth) | ||
serializedBytes = append(serializedBytes, k.ParentFP...) | ||
serializedBytes = append(serializedBytes, childNumBytes[:]...) | ||
serializedBytes = append(serializedBytes, k.ChainCode...) | ||
pubKeyBytes := serializeCompressed(k.PublicKey.X, k.PublicKey.Y) | ||
serializedBytes = append(serializedBytes, pubKeyBytes...) | ||
|
||
checkSum := doubleHashB(serializedBytes)[:4] | ||
serializedBytes = append(serializedBytes, checkSum...) | ||
return base58.Encode(serializedBytes) | ||
} | ||
|
||
// NewExtendedKeyFromString returns a new extended key from a base58-encoded extended key | ||
func NewExtendedKeyFromString(key string, curve elliptic.Curve) (*ExtendedKey, error) { | ||
// version(4) || depth(1) || parentFP (4) || childinde(4) || chaincode (32) || key(33) || checksum(4) | ||
|
||
decoded := base58.Decode(key) | ||
if len(decoded) != serializedKeyLen+4 { | ||
return nil, errors.New("invalid extended key") | ||
} | ||
|
||
// Split the payload and checksum up and ensure the checksum matches. | ||
payload := decoded[:len(decoded)-4] | ||
checkSum := decoded[len(decoded)-4:] | ||
expectedCheckSum := doubleHashB(payload)[:4] | ||
if !bytes.Equal(checkSum, expectedCheckSum) { | ||
return nil, errors.New("invalid extended key") | ||
} | ||
|
||
// Deserialize each of the payload fields. | ||
version := payload[:4] | ||
depth := payload[4:5][0] | ||
parentFP := payload[5:9] | ||
childNum := binary.BigEndian.Uint32(payload[9:13]) | ||
chainCode := payload[13:45] | ||
keyData := payload[45:78] | ||
|
||
var pubKey ecdsa.PublicKey | ||
|
||
if c, ok := curve.(*btcec.KoblitzCurve); ok { | ||
// Ensure the public key parses correctly and is actually on the | ||
// secp256k1 curve. | ||
pk, err := btcec.ParsePubKey(keyData, c) | ||
if err != nil { | ||
return nil, err | ||
} | ||
pubKey = ecdsa.PublicKey(*pk) | ||
} else { | ||
px, py := elliptic.Unmarshal(curve, keyData) | ||
pubKey = ecdsa.PublicKey{ | ||
Curve: curve, | ||
X: px, | ||
Y: py, | ||
} | ||
} | ||
|
||
return &ExtendedKey{ | ||
PublicKey: pubKey, | ||
Depth: depth, | ||
ChildIndex: childNum, | ||
ChainCode: chainCode, | ||
ParentFP: parentFP, | ||
Version: version, | ||
}, nil | ||
} | ||
|
||
func doubleHashB(b []byte) []byte { | ||
first := sha256.Sum256(b) | ||
second := sha256.Sum256(first[:]) | ||
return second[:] | ||
} | ||
|
||
func calcHash(buf []byte, hasher hash.Hash) []byte { | ||
hasher.Write(buf) | ||
return hasher.Sum(nil) | ||
} | ||
|
||
func hash160(buf []byte) []byte { | ||
return calcHash(calcHash(buf, sha256.New()), ripemd160.New()) | ||
} | ||
|
||
func isOdd(a *big.Int) bool { | ||
return a.Bit(0) == 1 | ||
} | ||
|
||
// PaddedAppend append src to dst, if less than size padding 0 at start | ||
func paddedAppend(dst []byte, srcPaddedSize int, src []byte) []byte { | ||
return append(dst, paddedBytes(srcPaddedSize, src)...) | ||
} | ||
|
||
// PaddedBytes padding byte array to size length | ||
func paddedBytes(size int, src []byte) []byte { | ||
offset := size - len(src) | ||
tmp := src | ||
if offset > 0 { | ||
tmp = make([]byte, size) | ||
copy(tmp[offset:], src) | ||
} | ||
return tmp | ||
} | ||
|
||
// SerializeCompressed serializes a public key 33-byte compressed format | ||
func serializeCompressed(publicKeyX *big.Int, publicKeyY *big.Int) []byte { | ||
b := make([]byte, 0, PubKeyBytesLenCompressed) | ||
format := pubKeyCompressed | ||
if isOdd(publicKeyY) { | ||
format |= 0x1 | ||
} | ||
b = append(b, format) | ||
return paddedAppend(b, 32, publicKeyX.Bytes()) | ||
} | ||
|
||
func DeriveChildKeyFromHierarchy(indicesHierarchy []uint32, pk *ExtendedKey, mod *big.Int, curve elliptic.Curve) (*big.Int, *ExtendedKey, error) { | ||
var k = pk | ||
var err error | ||
var childKey *ExtendedKey | ||
mod_ := common.ModInt(mod) | ||
ilNum := big.NewInt(0) | ||
for index := range indicesHierarchy { | ||
ilNumOld := ilNum | ||
ilNum, childKey, err = DeriveChildKey(indicesHierarchy[index], k, curve) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
k = childKey | ||
ilNum = mod_.Add(ilNum, ilNumOld) | ||
} | ||
return ilNum, k, nil | ||
} | ||
|
||
// DeriveChildKey Derive a child key from the given parent key. The function returns "IL" ("I left"), per BIP-32 spec. It also | ||
// returns the derived child key. | ||
func DeriveChildKey(index uint32, pk *ExtendedKey, curve elliptic.Curve) (*big.Int, *ExtendedKey, error) { | ||
if index >= HardenedKeyStart { | ||
return nil, nil, errors.New("the index must be non-hardened") | ||
} | ||
if pk.Depth == maxDepth { | ||
return nil, nil, errors.New("cannot derive key beyond max depth") | ||
} | ||
|
||
cryptoPk, err := crypto.NewECPoint(curve, pk.X, pk.Y) | ||
if err != nil { | ||
common.Logger.Error("error getting pubkey from extendedkey") | ||
return nil, nil, err | ||
} | ||
|
||
pkPublicKeyBytes := serializeCompressed(pk.X, pk.Y) | ||
|
||
data := make([]byte, 37) | ||
copy(data, pkPublicKeyBytes) | ||
binary.BigEndian.PutUint32(data[33:], index) | ||
|
||
// I = HMAC-SHA512(Key = chainCode, Data=data) | ||
hmac512 := hmac.New(sha512.New, pk.ChainCode) | ||
hmac512.Write(data) | ||
ilr := hmac512.Sum(nil) | ||
il := ilr[:32] | ||
childChainCode := ilr[32:] | ||
ilNum := new(big.Int).SetBytes(il) | ||
|
||
if ilNum.Cmp(curve.Params().N) >= 0 || ilNum.Sign() == 0 { | ||
// falling outside of the valid range for curve private keys | ||
err = errors.New("invalid derived key") | ||
common.Logger.Error("error deriving child key") | ||
return nil, nil, err | ||
} | ||
|
||
deltaG := crypto.ScalarBaseMult(curve, ilNum) | ||
if deltaG.X().Sign() == 0 || deltaG.Y().Sign() == 0 { | ||
err = errors.New("invalid child") | ||
common.Logger.Error("error invalid child") | ||
return nil, nil, err | ||
} | ||
childCryptoPk, err := cryptoPk.Add(deltaG) | ||
if err != nil { | ||
common.Logger.Error("error adding delta G to parent key") | ||
return nil, nil, err | ||
} | ||
|
||
childPk := &ExtendedKey{ | ||
PublicKey: *childCryptoPk.ToECDSAPubKey(), | ||
Depth: pk.Depth + 1, | ||
ChildIndex: index, | ||
ChainCode: childChainCode, | ||
ParentFP: hash160(pkPublicKeyBytes)[:4], | ||
Version: pk.Version, | ||
} | ||
return ilNum, childPk, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// Copyright © 2019 Binance | ||
// | ||
// This file is part of Binance. The full Binance copyright notice, including | ||
// terms governing use, modification, and redistribution, is contained in the | ||
// file LICENSE at the root of the source code distribution tree. | ||
|
||
package ckd_test | ||
|
||
import ( | ||
"testing" | ||
|
||
. "github.com/bnb-chain/tss-lib/crypto/ckd" | ||
"github.com/btcsuite/btcd/btcec" | ||
) | ||
|
||
func TestPublicDerivation(t *testing.T) { | ||
// port from https://github.com/btcsuite/btcutil/blob/master/hdkeychain/extendedkey_test.go | ||
// The public extended keys for test vectors in [BIP32]. | ||
testVec1MasterPubKey := "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" | ||
testVec2MasterPubKey := "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB" | ||
|
||
tests := []struct { | ||
name string | ||
master string | ||
path []uint32 | ||
wantPub string | ||
}{ | ||
// Test vector 1 | ||
{ | ||
name: "test vector 1 chain m", | ||
master: testVec1MasterPubKey, | ||
path: []uint32{}, | ||
wantPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", | ||
}, | ||
{ | ||
name: "test vector 1 chain m/0", | ||
master: testVec1MasterPubKey, | ||
path: []uint32{0}, | ||
wantPub: "xpub68Gmy5EVb2BdFbj2LpWrk1M7obNuaPTpT5oh9QCCo5sRfqSHVYWex97WpDZzszdzHzxXDAzPLVSwybe4uPYkSk4G3gnrPqqkV9RyNzAcNJ1", | ||
}, | ||
{ | ||
name: "test vector 1 chain m/0/1", | ||
master: testVec1MasterPubKey, | ||
path: []uint32{0, 1}, | ||
wantPub: "xpub6AvUGrnEpfvJBbfx7sQ89Q8hEMPM65UteqEX4yUbUiES2jHfjexmfJoxCGSwFMZiPBaKQT1RiKWrKfuDV4vpgVs4Xn8PpPTR2i79rwHd4Zr", | ||
}, | ||
{ | ||
name: "test vector 1 chain m/0/1/2", | ||
master: testVec1MasterPubKey, | ||
path: []uint32{0, 1, 2}, | ||
wantPub: "xpub6BqyndF6rhZqmgktFCBcapkwubGxPqoAZtQaYewJHXVKZcLdnqBVC8N6f6FSHWUghjuTLeubWyQWfJdk2G3tGgvgj3qngo4vLTnnSjAZckv", | ||
}, | ||
{ | ||
name: "test vector 1 chain m/0/1/2/2", | ||
master: testVec1MasterPubKey, | ||
path: []uint32{0, 1, 2, 2}, | ||
wantPub: "xpub6FHUhLbYYkgFQiFrDiXRfQFXBB2msCxKTsNyAExi6keFxQ8sHfwpogY3p3s1ePSpUqLNYks5T6a3JqpCGszt4kxbyq7tUoFP5c8KWyiDtPp", | ||
}, | ||
{ | ||
name: "test vector 1 chain m/0/1/2/2/1000000000", | ||
master: testVec1MasterPubKey, | ||
path: []uint32{0, 1, 2, 2, 1000000000}, | ||
wantPub: "xpub6GX3zWVgSgPc5tgjE6ogT9nfwSADD3tdsxpzd7jJoJMqSY12Be6VQEFwDCp6wAQoZsH2iq5nNocHEaVDxBcobPrkZCjYW3QUmoDYzMFBDu9", | ||
}, | ||
|
||
// Test vector 2 | ||
{ | ||
name: "test vector 2 chain m", | ||
master: testVec2MasterPubKey, | ||
path: []uint32{}, | ||
wantPub: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", | ||
}, | ||
{ | ||
name: "test vector 2 chain m/0", | ||
master: testVec2MasterPubKey, | ||
path: []uint32{0}, | ||
wantPub: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", | ||
}, | ||
{ | ||
name: "test vector 2 chain m/0/2147483647", | ||
master: testVec2MasterPubKey, | ||
path: []uint32{0, 2147483647}, | ||
wantPub: "xpub6ASAVgeWMg4pmutghzHG3BohahjwNwPmy2DgM6W9wGegtPrvNgjBwuZRD7hSDFhYfunq8vDgwG4ah1gVzZysgp3UsKz7VNjCnSUJJ5T4fdD", | ||
}, | ||
{ | ||
name: "test vector 2 chain m/0/2147483647/1", | ||
master: testVec2MasterPubKey, | ||
path: []uint32{0, 2147483647, 1}, | ||
wantPub: "xpub6CrnV7NzJy4VdgP5niTpqWJiFXMAca6qBm5Hfsry77SQmN1HGYHnjsZSujoHzdxf7ZNK5UVrmDXFPiEW2ecwHGWMFGUxPC9ARipss9rXd4b", | ||
}, | ||
{ | ||
name: "test vector 2 chain m/0/2147483647/1/2147483646", | ||
master: testVec2MasterPubKey, | ||
path: []uint32{0, 2147483647, 1, 2147483646}, | ||
wantPub: "xpub6FL2423qFaWzHCvBndkN9cbkn5cysiUeFq4eb9t9kE88jcmY63tNuLNRzpHPdAM4dUpLhZ7aUm2cJ5zF7KYonf4jAPfRqTMTRBNkQL3Tfta", | ||
}, | ||
{ | ||
name: "test vector 2 chain m/0/2147483647/1/2147483646/2", | ||
master: testVec2MasterPubKey, | ||
path: []uint32{0, 2147483647, 1, 2147483646, 2}, | ||
wantPub: "xpub6H7WkJf547AiSwAbX6xsm8Bmq9M9P1Gjequ5SipsjipWmtXSyp4C3uwzewedGEgAMsDy4jEvNTWtxLyqqHY9C12gaBmgUdk2CGmwachwnWK", | ||
}, | ||
} | ||
|
||
tests: | ||
for i, test := range tests { | ||
extKey, err := NewExtendedKeyFromString(test.master, btcec.S256()) | ||
if err != nil { | ||
t.Errorf("NewKeyFromString #%d (%s): unexpected error "+ | ||
"creating extended key: %v", i, test.name, | ||
err) | ||
continue | ||
} | ||
|
||
for _, childNum := range test.path { | ||
var err error | ||
_, extKey, err = DeriveChildKey(childNum, extKey, btcec.S256()) | ||
if err != nil { | ||
t.Errorf("err: %v", err) | ||
continue tests | ||
} | ||
} | ||
|
||
pubStr := extKey.String() | ||
if pubStr != test.wantPub { | ||
t.Errorf("Derive #%d (%s): mismatched serialized "+ | ||
"public extended key -- got: %s, want: %s", i, | ||
test.name, pubStr, test.wantPub) | ||
continue | ||
} | ||
} | ||
} |
Oops, something went wrong.