UNPKG

13 kBJavaScriptView Raw
1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 return new (P || (P = Promise))(function (resolve, reject) {
4 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6 function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
7 step((generator = generator.apply(thisArg, _arguments || [])).next());
8 });
9};
10var __importStar = (this && this.__importStar) || function (mod) {
11 if (mod && mod.__esModule) return mod;
12 var result = {};
13 if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
14 result["default"] = mod;
15 return result;
16};
17Object.defineProperty(exports, "__esModule", { value: true });
18const crypto_1 = __importStar(require("crypto"));
19const bitcoinjs_lib_1 = require("bitcoinjs-lib");
20const bip39 = __importStar(require("bip39"));
21const utils_1 = require("./utils");
22const wallet_1 = require("./encryption/wallet");
23const APPS_NODE_INDEX = 0;
24const IDENTITY_KEYCHAIN = 888;
25const BLOCKSTACK_ON_BITCOIN = 0;
26const BITCOIN_BIP_44_PURPOSE = 44;
27const BITCOIN_COIN_TYPE = 0;
28const BITCOIN_ACCOUNT_INDEX = 0;
29const EXTERNAL_ADDRESS = 'EXTERNAL_ADDRESS';
30const CHANGE_ADDRESS = 'CHANGE_ADDRESS';
31/**
32 *
33 * @ignore
34 */
35function hashCode(string) {
36 let hash = 0;
37 if (string.length === 0)
38 return hash;
39 for (let i = 0; i < string.length; i++) {
40 const character = string.charCodeAt(i);
41 hash = (hash << 5) - hash + character;
42 hash &= hash;
43 }
44 return hash & 0x7fffffff;
45}
46/**
47 *
48 * @ignore
49 */
50function getNodePrivateKey(node) {
51 return utils_1.ecPairToHexString(bitcoinjs_lib_1.ECPair.fromPrivateKey(node.privateKey));
52}
53/**
54 *
55 * @ignore
56 */
57function getNodePublicKey(node) {
58 return node.publicKey.toString('hex');
59}
60/**
61 * The `BlockstackWallet` class manages the hierarchical derivation
62 * paths for a standard Blockstack client wallet. This includes paths
63 * for Bitcoin payment address, Blockstack identity addresses, Blockstack
64 * application specific addresses.
65 *
66 * @ignore
67 */
68class BlockstackWallet {
69 constructor(rootNode) {
70 this.rootNode = rootNode;
71 }
72 toBase58() {
73 return this.rootNode.toBase58();
74 }
75 /**
76 * Initialize a Blockstack wallet from a seed buffer
77 * @param {Buffer} seed - the input seed for initializing the root node
78 * of the hierarchical wallet
79 * @return {BlockstackWallet} the constructed wallet
80 */
81 static fromSeedBuffer(seed) {
82 return new BlockstackWallet(bitcoinjs_lib_1.bip32.fromSeed(seed));
83 }
84 /**
85 * Initialize a Blockstack wallet from a base58 string
86 * @param {string} keychain - the Base58 string used to initialize
87 * the root node of the hierarchical wallet
88 * @return {BlockstackWallet} the constructed wallet
89 */
90 static fromBase58(keychain) {
91 return new BlockstackWallet(bitcoinjs_lib_1.bip32.fromBase58(keychain));
92 }
93 /**
94 * Initialize a blockstack wallet from an encrypted phrase & password. Throws
95 * if the password is incorrect. Supports all formats of Blockstack phrases.
96 * @param {string} data - The encrypted phrase as a hex-encoded string
97 * @param {string} password - The plain password
98 * @return {Promise<BlockstackWallet>} the constructed wallet
99 *
100 * @ignore
101 */
102 static fromEncryptedMnemonic(data, password) {
103 return __awaiter(this, void 0, void 0, function* () {
104 try {
105 const mnemonic = yield wallet_1.decryptMnemonic(data, password);
106 const seed = yield bip39.mnemonicToSeed(mnemonic);
107 return new BlockstackWallet(bitcoinjs_lib_1.bip32.fromSeed(seed));
108 }
109 catch (err) {
110 if (err.message && err.message.startsWith('bad header;')) {
111 throw new Error('Incorrect password');
112 }
113 else {
114 throw err;
115 }
116 }
117 });
118 }
119 /**
120 * Generate a BIP-39 12 word mnemonic
121 * @return {Promise<string>} space-separated 12 word phrase
122 */
123 static generateMnemonic() {
124 return bip39.generateMnemonic(128, crypto_1.randomBytes);
125 }
126 /**
127 * Encrypt a mnemonic phrase with a password
128 * @param {string} mnemonic - Raw mnemonic phrase
129 * @param {string} password - Password to encrypt mnemonic with
130 * @return {Promise<string>} Hex-encoded encrypted mnemonic
131 *
132 */
133 static encryptMnemonic(mnemonic, password) {
134 return __awaiter(this, void 0, void 0, function* () {
135 const encryptedBuffer = yield wallet_1.encryptMnemonic(mnemonic, password);
136 return encryptedBuffer.toString('hex');
137 });
138 }
139 getIdentityPrivateKeychain() {
140 return this.rootNode
141 .deriveHardened(IDENTITY_KEYCHAIN)
142 .deriveHardened(BLOCKSTACK_ON_BITCOIN);
143 }
144 getBitcoinPrivateKeychain() {
145 return this.rootNode
146 .deriveHardened(BITCOIN_BIP_44_PURPOSE)
147 .deriveHardened(BITCOIN_COIN_TYPE)
148 .deriveHardened(BITCOIN_ACCOUNT_INDEX);
149 }
150 getBitcoinNode(addressIndex, chainType = EXTERNAL_ADDRESS) {
151 return BlockstackWallet.getNodeFromBitcoinKeychain(this.getBitcoinPrivateKeychain().toBase58(), addressIndex, chainType);
152 }
153 getIdentityAddressNode(identityIndex) {
154 const identityPrivateKeychain = this.getIdentityPrivateKeychain();
155 return identityPrivateKeychain.deriveHardened(identityIndex);
156 }
157 static getAppsNode(identityNode) {
158 return identityNode.deriveHardened(APPS_NODE_INDEX);
159 }
160 /**
161 * Get a salt for use with creating application specific addresses
162 * @return {String} the salt
163 */
164 getIdentitySalt() {
165 const identityPrivateKeychain = this.getIdentityPrivateKeychain();
166 const publicKeyHex = getNodePublicKey(identityPrivateKeychain);
167 return crypto_1.default.createHash('sha256').update(publicKeyHex).digest('hex');
168 }
169 /**
170 * Get a bitcoin receive address at a given index
171 * @param {number} addressIndex - the index of the address
172 * @return {String} address
173 */
174 getBitcoinAddress(addressIndex) {
175 return BlockstackWallet.getAddressFromBIP32Node(this.getBitcoinNode(addressIndex));
176 }
177 /**
178 * Get the private key hex-string for a given bitcoin receive address
179 * @param {number} addressIndex - the index of the address
180 * @return {String} the hex-string. this will be either 64
181 * characters long to denote an uncompressed bitcoin address, or 66
182 * characters long for a compressed bitcoin address.
183 */
184 getBitcoinPrivateKey(addressIndex) {
185 return getNodePrivateKey(this.getBitcoinNode(addressIndex));
186 }
187 /**
188 * Get the root node for the bitcoin public keychain
189 * @return {String} base58-encoding of the public node
190 */
191 getBitcoinPublicKeychain() {
192 return this.getBitcoinPrivateKeychain().neutered();
193 }
194 /**
195 * Get the root node for the identity public keychain
196 * @return {String} base58-encoding of the public node
197 */
198 getIdentityPublicKeychain() {
199 return this.getIdentityPrivateKeychain().neutered();
200 }
201 static getNodeFromBitcoinKeychain(keychainBase58, addressIndex, chainType = EXTERNAL_ADDRESS) {
202 let chain;
203 if (chainType === EXTERNAL_ADDRESS) {
204 chain = 0;
205 }
206 else if (chainType === CHANGE_ADDRESS) {
207 chain = 1;
208 }
209 else {
210 throw new Error('Invalid chain type');
211 }
212 const keychain = bitcoinjs_lib_1.bip32.fromBase58(keychainBase58);
213 return keychain.derive(chain).derive(addressIndex);
214 }
215 /**
216 * Get a bitcoin address given a base-58 encoded bitcoin node
217 * (usually called the account node)
218 * @param {String} keychainBase58 - base58-encoding of the node
219 * @param {number} addressIndex - index of the address to get
220 * @param {String} chainType - either 'EXTERNAL_ADDRESS' (for a
221 * "receive" address) or 'CHANGE_ADDRESS'
222 * @return {String} the address
223 */
224 static getAddressFromBitcoinKeychain(keychainBase58, addressIndex, chainType = EXTERNAL_ADDRESS) {
225 return BlockstackWallet.getAddressFromBIP32Node(BlockstackWallet
226 .getNodeFromBitcoinKeychain(keychainBase58, addressIndex, chainType));
227 }
228 /**
229 * Get a ECDSA private key hex-string for an application-specific
230 * address.
231 * @param {String} appsNodeKey - the base58-encoded private key for
232 * applications node (the `appsNodeKey` return in getIdentityKeyPair())
233 * @param {String} salt - a string, used to salt the
234 * application-specific addresses
235 * @param {String} appDomain - the appDomain to generate a key for
236 * @return {String} the private key hex-string. this will be a 64
237 * character string
238 */
239 static getLegacyAppPrivateKey(appsNodeKey, salt, appDomain) {
240 const hash = crypto_1.default
241 .createHash('sha256')
242 .update(`${appDomain}${salt}`)
243 .digest('hex');
244 const appIndex = hashCode(hash);
245 const appNode = bitcoinjs_lib_1.bip32.fromBase58(appsNodeKey).deriveHardened(appIndex);
246 return getNodePrivateKey(appNode).slice(0, 64);
247 }
248 static getAddressFromBIP32Node(node) {
249 return bitcoinjs_lib_1.payments.p2pkh({ pubkey: node.publicKey }).address;
250 }
251 /**
252 * Get a ECDSA private key hex-string for an application-specific
253 * address.
254 * @param {String} appsNodeKey - the base58-encoded private key for
255 * applications node (the `appsNodeKey` return in getIdentityKeyPair())
256 * @param {String} salt - a string, used to salt the
257 * application-specific addresses
258 * @param {String} appDomain - the appDomain to generate a key for
259 * @return {String} the private key hex-string. this will be a 64
260 * character string
261 */
262 static getAppPrivateKey(appsNodeKey, salt, appDomain) {
263 const hash = crypto_1.default
264 .createHash('sha256')
265 .update(`${appDomain}${salt}`)
266 .digest('hex');
267 const appIndexHexes = [];
268 // note: there's hardcoded numbers here, precisely because I want this
269 // code to be very specific to the derivation paths we expect.
270 if (hash.length !== 64) {
271 throw new Error(`Unexpected app-domain hash length of ${hash.length}`);
272 }
273 for (let i = 0; i < 11; i++) { // split the hash into 3-byte chunks
274 // because child nodes can only be up to 2^31,
275 // and we shouldn't deal in partial bytes.
276 appIndexHexes.push(hash.slice(i * 6, i * 6 + 6));
277 }
278 let appNode = bitcoinjs_lib_1.bip32.fromBase58(appsNodeKey);
279 appIndexHexes.forEach((hex) => {
280 if (hex.length > 6) {
281 throw new Error('Invalid hex string length');
282 }
283 appNode = appNode.deriveHardened(parseInt(hex, 16));
284 });
285 return getNodePrivateKey(appNode).slice(0, 64);
286 }
287 /**
288 * Get the keypair information for a given identity index. This
289 * information is used to obtain the private key for an identity address
290 * and derive application specific keys for that address.
291 * @param {number} addressIndex - the identity index
292 * @param {boolean} alwaysUncompressed - if true, always return a
293 * private-key hex string corresponding to the uncompressed address
294 * @return {Object} an IdentityKeyPair type object with keys:
295 * .key {String} - the private key hex-string
296 * .keyID {String} - the public key hex-string
297 * .address {String} - the identity address
298 * .appsNodeKey {String} - the base-58 encoding of the applications node
299 * .salt {String} - the salt used for creating app-specific addresses
300 */
301 getIdentityKeyPair(addressIndex, alwaysUncompressed = false) {
302 const identityNode = this.getIdentityAddressNode(addressIndex);
303 const address = BlockstackWallet.getAddressFromBIP32Node(identityNode);
304 let identityKey = getNodePrivateKey(identityNode);
305 if (alwaysUncompressed && identityKey.length === 66) {
306 identityKey = identityKey.slice(0, 64);
307 }
308 const identityKeyID = getNodePublicKey(identityNode);
309 const appsNodeKey = BlockstackWallet.getAppsNode(identityNode).toBase58();
310 const salt = this.getIdentitySalt();
311 const keyPair = {
312 key: identityKey,
313 keyID: identityKeyID,
314 address,
315 appsNodeKey,
316 salt
317 };
318 return keyPair;
319 }
320}
321exports.BlockstackWallet = BlockstackWallet;
322//# sourceMappingURL=wallet.js.map
\No newline at end of file