Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement bip32 #1676

Merged
merged 30 commits into from
Apr 28, 2021
Merged

Implement bip32 #1676

merged 30 commits into from
Apr 28, 2021

Conversation

someone235
Copy link
Collaborator

No description provided.

@codecov
Copy link

codecov bot commented Apr 12, 2021

Codecov Report

Merging #1676 (2b6abdf) into v0.11.0-dev (c28366e) will increase coverage by 0.15%.
The diff coverage is 67.30%.

❗ Current head 2b6abdf differs from pull request most recent head e91d7ff. Consider uploading reports for the commit e91d7ff to get more accurate results
Impacted file tree graph

@@               Coverage Diff               @@
##           v0.11.0-dev    #1676      +/-   ##
===============================================
+ Coverage        59.14%   59.29%   +0.15%     
===============================================
  Files              556      565       +9     
  Lines            22035    22399     +364     
===============================================
+ Hits             13032    13281     +249     
- Misses            6930     7008      +78     
- Partials          2073     2110      +37     
Impacted Files Coverage Δ
...awallet/libkaspawallet/bip32/base58/base58check.go 0.00% <0.00%> (ø)
cmd/kaspawallet/libkaspawallet/bip32/bip32.go 15.78% <15.78%> (ø)
.../kaspawallet/libkaspawallet/bip32/base58/base58.go 53.33% <53.33%> (ø)
...d/kaspawallet/libkaspawallet/bip32/extended_key.go 65.11% <65.11%> (ø)
...allet/libkaspawallet/bip32/child_key_derivation.go 68.67% <68.67%> (ø)
cmd/kaspawallet/libkaspawallet/bip32/path.go 73.33% <73.33%> (ø)
cmd/kaspawallet/libkaspawallet/bip32/hash.go 78.94% <78.94%> (ø)
.../kaspawallet/libkaspawallet/bip32/serialization.go 84.94% <84.94%> (ø)
cmd/kaspawallet/libkaspawallet/bip32/version.go 95.83% <95.83%> (ø)
...s/processes/difficultymanager/difficultymanager.go 81.33% <100.00%> (+0.51%) ⬆️
... and 13 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c28366e...e91d7ff. Read the comment docs.

@@ -0,0 +1,41 @@
package bip32
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I don't think we want the word "bip32" in our code, this is good time to think about a more general name, that does not reference other coins.
  2. Sounds like this is code that is more general then libkaspawallet, I'd place it on the same level as the bech32 package (a.k.a. under "util")

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This is an implementation of bip32. I don't see any problem with this name.
  2. bip32 is related only to wallets, so I think this place is ok

