-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathkeys.js
387 lines (330 loc) · 11 KB
/
keys.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
/* @flow */
// TODO: most of this code should be in blockstack.js
// Will remove most of this code once the wallet functionality is there instead.
const blockstack = require('blockstack')
const keychains = require('blockstack-keychains')
const bitcoin = require('bitcoinjs-lib')
const bip39 = require('bip39')
const crypto = require('crypto')
const c32check = require('c32check')
import {
getPrivateKeyAddress
} from './utils';
const IDENTITY_KEYCHAIN = 888
const BLOCKSTACK_ON_BITCOIN = 0
const APPS_NODE_INDEX = 0
const SIGNING_NODE_INDEX = 1
const ENCRYPTION_NODE_INDEX = 2
export const STRENGTH = 128; // 12 words
class IdentityAddressOwnerNode {
hdNode: Object
salt: string
constructor(ownerHdNode: Object, salt: string) {
this.hdNode = ownerHdNode
this.salt = salt
}
getNode() {
return this.hdNode
}
getSalt() {
return this.salt
}
getIdentityKey() {
return toPrivkeyHex(this.hdNode)
}
getIdentityKeyID() {
return this.hdNode.publicKey.toString('hex')
}
getAppsNode() {
return new AppsNode(this.hdNode.deriveHardened(APPS_NODE_INDEX), this.salt)
}
getAddress() {
return getPrivateKeyAddress(blockstack.config.network, this.getIdentityKey())
}
getEncryptionNode() {
return this.hdNode.deriveHardened(ENCRYPTION_NODE_INDEX)
}
getSigningNode() {
return this.hdNode.deriveHardened(SIGNING_NODE_INDEX)
}
}
// for portal versions before 2038088458012dcff251027ea23a22afce443f3b
class IdentityNode{
key : Object
constructor(key: Object) {
this.key = key;
}
getAddress() : string {
return getPrivateKeyAddress(blockstack.config.network, this.getSKHex())
}
getSKHex() : string {
return toPrivkeyHex(this.key)
}
}
const VERSIONS = {
"pre-v0.9" : (m, i) => { return getIdentityKeyPre09(m) },
"v0.9-v0.10" : (m, i) => { return getIdentityKey09to10(getMaster(m), i) },
"v0.10-current" : (m, i) => { return getIdentityKeyCurrent(getMaster(m), i) },
"current-btc" : (m, i) => { return getBTC(getMaster(m)) },
}
function getBTC(pK : Object) {
const BIP_44_PURPOSE = 44;
const BITCOIN_COIN_TYPE = 0;
const ACCOUNT_INDEX = 0;
return pK.deriveHardened(BIP_44_PURPOSE)
.deriveHardened(BITCOIN_COIN_TYPE)
.deriveHardened(ACCOUNT_INDEX).derive(0).derive(0);
}
function getIdentityNodeFromPhrase(phrase : string,
index : number,
version : string = "current"){
if (! (version in VERSIONS)){
throw new Error(`Key derivation version '${version}' not uspported`);
}
return VERSIONS[version](phrase, index);
}
function getIdentityKeyCurrent(pK : Object, index : number = 0){
return new IdentityNode(
pK.deriveHardened(IDENTITY_KEYCHAIN)
.deriveHardened(BLOCKSTACK_ON_BITCOIN)
.deriveHardened(index));
}
function getIdentityKey09to10(pK : Object, index : number = 0){
return new IdentityNode(
pK.deriveHardened(IDENTITY_KEYCHAIN)
.deriveHardened(BLOCKSTACK_ON_BITCOIN)
.deriveHardened(index)
.derive(0));
}
function toPrivkeyHex(k : Object) : string {
return `${k.privateKey.toString('hex')}01`;
}
function getIdentityKeyPre09(mnemonic : string) : IdentityNode {
// on browser branch, v09 was commit -- 848d1f5445f01db1e28cde4a52bb3f22e5ca014c
const pK = keychains.PrivateKeychain.fromMnemonic(mnemonic);
const identityKey = pK.privatelyNamedChild('blockstack-0');
const secret = identityKey.privateKey;
const keyPair = new bitcoin.ECPair(secret, false, {"network" :
bitcoin.networks.bitcoin});
return new IdentityNode({ keyPair });
}
function getMaster(mnemonic : string) {
const seed = bip39.mnemonicToSeed(mnemonic);
return bitcoin.bip32.fromSeed(seed);
}
// NOTE: legacy
function hashCode(string) {
let hash = 0
if (string.length === 0) return hash
for (let i = 0; i < string.length; i++) {
const character = string.charCodeAt(i)
hash = (hash << 5) - hash + character
hash = hash & hash
}
return hash & 0x7fffffff
}
export class AppNode {
hdNode: Object
appDomain: string
constructor(hdNode: Object, appDomain: string) {
this.hdNode = hdNode
this.appDomain = appDomain
}
getAppPrivateKey() {
return toPrivkeyHex(this.hdNode)
}
getAddress() {
return getPrivateKeyAddress(blockstack.config.network, toPrivkeyHex(this.hdNode))
}
}
export class AppsNode {
hdNode: Object
salt: string
constructor(appsHdNode: Object, salt: string) {
this.hdNode = appsHdNode
this.salt = salt
}
getNode() {
return this.hdNode
}
getAppNode(appDomain: string) {
const hash = crypto
.createHash('sha256')
.update(`${appDomain}${this.salt}`)
.digest('hex')
const appIndex = hashCode(hash)
const appNode = this.hdNode.deriveHardened(appIndex)
return new AppNode(appNode, appDomain)
}
toBase58() {
return this.hdNode.toBase58()
}
getSalt() {
return this.salt
}
}
export function getIdentityPrivateKeychain(masterKeychain: Object) {
return masterKeychain.deriveHardened(IDENTITY_KEYCHAIN).deriveHardened(BLOCKSTACK_ON_BITCOIN)
}
export function getIdentityPublicKeychain(masterKeychain: Object) {
return getIdentityPrivateKeychain(masterKeychain).neutered()
}
export function getIdentityOwnerAddressNode(
identityPrivateKeychain: Object, identityIndex: ?number = 0) {
if (identityPrivateKeychain.isNeutered()) {
throw new Error('You need the private key to generate identity addresses')
}
// const publicKeyHex = identityPrivateKeychain.keyPair.getPublicKeyBuffer().toString('hex')
const publicKeyHex = identityPrivateKeychain.publicKey.toString('hex')
const salt = crypto
.createHash('sha256')
.update(publicKeyHex)
.digest('hex')
return new IdentityAddressOwnerNode(identityPrivateKeychain.deriveHardened(identityIndex), salt)
}
export function deriveIdentityKeyPair(identityOwnerAddressNode: Object) {
const address = identityOwnerAddressNode.getAddress()
const identityKey = identityOwnerAddressNode.getIdentityKey()
const identityKeyID = identityOwnerAddressNode.getIdentityKeyID()
const appsNode = identityOwnerAddressNode.getAppsNode()
const keyPair = {
key: identityKey,
keyID: identityKeyID,
address,
appsNodeKey: appsNode.toBase58(),
salt: appsNode.getSalt()
}
return keyPair
}
/*
* Get the owner key information for a 12-word phrase, at a specific index.
* @network (object) the blockstack network
* @mnemonic (string) the 12-word phrase
* @index (number) the account index
* @version (string) the derivation version string
*
* Returns an object with:
* .privateKey (string) the hex private key
* .version (string) the version string of the derivation
* .idAddress (string) the ID-address
*/
export function getOwnerKeyInfo(network: Object,
mnemonic : string,
index : number,
version : string = 'v0.10-current') {
const identity = getIdentityNodeFromPhrase(mnemonic, index, version);
const addr = network.coerceAddress(identity.getAddress());
const privkey = identity.getSKHex();
return {
privateKey: privkey,
version: version,
index: index,
idAddress: `ID-${addr}`,
};
}
/*
* Get the payment key information for a 12-word phrase.
* @network (object) the blockstack network
* @mnemonic (string) the 12-word phrase
*
* Returns an object with:
* .privateKey (string) the hex private key
* .address (string) the address of the private key
*/
export function getPaymentKeyInfo(network: Object, mnemonic : string) {
const identityHDNode = getIdentityNodeFromPhrase(mnemonic, 0, 'current-btc');
const privkey = toPrivkeyHex(identityHDNode);
const addr = getPrivateKeyAddress(network, privkey);
return {
privateKey: privkey,
address: {
BTC: addr,
STACKS: c32check.b58ToC32(addr),
},
index: 0
};
}
/*
* Find the index of an ID address, given the mnemonic.
* Returns the index if found
* Returns -1 if not found
*/
export function findIdentityIndex(network: Object, mnemonic: string, idAddress: string, maxIndex: ?number = 16) {
if (!maxIndex) {
maxIndex = 16;
}
if (idAddress.substring(0,3) !== 'ID-') {
throw new Error('Not an identity address');
}
for (let i = 0; i < maxIndex; i++) {
const identity = getIdentityNodeFromPhrase(mnemonic, i, 'v0.10-current');
if (network.coerceAddress(identity.getAddress()) ===
network.coerceAddress(idAddress.slice(3))) {
return i;
}
}
return -1;
}
/*
* Get the Gaia application key from a 12-word phrase
* @network (object) the blockstack network
* @mmemonic (string) the 12-word phrase
* @idAddress (string) the ID-address used to sign in
* @appDomain (string) the application's Origin
*
* Returns an object with
* .keyInfo (object) the app key info with the current derivation path
* .privateKey (string) the app's hex private key
* .address (string) the address of the private key
* .legacyKeyInfo (object) the app key info with the legacy derivation path
* .privateKey (string) the app's hex private key
* .address (string) the address of the private key
*/
export function getApplicationKeyInfo(network: Object,
mnemonic : string,
idAddress: string,
appDomain: string,
idIndex: ?number) {
if (!idIndex) {
idIndex = -1;
}
if (idIndex < 0) {
idIndex = findIdentityIndex(network, mnemonic, idAddress);
if (idIndex < 0) {
throw new Error('Identity address does not belong to this keychain');
}
}
const masterKeychain = getMaster(mnemonic);
const identityPrivateKeychainNode = getIdentityPrivateKeychain(masterKeychain);
const identityOwnerAddressNode = getIdentityOwnerAddressNode(
identityPrivateKeychainNode, idIndex);
// legacy app key (will update later to use the longer derivation path)
const identityInfo = deriveIdentityKeyPair(identityOwnerAddressNode);
const appsNodeKey = identityInfo.appsNodeKey;
const salt = identityInfo.salt;
const appsNode = new AppsNode(bitcoin.bip32.fromBase58(appsNodeKey), salt);
// NOTE: we don't include the 'compressed' flag for app private keys, even though
// the app address is the hash of the compressed public key.
const appPrivateKey = appsNode.getAppNode(appDomain).getAppPrivateKey().substring(0,64);
const res = {
keyInfo: {
privateKey: 'TODO',
address: 'TODO',
},
legacyKeyInfo: {
privateKey: appPrivateKey,
address: getPrivateKeyAddress(network, `${appPrivateKey}01`)
},
ownerKeyIndex: idIndex
};
return res;
}
/*
* Extract the "right" app key
*/
export function extractAppKey(appKeyInfo: { keyInfo: { privateKey: string }, legacyKeyInfo: { privateKey : string } }) {
const appPrivateKey = (appKeyInfo.keyInfo.privateKey === 'TODO' || !appKeyInfo.keyInfo.privateKey ?
appKeyInfo.legacyKeyInfo.privateKey :
appKeyInfo.keyInfo.privateKey);
return appPrivateKey;
}