Closed SebastienGllmt closed 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()
}
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
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);
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.
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: