UNPKG

7.37 kBJavaScriptView Raw
1"use strict";
2// tslint:disable:no-bitwise
3var __importDefault = (this && this.__importDefault) || function (mod) {
4 return (mod && mod.__esModule) ? mod : { "default": mod };
5};
6Object.defineProperty(exports, "__esModule", { value: true });
7/**
8 * Based on: https://github.com/bitcoinjs/bip38 @ 8e3a2cc6f7391782f3012129924a73bb632a3d4d
9 */
10const assert_1 = __importDefault(require("assert"));
11const bcrypto_1 = require("bcrypto");
12const browserify_aes_1 = __importDefault(require("browserify-aes"));
13const bs58check_1 = __importDefault(require("bs58check"));
14const inplace_1 = __importDefault(require("buffer-xor/inplace"));
15const crypto_1 = __importDefault(require("crypto"));
16const crypto_2 = require("../crypto");
17const errors_1 = require("../errors");
18const identities_1 = require("../identities");
19const SCRYPT_PARAMS = {
20 N: 16384,
21 r: 8,
22 p: 8,
23};
24const NULL = Buffer.alloc(0);
25const getPublicKey = (buffer, compressed) => {
26 return Buffer.from(identities_1.Keys.fromPrivateKey(buffer, compressed).publicKey, "hex");
27};
28const getAddressPrivate = (privateKey, compressed) => {
29 const publicKey = getPublicKey(privateKey, compressed);
30 const buffer = crypto_2.HashAlgorithms.hash160(publicKey);
31 const payload = Buffer.alloc(21);
32 payload.writeUInt8(0x00, 0);
33 buffer.copy(payload, 1);
34 return bs58check_1.default.encode(payload);
35};
36exports.verify = (bip38) => {
37 const decoded = bs58check_1.default.decodeUnsafe(bip38);
38 if (!decoded) {
39 return false;
40 }
41 if (decoded.length !== 39) {
42 return false;
43 }
44 if (decoded.readUInt8(0) !== 0x01) {
45 return false;
46 }
47 const type = decoded.readUInt8(1);
48 const flag = decoded.readUInt8(2);
49 // encrypted WIF
50 if (type === 0x42) {
51 if (flag !== 0xc0 && flag !== 0xe0) {
52 return false;
53 }
54 // EC mult
55 }
56 else if (type === 0x43) {
57 if (flag & ~0x24) {
58 return false;
59 }
60 }
61 else {
62 return false;
63 }
64 return true;
65};
66const encryptRaw = (buffer, compressed, passphrase) => {
67 if (buffer.length !== 32) {
68 throw new errors_1.PrivateKeyLengthError(32, buffer.length);
69 }
70 const address = getAddressPrivate(buffer, compressed);
71 const secret = Buffer.from(passphrase, "utf8");
72 const salt = crypto_2.HashAlgorithms.hash256(address).slice(0, 4);
73 const scryptBuf = crypto_1.default.scryptSync(secret, salt, 64, SCRYPT_PARAMS);
74 const derivedHalf1 = scryptBuf.slice(0, 32);
75 const derivedHalf2 = scryptBuf.slice(32, 64);
76 const xorBuf = inplace_1.default(derivedHalf1, buffer);
77 const cipher = browserify_aes_1.default.createCipheriv("aes-256-ecb", derivedHalf2, NULL);
78 cipher.setAutoPadding(false);
79 cipher.end(xorBuf);
80 const cipherText = cipher.read();
81 // 0x01 | 0x42 | flagByte | salt (4) | cipherText (32)
82 const result = Buffer.allocUnsafe(7 + 32);
83 result.writeUInt8(0x01, 0);
84 result.writeUInt8(0x42, 1);
85 result.writeUInt8(compressed ? 0xe0 : 0xc0, 2);
86 salt.copy(result, 3);
87 cipherText.copy(result, 7);
88 return result;
89};
90const decryptECMult = (buffer, passphrase) => {
91 buffer = buffer.slice(1);
92 const flag = buffer.readUInt8(1);
93 const compressed = (flag & 0x20) !== 0;
94 const hasLotSeq = (flag & 0x04) !== 0;
95 assert_1.default.strictEqual(flag & 0x24, flag, "Invalid private key.");
96 const addressHash = buffer.slice(2, 6);
97 const ownerEntropy = buffer.slice(6, 14);
98 let ownerSalt;
99 // 4 bytes ownerSalt if 4 bytes lot/sequence
100 if (hasLotSeq) {
101 ownerSalt = ownerEntropy.slice(0, 4);
102 // else, 8 bytes ownerSalt
103 }
104 else {
105 ownerSalt = ownerEntropy;
106 }
107 const encryptedPart1 = buffer.slice(14, 22); // First 8 bytes
108 const encryptedPart2 = buffer.slice(22, 38); // 16 bytes
109 const preFactor = crypto_1.default.scryptSync(passphrase, ownerSalt, 32, SCRYPT_PARAMS);
110 let passFactor;
111 if (hasLotSeq) {
112 const hashTarget = Buffer.concat([preFactor, ownerEntropy]);
113 passFactor = crypto_2.HashAlgorithms.hash256(hashTarget);
114 }
115 else {
116 passFactor = preFactor;
117 }
118 const publicKey = getPublicKey(passFactor, true);
119 const seedBPass = crypto_1.default.scryptSync(publicKey, Buffer.concat([addressHash, ownerEntropy]), 64, {
120 N: 1024,
121 r: 1,
122 p: 1,
123 });
124 const derivedHalf1 = seedBPass.slice(0, 32);
125 const derivedHalf2 = seedBPass.slice(32, 64);
126 const decipher = browserify_aes_1.default.createDecipheriv("aes-256-ecb", derivedHalf2, Buffer.alloc(0));
127 decipher.setAutoPadding(false);
128 decipher.end(encryptedPart2);
129 const decryptedPart2 = decipher.read();
130 const tmp = inplace_1.default(decryptedPart2, derivedHalf1.slice(16, 32));
131 const seedBPart2 = tmp.slice(8, 16);
132 const decipher2 = browserify_aes_1.default.createDecipheriv("aes-256-ecb", derivedHalf2, Buffer.alloc(0));
133 decipher2.setAutoPadding(false);
134 decipher2.write(encryptedPart1); // first 8 bytes
135 decipher2.end(tmp.slice(0, 8)); // last 8 bytes
136 const seedBPart1 = inplace_1.default(decipher2.read(), derivedHalf1.slice(0, 16));
137 const seedB = Buffer.concat([seedBPart1, seedBPart2], 24);
138 const privateKey = bcrypto_1.secp256k1.privateKeyTweakMul(crypto_2.HashAlgorithms.hash256(seedB), passFactor);
139 return {
140 privateKey,
141 compressed,
142 };
143};
144// some of the techniques borrowed from: https://github.com/pointbiz/bitaddress.org
145const decryptRaw = (buffer, passphrase) => {
146 // 39 bytes: 2 bytes prefix, 37 bytes payload
147 if (buffer.length !== 39) {
148 throw new errors_1.Bip38LengthError(39, buffer.length);
149 }
150 if (buffer.readUInt8(0) !== 0x01) {
151 throw new errors_1.Bip38PrefixError(0x01, buffer.readUInt8(0));
152 }
153 // check if BIP38 EC multiply
154 const type = buffer.readUInt8(1);
155 if (type === 0x43) {
156 return decryptECMult(buffer, passphrase);
157 }
158 if (type !== 0x42) {
159 throw new errors_1.Bip38TypeError(0x42, type);
160 }
161 const flagByte = buffer.readUInt8(2);
162 const compressed = flagByte === 0xe0;
163 if (!compressed && flagByte !== 0xc0) {
164 throw new errors_1.Bip38CompressionError(0xc0, flagByte);
165 }
166 const salt = buffer.slice(3, 7);
167 const scryptBuf = crypto_1.default.scryptSync(passphrase, salt, 64, SCRYPT_PARAMS);
168 const derivedHalf1 = scryptBuf.slice(0, 32);
169 const derivedHalf2 = scryptBuf.slice(32, 64);
170 const privKeyBuf = buffer.slice(7, 7 + 32);
171 const decipher = browserify_aes_1.default.createDecipheriv("aes-256-ecb", derivedHalf2, NULL);
172 decipher.setAutoPadding(false);
173 decipher.end(privKeyBuf);
174 const plainText = decipher.read();
175 const privateKey = inplace_1.default(derivedHalf1, plainText);
176 // verify salt matches address
177 const address = getAddressPrivate(privateKey, compressed);
178 const checksum = crypto_2.HashAlgorithms.hash256(address).slice(0, 4);
179 assert_1.default.deepEqual(salt, checksum);
180 return {
181 privateKey,
182 compressed,
183 };
184};
185exports.encrypt = (privateKey, compressed, passphrase) => {
186 return bs58check_1.default.encode(encryptRaw(privateKey, compressed, passphrase));
187};
188exports.decrypt = (bip38, passphrase) => {
189 return decryptRaw(bs58check_1.default.decode(bip38), passphrase);
190};
191//# sourceMappingURL=bip38.js.map
\No newline at end of file