UNPKG

5.91 kBPlain TextView Raw
1/**
2 * @hidden
3 */
4
5/**
6 */
7import * as common from './common';
8import * as bitcoin from '@bitgo/utxo-lib';
9import { V1Network } from './v2/types';
10import { InvalidKeyPathError } from './errors';
11const ecurve = require('ecurve');
12const curve = ecurve.getCurveByName('secp256k1');
13const BigInteger = require('bigi');
14const createHmac = require('create-hmac');
15
16let secp256k1: typeof import('secp256k1') | undefined;
17
18try {
19 secp256k1 = require('secp256k1');
20} catch (err) {
21 console.log('running without secp256k1 acceleration');
22}
23
24export function getNetwork(network?: V1Network): bitcoin.Network {
25 network = network || common.getNetwork();
26 return bitcoin.networks[network];
27}
28
29export function makeRandomKey(): bitcoin.ECPair {
30 return bitcoin.ECPair.makeRandom({ network: getNetwork() });
31}
32
33function getKey(network?: bitcoin.Network): bitcoin.ECPair {
34 network = network || getNetwork();
35 const k = this.keyPair;
36 const result = new bitcoin.ECPair(k.d, k.d ? null : k.Q, { network: network, compressed: k.compressed });
37 // Creating Q from d takes ~25ms, so if it's not created, use native bindings to pre-compute
38 if (!result.__Q && secp256k1) {
39 result.__Q = ecurve.Point.decodeFrom(curve, Buffer.from(secp256k1.publicKeyCreate(k.d.toBuffer(32), false)));
40 }
41 return result;
42}
43
44bitcoin.HDNode.prototype.getKey = getKey;
45
46/**
47 * Given a key and a path, derive the child key.
48 * @param {bitcoin.HDNode} userKey
49 * @param {string} path
50 * @returns {bitcoin.HDNode}
51 */
52export function deriveKeyByPath(userKey: bitcoin.HDNode, path: string): bitcoin.HDNode {
53 let key = userKey;
54 let splitPath = path.split('/');
55 // if a key path starts with "m", it is the path for a master node. derivePath() is used specifically for
56 // deriving master node and we can call it directly.
57 if (splitPath[0] === 'm') {
58 key = userKey.derivePath(path);
59 } else {
60 // if the path does not start with "m", it typically looks like "/x/y/...", and the splitPath
61 // would look like ['', 'x', 'y',...], and we need to get ride of the empty string at index 0.
62 // Then we continue deriving the child by calling derive() on the new child at each subsequent level.
63 splitPath = splitPath.slice(1);
64 for (const p of splitPath) {
65 const index = parseInt(p, 10);
66 if (isNaN(index) || index.toString() != p) {
67 throw new InvalidKeyPathError(path);
68 }
69 key = key.derive(index);
70 }
71 }
72 return key;
73}
74
75/**
76 * Derive a child HDNode from a parent HDNode and index. Uses secp256k1 to speed
77 * up public key derivations by 100x vs. bitcoinjs-lib implementation.
78 *
79 * @param {HDNode} hdnode parent HDNode
80 * @param {Number} index child index
81 * @returns {HDNode} derived HDNode
82 */
83function deriveFast(hdnode: bitcoin.HDNode, index: number): bitcoin.HDNode {
84 // no fast path for private key derivations -- delegate to standard method
85 if (!secp256k1 || hdnode.keyPair.d) {
86 return hdnode.derive(index);
87 }
88
89 const isHardened = index >= bitcoin.HDNode.HIGHEST_BIT;
90 if (isHardened) {
91 throw new Error('cannot derive hardened key from public key');
92 }
93
94 const indexBuffer = Buffer.alloc(4);
95 indexBuffer.writeUInt32BE(index, 0);
96
97 // data = serP(point(kpar)) || ser32(index)
98 // = serP(Kpar) || ser32(index)
99 const data = Buffer.concat([
100 hdnode.keyPair.getPublicKeyBuffer(),
101 indexBuffer,
102 ]);
103
104 const I = createHmac('sha512', hdnode.chainCode).update(data).digest();
105 const IL = I.slice(0, 32);
106 const IR = I.slice(32);
107
108 const pIL = BigInteger.fromBuffer(IL);
109
110 // In case parse256(IL) >= n, proceed with the next value for i
111 if (pIL.compareTo(curve.n) >= 0) {
112 return deriveFast(hdnode, index + 1);
113 }
114
115 // Private parent key -> private child key
116 // Ki = point(parse256(IL)) + Kpar
117 // = G*IL + Kpar
118
119 // The expensive op is the point multiply -- use secp256k1 lib to do that
120 const Ki = ecurve.Point.decodeFrom(curve, Buffer.from(secp256k1.publicKeyCreate(IL, false))).add(hdnode.keyPair.Q);
121
122 // In case Ki is the point at infinity, proceed with the next value for i
123 if (curve.isInfinity(Ki)) {
124 return deriveFast(hdnode, index + 1);
125 }
126
127 const keyPair = new bitcoin.ECPair(null, Ki, { network: hdnode.keyPair.network });
128 const hd = new bitcoin.HDNode(keyPair, IR);
129
130 hd.depth = hdnode.depth + 1;
131 hd.index = index;
132 hd.parentFingerprint = hdnode.getFingerprint().readUInt32BE(0);
133
134 return hd;
135}
136
137/**
138 * Derive a BIP32 path, given a root key
139 * We cache keys at each level of hierarchy we derive, to avoid re-deriving (approx 25ms per derivation)
140 * @param rootKey key to derive off
141 * @returns {*} function which can be used to derive a new HDNode from the root HDNode on a given path
142 */
143export function hdPath(rootKey): { derive: (path: string) => bitcoin.HDNode; deriveKey: (path: string) => bitcoin.ECPair; } {
144 const cache = {};
145 const derive = function(path: string): bitcoin.HDNode {
146 const components = path.split('/').filter(function(c) {
147 return c !== '';
148 });
149 // strip any extraneous / characters
150 path = components.join('/');
151 if (cache[path]) {
152 return cache[path];
153 }
154 const len = components.length;
155 if (len === 0 || len === 1 && components[0] === 'm') {
156 return rootKey;
157 }
158 const parentPath = components.slice(0, len - 1).join('/');
159 const parentKey = derive(parentPath);
160 const el = components[len - 1];
161
162 let hardened = false;
163 if (el[el.length - 1] === "'") {
164 hardened = true;
165 }
166 const index = parseInt(el, 10);
167 let derived;
168 if (hardened) {
169 derived = parentKey.deriveHardened(index);
170 } else {
171 derived = deriveFast(parentKey, index);
172 }
173 cache[path] = derived;
174 return derived;
175 };
176
177 function deriveKey(path: string): bitcoin.ECPair {
178 const hdNode = derive(path);
179 return hdNode.keyPair;
180 }
181
182 return {
183 derive: derive,
184 deriveKey: deriveKey,
185 };
186}