bitcoinjs / bip32

A BIP32 compatible library.
MIT License
181 stars 129 forks source link

Improve usage with hw-app-eth keys #56

Closed SebastienGllmt closed 2 years ago

SebastienGllmt commented 2 years ago

Background

In hw-app-eth, to export a key, you're given the following interface:

(path: string): Promise<{publicKey: string, address: string, chainCode?: string}>

This is awkward to deal with because you're given the publicKey and not the extended public key (which is what is needed by libraries like this one). One way people tackle this is by rewriting the logic to calculate the extended key which is not ideal either.

Why can't we solve this with this library currently?

There shouldn't be a need for users to have to copy-paste logic to recalculate the fingerprint needed for the extended public key because this library already knows how to do this. However, this library doesn't make public the right constructor to able able to inject the proper values for depth, index and parent fingerprint and instead only providers these constructors.

Solution

The solution, I believe, is to do the following:

  1. Expose the crypto.ts file to users (currently it's not part of bip32.ts)
  2. Expose the BIP32Interface constructor (or an equivalent static function)
junderw commented 2 years ago

Hello.

The whole point of the base58 xpub/xprv format is to provide a standard way of importing and exporting BIP32 keys... so unfortunately, modifying the library extensively to make up for the UX problems of another library is not in scope.

That said. You can just create a bip32 object from the parent and use the getter to calculate the fingerprint.

import * as ecc from 'tiny-secp256k1';
import { BIP32Factory } from 'bip32';
const bip32 = BIP32Factory(ecc);

const lastIndex = (pathArray: string[]): number => {
    const last = pathArray[pathArray.length - 1];
    const index = parseInt(last.replace('\'', ''));

    return last.slice(-1) === '\'' ? index | 0x80000000 : index
}

interface XKey {
    publicKey: string;
    chainCode: string;
}

const createXPUB = (path: string, child: XKey, parent: XKey): string => {
    const pathArray = path.split('/');
    const pkChild = Buffer.from(ecc.pointCompress(Buffer.from(child.publicKey, 'hex')));
    const pkParent = Buffer.from(ecc.pointCompress(Buffer.from(parent.publicKey, 'hex')));
    // parent.chainCode can be anything, since fingerprint getter doesn't touch chainCode
    const parentHdnode = bip32.fromPublicKey(pkParent, Buffer.from(parent.chainCode, 'hex'));

    const hdnode = bip32.fromPublicKey(pkChild, Buffer.from(child.chainCode, 'hex'));
    // @ts-ignore
    hdnode.__PARENT_FINGERPRINT = parentHdnode.fingerprint.readUInt32BE(0);
    // "m/42" would be length === 2, but depth starts with m at depth 0, so 42 index is depth 1.
    // @ts-ignore
    hdnode.__DEPTH = pathArray.length - 1;
    // @ts-ignore
    hdnode.__INDEX = lastIndex(pathArray);

    return hdnode.toBase58()
}
SebastienGllmt commented 2 years ago

hdnode.parentFingerprint = parentHdnode.fingerprint.readUInt32BE(0); // "m/42" would be length === 2, but depth starts with m at depth 0, so 42 index is depth 1. hdnode.depth = pathArray.length - 1; hdnode.index = lastIndex(pathArray);

This code snippet is exactly what I want to do, but I cannot because parentFingerprint, depth and index are only exposed as getters (no setters) and there is no publicly exposed constructor I can use to manually set them either

Trying to run your snippet will give the error

TypeError: Cannot set property parentFingerprint of #<BIP32> which has only a getter
junderw commented 2 years ago

Then just use ts-ignore (not needed if using JS)

    // @ts-ignore
    hdnode.__PARENT_FINGERPRINT = parentHdnode.fingerprint.readUInt32BE(0);
    // "m/42" would be length === 2, but depth starts with m at depth 0, so 42 index is depth 1.
    // @ts-ignore
    hdnode.__DEPTH = pathArray.length - 1;
    // @ts-ignore
    hdnode.__INDEX = lastIndex(pathArray);
junderw commented 2 years ago

Importing/exporting BIP32 from one library to another should be done with xpub/xprv strings.

I am not going to add foot guns to this library's public API just because some other library doesn't export properly.