bip39: (CRITICAL) Incorrect derivation for certain BIP39 keys, fund loss >:(

Test case (by luck this is the first one I generated, thankfully I cross-referenced with other tools. Not all mnemonics / root keys trigger this bug)

mnemonic: fruit wave dwarf banana earth journey tattoo true farm silk olive fence passphrase: banana

https://iancoleman.github.io/bip39/ derived first address: 17rxURoF96VhmkcEGCj5LNQkmN9HVhWb7F (also shared by Electrum)

Other clients derive a different address (Copay, BIP32JP, etc): 13EuKhffWkBE2KUwcbkbELZb1MpzbimJ3Y

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 28 (10 by maintainers)

Commits related to this issue

Most upvoted comments

I’ve extensively compared results across several libraries, and bcoin (https://github.com/bcoin-org/bcoin), bitcoinjs-lib (https://github.com/bitcoinjs/bitcoinjs-lib), libbtc (https://github.com/libbtc/libbtc), hdkeys (https://github.com/cryptocoinjs/hdkey), and many others, are all using correct BIP32 derivation.

The derivation in Copay (and anything using bitcore-lib) is incorrect. The bug is when there is a leading zero of the private key and the hash during derivation does not include the zero. The BIP32 specification states that the size of the private key is always 32 bytes before it’s hashed.

FWIW: Funds will still be recoverable, however it may be cumbersome to derive both sets of private keys for recovery for those derivations affected.

NBitcoin: 17rxURoF96VhmkcEGCj5LNQkmN9HVhWb7F @dangershony I used @Thashiznets implementation, but kind of changed lots of stuff, so you might need to check also.

new Mnemonic("fruit wave dwarf banana earth journey tattoo true farm silk olive fence")
.DeriveExtKey("banana")
.Derive(new KeyPath("m/44'/0'/0'/0/0"))
.Neuter()
.PubKey
.GetAddress(Network.Main).ToString()

Output

17rxURoF96VhmkcEGCj5LNQkmN9HVhWb7F 

BIP32JP now has more permanent fix in place.

  1. Asks user if they want to use old buggy code and if they don’t know what that means say no. (I assume most users just click cancel to any pop up they don’t understand or the x mark)
  2. global variable (only persists until the tab visits another site) stores which derivation they want to use.
  3. I ran tests, and it looks like on average the bug occurs around 1 / 256 hard key derivations (makes sense)

So in theory, any wallet that makes 256 accounts in Copay will likely have one account that is wrong compared to other implementations.

@Thashiznets we should thank you for deving bip39, you are welcome to join the Blockchain C# community on stratisplatform.slack.com

@thashiznets @nicolasdorier This is the implementation we are using in C# https://github.com/Thashiznets/BIP39.NET I will try to run this mnemonic on our framework.

https://medium.com/@alexberegszaszi/why-do-my-bip32-wallets-disagree-6f3254cc5846#.86inuifuq

The implementation in bitcore-lib has an issue with certain private keys. It drops the leading byte if it is a zero.

According to that post, the 17rxU... address would be correct (ie this tool, electrum, bip32.org are correct)

Bcoin seems to agree with iancoleman/bip39:

var bcoin = require('bcoin');
var seed, key, pub, addr;
seed = bcoin.hd.Mnemonic.fromPhrase('fruit wave dwarf banana earth journey tattoo true farm silk olive fence').toSeed('banana')
key = bcoin.hd.PrivateKey.fromSeed(seed);
pub = key.derivePath("m/44'/0'/0'/0/0").publicKey;
addr = new bcoin.keyring(pub);
console.log(addr);

Output:

{ network: 'main',
  witness: false,
  nested: false,
  publicKey: '02ee29857a40f81c39181dffac6937e65473d16c6274368e337d1ede2892036ae8',
  script: null,
  program: null,
  type: 'pubkeyhash',
  address: '17rxURoF96VhmkcEGCj5LNQkmN9HVhWb7F' }