UNPKG

9.8 kBJavaScriptView Raw
1const crypto = require('crypto');
2const keccak = require('keccak');
3const secp256k1 = require('secp256k1');
4const { syncScrypt: scrypt } = require('scrypt-js');
5const { encode: rlpEncode } = require('./rlp');
6
7// ----------------------------------------------------------------------------
8/**
9 * alias of keccak256
10 *
11 * @param buffer {Buffer}
12 * @return {Buffer}
13 *
14 * @example
15 * > sha3(Buffer.from(''))
16 <Buffer c5 d2 46 01 86 f7 23 3c 92 7e 7d b2 dc c7 03 c0 e5 00 b6 53 ca 82 27 3b 7b fa d8 04 5d 85 a4 70>
17 */
18function sha3(buffer) {
19 return keccak('keccak256').update(buffer).digest();
20}
21
22/**
23 * Makes a checksum address
24 *
25 * @param address {string} - Hex string
26 * @return {string}
27 *
28 * @example
29 * > checksumAddress('0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359')
30 "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"
31 */
32function checksumAddress(address) {
33 address = address.toLowerCase().replace('0x', '');
34
35 const hash = sha3(Buffer.from(address)).toString('hex');
36 const sequence = Object.entries(address).map(([index, char]) => {
37 return parseInt(hash[index], 16) >= 8 ? char.toUpperCase() : char;
38 });
39 return `0x${sequence.join('')}`;
40}
41
42// ----------------------------------------------------------------------------
43/**
44 * gen a random buffer with `size` bytes.
45 *
46 * > Note: call `crypto.randomBytes`
47 *
48 * @param size {number}
49 * @return {Buffer}
50 *
51 * @example
52 * > randomBuffer(0)
53 <Buffer >
54 * > randomBuffer(1)
55 <Buffer 33>
56 * > randomBuffer(1)
57 <Buffer 5a>
58 */
59function randomBuffer(size) {
60 return crypto.randomBytes(size);
61}
62
63/**
64 * Gen a random PrivateKey buffer.
65 *
66 * @param entropy {Buffer}
67 * @return {Buffer}
68 *
69 * @example
70 * > randomPrivateKey()
71 <Buffer 23 fb 3b 2b 1f c9 36 8c a4 8e 5b dc c7 a9 e2 bd 67 81 43 3b f2 3a cc da da ff a9 dd dd b6 08 d4>
72 * > randomPrivateKey()
73 <Buffer e7 5b 68 fb f9 50 19 94 07 80 d5 13 2e 40 a7 f9 a1 b0 5d 72 c8 86 ca d1 c6 59 cd a6 bf 37 cb 73>
74
75 * @example
76 * > entropy = randomBuffer(32)
77 * > randomPrivateKey(entropy)
78 <Buffer 57 90 e8 3d 16 10 02 b9 a4 33 87 e1 6b cd 40 7e f7 22 b1 d8 94 ae 98 bf 76 a4 56 fb b6 0c 4b 4a>
79 * > randomPrivateKey(entropy) // same `entropy`
80 <Buffer 89 44 ef 31 d4 9c d0 25 9f b0 de 61 99 12 4a 21 57 43 d4 4b af ae ef ae e1 3a ba 05 c3 e6 ad 21>
81 */
82function randomPrivateKey(entropy = randomBuffer(32)) {
83 if (!(Buffer.isBuffer(entropy) && entropy.length === 32)) {
84 throw new Error(`entropy must be 32 length Buffer, got "${typeof entropy}"`);
85 }
86
87 const inner = sha3(Buffer.concat([randomBuffer(32), entropy]));
88 const middle = Buffer.concat([randomBuffer(32), inner, randomBuffer(32)]);
89 return sha3(middle);
90}
91
92/**
93 * @param privateKey {Buffer}
94 * @return {Buffer}
95 */
96function privateKeyToPublicKey(privateKey) {
97 return secp256k1.publicKeyCreate(privateKey, false).slice(1);
98}
99
100/**
101 * Get account address by public key.
102 *
103 * > Account address hex starts with '0x1'
104 *
105 * @param publicKey {Buffer}
106 * @return {Buffer}
107 *
108 * @example
109 * > privateKeyToAddress(Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1]))
110 <Buffer 4c 6f a3 22 12 5f a3 1a 42 cb dd a8 73 0d 4c f0 20 0d 72 db>
111 */
112function publicKeyToAddress(publicKey) {
113 const buffer = sha3(publicKey).slice(-20);
114 buffer[0] = (buffer[0] & 0x0f) | 0x10; // eslint-disable-line no-bitwise
115 return buffer;
116}
117
118/**
119 * Get address by private key.
120 *
121 * @param privateKey {Buffer}
122 * @return {Buffer}
123 *
124 * @example
125 * > privateKeyToAddress(Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1]))
126 <Buffer 0d b9 e0 02 85 67 52 28 8b ef 47 60 fa 67 94 ec 83 a8 53 b9>
127 */
128function privateKeyToAddress(privateKey) {
129 return publicKeyToAddress(privateKeyToPublicKey(privateKey));
130}
131
132/**
133 * Sign ecdsa
134 *
135 * @param hash {Buffer}
136 * @param privateKey {Buffer}
137 * @return {object} ECDSA signature object.
138 * - r {Buffer}
139 * - s {Buffer}
140 * - v {number}
141 *
142 * @example
143 * > privateKey = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1]);
144 * > buffer32 = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31])
145 * > ecdsaSign(buffer32, privateKey)
146 {
147 r: <Buffer 21 ab b4 c3 fd 51 75 81 e6 c7 e7 e0 7f 40 4f a2 2c ba 8d 8f 71 27 0b 29 58 42 b8 3c 44 b5 a4 c6>,
148 s: <Buffer 08 59 7b 69 8f 8f 3c c2 ba 0b 45 ee a7 7f 55 29 ad f9 5c a5 51 41 e7 9b 56 53 77 3d 00 5d 18 58>,
149 v: 0
150 }
151 */
152function ecdsaSign(hash, privateKey) {
153 const sig = secp256k1.sign(hash, privateKey);
154 return {
155 r: sig.signature.slice(0, 32),
156 s: sig.signature.slice(32, 64),
157 v: sig.recovery,
158 };
159}
160
161/**
162 * Recover ecdsa
163 *
164 * @param hash {Buffer}
165 * @param options {object}
166 * @param options.r {Buffer}
167 * @param options.s {Buffer}
168 * @param options.v {number}
169 * @return {Buffer} publicKey
170 *
171 * @example
172 * > privateKey = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1])
173 * > buffer32 = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31])
174 * > privateKeyToAddress(privateKey)
175 <Buffer 0d b9 e0 02 85 67 52 28 8b ef 47 60 fa 67 94 ec 83 a8 53 b9>
176 * > publicKeyToAddress(ecdsaRecover(buffer32, ecdsaSign(buffer32, privateKey)))
177 <Buffer 0d b9 e0 02 85 67 52 28 8b ef 47 60 fa 67 94 ec 83 a8 53 b9>
178 */
179function ecdsaRecover(hash, { r, s, v }) {
180 const senderPublic = secp256k1.recover(hash, Buffer.concat([r, s]), v);
181 return secp256k1.publicKeyConvert(senderPublic, false).slice(1);
182}
183
184// ----------------------------------------------------------------------------
185function uuidV4() {
186 return [4, 2, 2, 2, 6].map(randomBuffer).map(v => v.toString('hex')).join('-');
187}
188
189/**
190 *
191 * @param privateKey {Buffer}
192 * @param password {string|Buffer}
193 * @return {object} - keystoreV3 object
194 *
195 * @example
196 * > encrypt(Buffer.from('0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 'hex'), 'password')
197 {
198 version: 3,
199 id: '0bb47ee0-aac3-a006-2717-03877afa15f0',
200 address: '1cad0b19bb29d4674531d6f115237e16afce377c',
201 crypto: {
202 ciphertext: 'a8ec41d2440311ce897bacb6f7942f3235113fa17c4ae6732e032336038a8f73',
203 cipherparams: { iv: '85b5e092c1c32129e3d27df8c581514d' },
204 cipher: 'aes-128-ctr',
205 kdf: 'scrypt',
206 kdfparams: {
207 dklen: 32,
208 salt: 'b662f09bdf6751ac599219732609dceac430bc0629a7906eaa1451555f051ebc',
209 n: 8192,
210 r: 8,
211 p: 1
212 },
213 mac: 'cc89df7ef6c27d284526a65cabf8e5042cdf1ec1aa4ee36dcf65b965fa34843d'
214 }
215 }
216 */
217function encrypt(privateKey, password) {
218 const cipher = 'aes-128-ctr';
219 const n = 8192;
220 const r = 8;
221 const p = 1;
222 const dklen = 32;
223 const salt = randomBuffer(32);
224 const iv = randomBuffer(16);
225
226 password = Buffer.from(password);
227 const derived = scrypt(password, salt, n, r, p, dklen);
228 const ciphertext = crypto.createCipheriv(cipher, derived.slice(0, 16), iv).update(privateKey);
229 const mac = sha3(Buffer.concat([derived.slice(16, 32), ciphertext]));
230 const address = privateKeyToAddress(privateKey);
231
232 return {
233 version: 3,
234 id: uuidV4(),
235 address: address.toString('hex'),
236 crypto: {
237 ciphertext: ciphertext.toString('hex'),
238 cipherparams: { iv: iv.toString('hex') },
239 cipher,
240 kdf: 'scrypt',
241 kdfparams: { dklen, salt: salt.toString('hex'), n, r, p },
242 mac: mac.toString('hex'),
243 },
244 };
245}
246
247/**
248 * Decrypt account encrypt info.
249 *
250 * @param keystoreV3 {object}
251 * @param password {string|Buffer}
252 * @return {Buffer} Buffer of private key
253 *
254 * @example
255 * > decrypt({
256 version: 3,
257 id: '0bb47ee0-aac3-a006-2717-03877afa15f0',
258 address: '1cad0b19bb29d4674531d6f115237e16afce377c',
259 crypto: {
260 ciphertext: 'a8ec41d2440311ce897bacb6f7942f3235113fa17c4ae6732e032336038a8f73',
261 cipherparams: { iv: '85b5e092c1c32129e3d27df8c581514d' },
262 cipher: 'aes-128-ctr',
263 kdf: 'scrypt',
264 kdfparams: {
265 dklen: 32,
266 salt: 'b662f09bdf6751ac599219732609dceac430bc0629a7906eaa1451555f051ebc',
267 n: 8192,
268 r: 8,
269 p: 1
270 },
271 mac: 'cc89df7ef6c27d284526a65cabf8e5042cdf1ec1aa4ee36dcf65b965fa34843d'
272 }
273 }, 'password')
274 <Buffer 01 23 45 67 89 ab cd ef 01 23 45 67 89 ab cd ef 01 23 45 67 89 ab cd ef 01 23 45 67 89 ab cd ef>
275 */
276function decrypt({
277 version,
278 crypto: {
279 ciphertext,
280 cipherparams: { iv },
281 cipher,
282 kdf,
283 kdfparams: { dklen, salt, n, r, p },
284 mac,
285 },
286}, password) {
287 if (version !== 3) {
288 throw new Error('Not a valid V3 wallet');
289 }
290 if (kdf !== 'scrypt') {
291 throw new Error(`Unsupported kdf "${kdf}", only support "scrypt"`);
292 }
293
294 password = Buffer.from(password);
295 ciphertext = Buffer.from(ciphertext, 'hex');
296 iv = Buffer.from(iv, 'hex');
297 salt = Buffer.from(salt, 'hex');
298 mac = Buffer.from(mac, 'hex');
299
300 const derived = scrypt(password, salt, n, r, p, dklen);
301 if (!sha3(Buffer.concat([derived.slice(16, 32), ciphertext])).equals(mac)) {
302 throw new Error('Key derivation failed, possibly wrong password!');
303 }
304 return crypto.createDecipheriv(cipher, derived.slice(0, 16), iv).update(ciphertext);
305}
306
307module.exports = {
308 rlpEncode,
309
310 sha3,
311 checksumAddress,
312
313 randomBuffer,
314 randomPrivateKey,
315 privateKeyToPublicKey,
316 publicKeyToAddress,
317 privateKeyToAddress,
318 ecdsaSign,
319 ecdsaRecover,
320
321 encrypt,
322 decrypt,
323};