1 | "use strict";
|
2 |
|
3 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
4 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
5 | };
|
6 | Object.defineProperty(exports, "__esModule", { value: true });
|
7 |
|
8 |
|
9 |
|
10 | const assert_1 = __importDefault(require("assert"));
|
11 | const bcrypto_1 = require("bcrypto");
|
12 | const browserify_aes_1 = __importDefault(require("browserify-aes"));
|
13 | const bs58check_1 = __importDefault(require("bs58check"));
|
14 | const inplace_1 = __importDefault(require("buffer-xor/inplace"));
|
15 | const crypto_1 = __importDefault(require("crypto"));
|
16 | const crypto_2 = require("../crypto");
|
17 | const errors_1 = require("../errors");
|
18 | const identities_1 = require("../identities");
|
19 | const SCRYPT_PARAMS = {
|
20 | N: 16384,
|
21 | r: 8,
|
22 | p: 8,
|
23 | };
|
24 | const NULL = Buffer.alloc(0);
|
25 | const getPublicKey = (buffer, compressed) => {
|
26 | return Buffer.from(identities_1.Keys.fromPrivateKey(buffer, compressed).publicKey, "hex");
|
27 | };
|
28 | const 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 | };
|
36 | exports.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 |
|
50 | if (type === 0x42) {
|
51 | if (flag !== 0xc0 && flag !== 0xe0) {
|
52 | return false;
|
53 | }
|
54 |
|
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 | };
|
66 | const 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 |
|
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 | };
|
90 | const 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 |
|
100 | if (hasLotSeq) {
|
101 | ownerSalt = ownerEntropy.slice(0, 4);
|
102 |
|
103 | }
|
104 | else {
|
105 | ownerSalt = ownerEntropy;
|
106 | }
|
107 | const encryptedPart1 = buffer.slice(14, 22);
|
108 | const encryptedPart2 = buffer.slice(22, 38);
|
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);
|
135 | decipher2.end(tmp.slice(0, 8));
|
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 |
|
145 | const decryptRaw = (buffer, passphrase) => {
|
146 |
|
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 |
|
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 |
|
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 | };
|
185 | exports.encrypt = (privateKey, compressed, passphrase) => {
|
186 | return bs58check_1.default.encode(encryptRaw(privateKey, compressed, passphrase));
|
187 | };
|
188 | exports.decrypt = (bip38, passphrase) => {
|
189 | return decryptRaw(bs58check_1.default.decode(bip38), passphrase);
|
190 | };
|
191 |
|
\ | No newline at end of file |