1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | import * as common from './common';
|
8 | import * as bitcoin from '@bitgo/utxo-lib';
|
9 | import { V1Network } from './v2/types';
|
10 | import { InvalidKeyPathError } from './errors';
|
11 | const ecurve = require('ecurve');
|
12 | const curve = ecurve.getCurveByName('secp256k1');
|
13 | const BigInteger = require('bigi');
|
14 | const createHmac = require('create-hmac');
|
15 |
|
16 | let secp256k1: typeof import('secp256k1') | undefined;
|
17 |
|
18 | try {
|
19 | secp256k1 = require('secp256k1');
|
20 | } catch (err) {
|
21 | console.log('running without secp256k1 acceleration');
|
22 | }
|
23 |
|
24 | export function getNetwork(network?: V1Network): bitcoin.Network {
|
25 | network = network || common.getNetwork();
|
26 | return bitcoin.networks[network];
|
27 | }
|
28 |
|
29 | export function makeRandomKey(): bitcoin.ECPair {
|
30 | return bitcoin.ECPair.makeRandom({ network: getNetwork() });
|
31 | }
|
32 |
|
33 | function 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 |
|
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 |
|
44 | bitcoin.HDNode.prototype.getKey = getKey;
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 | export function deriveKeyByPath(userKey: bitcoin.HDNode, path: string): bitcoin.HDNode {
|
53 | let key = userKey;
|
54 | let splitPath = path.split('/');
|
55 |
|
56 |
|
57 | if (splitPath[0] === 'm') {
|
58 | key = userKey.derivePath(path);
|
59 | } else {
|
60 |
|
61 |
|
62 |
|
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 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 | function deriveFast(hdnode: bitcoin.HDNode, index: number): bitcoin.HDNode {
|
84 |
|
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 |
|
98 |
|
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 |
|
111 | if (pIL.compareTo(curve.n) >= 0) {
|
112 | return deriveFast(hdnode, index + 1);
|
113 | }
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 | const Ki = ecurve.Point.decodeFrom(curve, Buffer.from(secp256k1.publicKeyCreate(IL, false))).add(hdnode.keyPair.Q);
|
121 |
|
122 |
|
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 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 | export 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 |
|
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 | }
|