-
Notifications
You must be signed in to change notification settings - Fork 315
/
Copy pathkeys.ts
300 lines (271 loc) · 8.71 KB
/
keys.ts
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
// TODO: most of this code should be in blockstack.js
// Will remove most of this code once the wallet functionality is there instead.
// eslint-disable-next-line @typescript-eslint/no-var-requires
const c32check = require('c32check');
import { HDKey } from '@scure/bip32';
import * as scureBip39 from '@scure/bip39';
import { getPublicKeyFromPrivate, publicKeyToBtcAddress } from '@stacks/encryption';
import { DerivationType, deriveAccount, generateWallet, getRootNode } from '@stacks/wallet-sdk';
import * as bip32 from 'bip32';
import * as bip39 from 'bip39';
import * as blockstack from 'blockstack';
import * as wif from 'wif';
import { getMaxIDSearchIndex, getPrivateKeyAddress } from './common';
import { CLINetworkAdapter } from './network';
import { compressPrivateKey } from '@stacks/transactions';
const BITCOIN_PUBKEYHASH = 0;
const BITCOIN_PUBKEYHASH_TESTNET = 111;
const BITCOIN_WIF = 128;
const BITCOIN_WIF_TESTNET = 239;
export const STX_WALLET_COMPATIBLE_SEED_STRENGTH = 256;
export const DERIVATION_PATH = "m/44'/5757'/0'/0/0";
export type OwnerKeyInfoType = {
privateKey: string;
version: string;
index: number;
idAddress: string;
};
export type PaymentKeyInfoType = {
privateKey: string;
address: {
BTC: string;
STACKS: string;
};
index: number;
};
export type StacksKeyInfoType = {
privateKey: string;
address: string;
btcAddress: string;
wif: string;
index: number;
};
export type AppKeyInfoType = {
keyInfo: {
privateKey: string;
address: string;
};
legacyKeyInfo: {
privateKey: string;
address: string;
};
ownerKeyIndex: number;
};
async function walletFromMnemonic(mnemonic: string): Promise<blockstack.BlockstackWallet> {
const seed = await bip39.mnemonicToSeed(mnemonic);
return new blockstack.BlockstackWallet(bip32.fromSeed(seed));
}
/*
* 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 async function getOwnerKeyInfo(
network: CLINetworkAdapter,
mnemonic: string,
index: number,
version: string = 'v0.10-current'
): Promise<OwnerKeyInfoType> {
const wallet = await generateWallet({ secretKey: mnemonic, password: '' });
const account = deriveAccount({
rootNode: getRootNode(wallet),
salt: wallet.salt,
stxDerivationType: DerivationType.Wallet,
index,
});
const publicKey = getPublicKeyFromPrivate(account.dataPrivateKey);
const addr = network.coerceAddress(publicKeyToBtcAddress(publicKey));
return {
privateKey: account.dataPrivateKey + '01',
idAddress: `ID-${addr}`,
version,
index,
} as OwnerKeyInfoType;
}
/*
* 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 async function getPaymentKeyInfo(
network: CLINetworkAdapter,
mnemonic: string
): Promise<PaymentKeyInfoType> {
const wallet = await walletFromMnemonic(mnemonic);
const privkey = wallet.getBitcoinPrivateKey(0);
const addr = getPrivateKeyAddress(network, privkey);
const result: PaymentKeyInfoType = {
privateKey: privkey,
address: {
BTC: addr,
STACKS: c32check.b58ToC32(addr),
},
index: 0,
};
return result;
}
/*
* Get the payment key information for a 24-word phrase used by the Stacks wallet.
* @network (object) the blockstack network
* @mnemonic (string) the 24-word phrase
*
* Returns an object with:
* .privateKey (string) the hex private key
* .address (string) the address of the private key
*/
export async function getStacksWalletKeyInfo(
network: CLINetworkAdapter,
mnemonic: string,
derivationPath = DERIVATION_PATH
): Promise<StacksKeyInfoType> {
const seed = await scureBip39.mnemonicToSeed(mnemonic);
const master = HDKey.fromMasterSeed(seed);
const child = master.derive(derivationPath);
const pubkey = Buffer.from(child.publicKey!);
const privkeyBuffer = Buffer.from(child.privateKey!);
const privkey = compressPrivateKey(privkeyBuffer);
const wifVersion = network.isTestnet() ? BITCOIN_WIF_TESTNET : BITCOIN_WIF;
const walletImportFormat = wif.encode(wifVersion, privkeyBuffer, true);
const addr = getPrivateKeyAddress(network, privkey);
const btcAddress = publicKeyToBtcAddress(
pubkey,
network.isTestnet() ? BITCOIN_PUBKEYHASH_TESTNET : BITCOIN_PUBKEYHASH
);
return {
privateKey: privkey,
publicKey: pubkey.toString('hex'),
address: c32check.b58ToC32(addr),
btcAddress,
wif: walletImportFormat,
index: 0,
} as StacksKeyInfoType;
}
/*
* Find the index of an ID address, given the mnemonic.
* Returns the index if found
* Returns -1 if not found
*/
export async function findIdentityIndex(
network: CLINetworkAdapter,
mnemonic: string,
idAddress: string,
maxIndex?: number
): Promise<number> {
if (!maxIndex) {
maxIndex = getMaxIDSearchIndex();
}
if (idAddress.substring(0, 3) !== 'ID-') {
throw new Error('Not an identity address');
}
const wallet = await generateWallet({ secretKey: mnemonic, password: '' });
const needle = network.coerceAddress(idAddress.slice(3));
for (let i = 0; i < maxIndex; i++) {
const account = deriveAccount({
rootNode: getRootNode(wallet),
salt: wallet.salt,
stxDerivationType: DerivationType.Wallet,
index: i,
});
const publicKey = getPublicKeyFromPrivate(account.dataPrivateKey);
const address = network.coerceAddress(publicKeyToBtcAddress(publicKey));
if (address === needle) 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 async function getApplicationKeyInfo(
network: CLINetworkAdapter,
mnemonic: string,
idAddress: string,
appDomain: string,
idIndex?: number
): Promise<AppKeyInfoType> {
if (!idIndex) {
idIndex = -1;
}
if (idIndex < 0) {
idIndex = await findIdentityIndex(network, mnemonic, idAddress);
if (idIndex < 0) {
throw new Error('Identity address does not belong to this keychain');
}
}
const wallet = await walletFromMnemonic(mnemonic);
const identityOwnerAddressNode = wallet.getIdentityAddressNode(idIndex);
const appsNode = blockstack.BlockstackWallet.getAppsNode(identityOwnerAddressNode);
//const appPrivateKey = blockstack.BlockstackWallet.getAppPrivateKey(
// appsNode.toBase58(), wallet.getIdentitySalt(), appDomain);
const legacyAppPrivateKey = blockstack.BlockstackWallet.getLegacyAppPrivateKey(
appsNode.toBase58(),
wallet.getIdentitySalt(),
appDomain
);
// TODO: figure out when we can start using the new derivation path
const res: AppKeyInfoType = {
keyInfo: {
privateKey: 'TODO', // appPrivateKey,
address: 'TODO', // getPrivateKeyAddress(network, `${appPrivateKey}01`)
},
legacyKeyInfo: {
privateKey: legacyAppPrivateKey,
address: getPrivateKeyAddress(network, `${legacyAppPrivateKey}01`),
},
ownerKeyIndex: idIndex,
};
return res;
}
/*
* Extract the "right" app key
*/
export function extractAppKey(
network: CLINetworkAdapter,
appKeyInfo: {
keyInfo: { privateKey: string; address: string };
legacyKeyInfo: { privateKey: string; address: string };
},
appAddress?: string
): string {
if (appAddress) {
if (
network.coerceMainnetAddress(appKeyInfo.keyInfo.address) ===
network.coerceMainnetAddress(appAddress)
) {
return appKeyInfo.keyInfo.privateKey;
}
if (
network.coerceMainnetAddress(appKeyInfo.legacyKeyInfo.address) ===
network.coerceMainnetAddress(appAddress)
) {
return appKeyInfo.legacyKeyInfo.privateKey;
}
}
const appPrivateKey =
appKeyInfo.keyInfo.privateKey === 'TODO' || !appKeyInfo.keyInfo.privateKey
? appKeyInfo.legacyKeyInfo.privateKey
: appKeyInfo.keyInfo.privateKey;
return appPrivateKey;
}