UNPKG

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