mimblewimble / grin-wallet

Grin Wallet
Apache License 2.0
185 stars 134 forks source link

wallet shows incorrect BIP32 derivation paths #663

Open marekyggdrasil opened 2 years ago

marekyggdrasil commented 2 years ago

Describe the bug

The key derivation path shown after running grin-wallet account is not correct.

To Reproduce Steps to reproduce the behavior:

  1. Create bunch of accounts
  2. Run grin-wallet account to list them, note the derivation paths
  3. Derive the slatepack addresses from those exact paths using GRIN++ or mimblewimble-py
  4. Observe they do not match

Expected behavior

When I run

$ grin-wallet account
Password: 

____ Wallet Accounts ____

 Name    | Parent BIP-32 Derivation Path 
---------+-------------------------------
 alice   | m/1/0 
 bob     | m/2/0 
 default | m/0/0 

Command 'account' completed successfully

correct paths should be

$ grin-wallet account
Password: 

____ Wallet Accounts ____

 Name    | Parent BIP-32 Derivation Path 
---------+-------------------------------
 alice   | m/1/1/0
 bob     | m/2/1/0
 default | m/0/1/0

Command 'account' completed successfully

You can get the test data here

Screenshots If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

$ grin-wallet -V
grin-wallet 5.0.3

Additional context Add any other context about the problem here.

davidtavarez commented 2 years ago

Here is some python code:

import hmac
from hashlib import blake2b, sha512

import bip32
from bip32 import BIP32
from bip_utils import Bech32Encoder
from mnemonic import Mnemonic
from nacl import bindings

mnemo = Mnemonic("english")
words = "sign interest obtain raw window monster jump bring nice crunch toward grunt prosper recycle sphere battle mother fold reject velvet emotion similar romance govern"
seed = mnemo.to_seed(mnemonic=words, passphrase="")

# I AM VOLDEMORT
m = hmac.new("IamVoldemort".encode("utf8"), digestmod=sha512)
m.update(mnemo.to_entropy(words))
secret = m.digest()

bip32 = BIP32(chaincode=secret[32:], privkey=secret[:32])

# get the m/0/1/0 key
chain, sk_der = bip32.get_extended_privkey_from_path("m/0/1/0")
# compute the blake2 hash of that key and that is ed25519 private key
sk_der_blake = blake2b(sk_der, digest_size=32).digest()
# get the ed25519 public key from it
pk, sk = bindings.crypto_sign_seed_keypair(sk_der_blake)
# compute the slatepack address
slatepack_address = Bech32Encoder.Encode("grin", pk)

print(f"slatepack at m/0/1/0:\t{slatepack_address}")

assert (
    slatepack_address
    == "grin14kgku7l5x6te3arast3p59zk4rteznq2ug6kmmypf2d6z8md76eqg3su35"
)

# get the m/0/2/0 key
chain, sk_der = bip32.get_extended_privkey_from_path("m/1/1/0")
# compute the blake2 hash of that key and that is ed25519 private key
sk_der_blake = blake2b(sk_der, digest_size=32).digest()
# get the ed25519 public key from it
pk, sk = bindings.crypto_sign_seed_keypair(sk_der_blake)
# compute the slatepack address
slatepack_address = Bech32Encoder.Encode("grin", pk)

print(f"slatepack at m/1/1/0:\t{slatepack_address}")

assert (
    slatepack_address
    == "grin1uqan8sf49yf0369ezef9jhl25jll9fc8xc5wjkcg0w6nv6v85v2sp4wgwy"
)

# get the m/0/3/0 key
chain, sk_der = bip32.get_extended_privkey_from_path("m/2/1/0")
# compute the blake2 hash of that key and that is ed25519 private key
sk_der_blake = blake2b(sk_der, digest_size=32).digest()
# get the ed25519 public key from it
pk, sk = bindings.crypto_sign_seed_keypair(sk_der_blake)
# compute the slatepack address
slatepack_address = Bech32Encoder.Encode("grin", pk)

print(f"slatepack at m/2/1/0:\t{slatepack_address}")

assert (
    slatepack_address
    == "grin1guszgjsjlt9vrppu42l03xx080epzzvse5nev3nvdh632explc0sj8ylja"
)

Output:

slatepack at m/0/1/0:   grin14kgku7l5x6te3arast3p59zk4rteznq2ug6kmmypf2d6z8md76eqg3su35
slatepack at m/1/1/0:   grin1uqan8sf49yf0369ezef9jhl25jll9fc8xc5wjkcg0w6nv6v85v2sp4wgwy
slatepack at m/2/1/0:   grin1guszgjsjlt9vrppu42l03xx080epzzvse5nev3nvdh632explc0sj8ylja