func TestBIP32SpecVectors(t *testing.T) {
type testPath struct {
path string
extPub string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use full words for names.


type testVector struct {
seed string
pathes []testPath
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: paths


import "github.com/pkg/errors"

var BitcoinMainnetPrivate = [4]byte{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it needed? My guess is only to verify against bitcoin-provided test vectors.
As such, I'd rather if this was only in test code.

EDIT: Read some more of the code, now I understand this is not as trivial as I thought.
As such, I'd rather not have bitcoin vectors at all.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an implementation of the bip32 spec, so it makes sense to use the test vectors that are specified by the spec

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, but this should be in test code only

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but then how can we use toPublicVersion?

t.Fatalf("DecodeString: %+v", err)
}

masterKey, err := NewMaster(seed, BitcoinMainnetPrivate)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a few test vectors with Kaspa master

return sha2.Sum(nil)
}

func validateChecksum(data []byte) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment that explains what's the expected structure of data (I'd also rename it to dataWithChecksum)


func parsePath(pathString string) (*path, error) {
parts := strings.Split(pathString, "/")
if len(parts) == 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't a path be just "m" or "M"?
It is being used this way, for example, here: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#full-wallet-sharing-m

Not sure it's even going to happen in the context of this function, but as a general-purpose function I think just "m" should be supported

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you are right. I think this condition can never happen

}

func parseIndex(indexString string) (uint32, error) {
const isHardenedSuffix = "'"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really annoying that the spec uses the unprintable H while everyone in the world uses '.

As such, I'd make this suffix much more visible (either in some readme, or somewhere else where it's clear what this means).

checkSumLen

func DeserializeExtendedPrivateKey(extKeyString string) (*ExtendedKey, error) {
serializedBytes := base58.Decode(extKeyString)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to encode this in base58?
And even worse - using btcutil library for doing that...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of the spec. I can copy the library though

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to copy the library then.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you missed copying the library.

}

var KaspaMainnetPrivate = [4]byte{
0x01,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want something human-recognizable here.

@stasatdaglabs stasatdaglabs changed the base branch from v0.10.0-dev to v0.11.0-dev April 20, 2021 10:41
Comment on lines 18 to 22
var iL, iR [32]byte
copy(iL[:], I[:32])
copy(iR[:], I[32:])

privateKey, err := secp256k1.DeserializeECDSAPrivateKeyFromSlice(iL[:])
Copy link
Member

@elichai elichai Apr 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit,

var iL secp256k1.SerializedPrivateKey
copy(iL[:], I[:32])
privateKey, err := secp256k1.DeserializeECDSAPrivateKey(&iL)

is a little more efficient

publicKey *secp256k1.ECDSAPublicKey
Version [4]byte
Depth uint8
Fingerprint [4]byte
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "ParentFingerprint" is more correct

return &kCopy, nil
}

func calcI(extKey *ExtendedKey, i uint32) ([]byte, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe make this a function on ExtendedKey?

Comment on lines 124 to 150
if isHardened(i) {
if !extKey.IsPrivate() {
return nil, errors.Errorf("Cannot calculate hardened child for public key")
}

mac := newHMACWriter(extKey.ChainCode[:])
mac.InfallibleWrite([]byte{0x00})
mac.InfallibleWrite(extKey.PrivateKey.Serialize()[:])
mac.InfallibleWrite(ser32(i))
return mac.Sum(nil), nil
}

mac := newHMACWriter(extKey.ChainCode[:])
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(ser32(i))
return mac.Sum(nil), nil
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, I find that writing it like this allows to easily see the difference between hardened and non-hardened (and reduces code duplication):

func calcI(extKey *ExtendedKey, 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(ser32(i))
	return mac.Sum(nil), nil
}

return fingerPrintFromPoint(point)
}

func fingerPrintFromPoint(point *secp256k1.ECDSAPublicKey) ([4]byte, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, maybe make Fingerprint() a public function on ExtendedKey? I think users will want it (especially as ExtendedKey already exports the parent's fingerprint publicly)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why users may need it. For now I'll keep it private

return fingerprint, nil
}

func hash160(data []byte) []byte {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this to hash.go

Comment on lines 34 to 39
sha1 := sha256.New()
sha2 := sha256.New()
sha1.Write(data)
sha2.Write(sha1.Sum(nil))
return sha2.Sum(nil)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, you can do:

inner := sha256.Sum256(data)
outer := sha256.Sum256(inner[:])
return outer[:]


func (extKey *ExtendedKey) serialize() ([]byte, error) {
var serialized [extendedKeySerializationLen]byte
copy(serialized[:versionSerializationLen], extKey.Version[:])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little weird that this is the only place that you specify the end of the slice

Comment on lines 114 to 118
serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen+childNumberSerializationLen+chainCodeSerializationLen] = 0
copy(
serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen+childNumberSerializationLen+chainCodeSerializationLen+1:],
extKey.PrivateKey.Serialize()[:],
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, This is too long for my IDE

Comment on lines +136 to +141
checkSum := doubleSha256(serialized[:len(serialized)-checkSumLen])
copy(
serialized[versionSerializationLen+depthSerializationLen+fingerprintSerializationLen+childNumberSerializationLen+chainCodeSerializationLen+keySerializationLen:],
checkSum,
)
return serialized[:], nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, I think the checksum is more part of the base58 encoding than part of the serialization,
but serialize is currently a private function so we don't care about the distinction (it also saves use from another allocation+copy)

elichai
elichai previously approved these changes Apr 27, 2021
"github.com/pkg/errors"
)

type ExtendedKey struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... oh well I guess.

pathes: []testPath{
seed: "000102030405060708090a0b0c0d0e0f",
version: BitcoinMainnetPrivate,
paths: []testPath{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have no test vectors that check public key deriviation (a.k.a capital M paths), maybe write some such as well?
(Or alternatively, for each test-vector here, also test what happens when you replace m with M)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestExtendedKey_DeriveFromPath checks that.

t.Fatalf("Test (%d, %d): deserializing and serializing the ext pub didn't preserve the data", i, j)
}
}
}
}

func TestExtendedPublicKey_Path(t *testing.T) {
// TestExtendedKey_DeriveFromPath checks that path that derive from extended public key and extended
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suspected typo: derive from extended public key and extended public key

I think you want to replace one "public" to "private"

checkSumLen

func DeserializeExtendedPrivateKey(extKeyString string) (*ExtendedKey, error) {
serializedBytes := base58.Decode(extKeyString)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you missed copying the library.

var BitcoinMainnetPublic = [4]byte{
0x04,
0x88,
0xb2,
0x1e,
}

// KaspaMainnetPrivate is the version that is used for
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we maybe want to define versions for kaspa testnet/mainnet/simnet as well?

// KaspaTestnetPrivate is the version that is used for
// kaspa testnet bip32 public extended keys.
var KaspaTestnetPrivate = [4]byte{
0x03,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use either decimal or hex for these values, but not a mixture. (This also refers to the fact that the previous versions were all hexa, and the new ones are mostly decimal).

Also, maybe write in the comment what this encodes to? (a.k.a. kpriv/tpub/etc)

@svarogg svarogg merged commit fa16c30 into v0.11.0-dev Apr 28, 2021
@svarogg svarogg deleted the bip32 branch April 28, 2021 12:27
@dravenk dravenk mentioned this pull request May 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants