This repository has been archived by the owner on Oct 13, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
140 lines (114 loc) · 4.54 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
const bip39 = require('bip39') // a forked version, see package.json
const CryptoJS = require('crypto-js')
const ethUtil = require('ethereumjs-util')
const hdkey = require('ethereumjs-wallet/hdkey')
const keySize = 32
const iterations = 250
const AESBlockSize = 16
module.exports.isMnemonicValid = (mnemonic) => {
return bip39.validateMnemonic(mnemonic)
}
module.exports.concatSignature = (signature) => {
var r = signature.r
var s = signature.s
var v = signature.v
r = ethUtil.fromSigned(r)
s = ethUtil.fromSigned(s)
v = ethUtil.bufferToInt(v)
r = ethUtil.setLengthLeft(ethUtil.toUnsigned(r), 32).toString('hex')
s = ethUtil.setLengthLeft(ethUtil.toUnsigned(s), 32).toString('hex')
v = ethUtil.stripHexPrefix(ethUtil.intToHex(v))
return ethUtil.addHexPrefix(r.concat(s, v).toString('hex'))
}
module.exports.Keystore = class Keystore {
constructor (rng) {
// rng should be a function that accepts a number of bytes argument and returns an unprefixed hex string
// see https://github.com/brix/crypto-js/issues/7 for CryptoJS random
var native = (bytes) => { return CryptoJS.lib.WordArray.random(bytes).toString() }
rng = rng || native
this.rng = (bytes) => { return ethUtil.stripHexPrefix(rng(bytes)) }
}
async initializeFromEntropy (entropy, password) {
if (typeof entropy !== 'string' || typeof password !== 'string') {
throw new Error('entropy and password must both be strings')
}
// generate extra randomness of the same size as the hash function
var extraEntropy = await this.rng(32)
// hash the entropy sources together and take first the 16 bytes (corresponds to 12 seed words)
var hashedEntropy = ethUtil.sha256(entropy + extraEntropy).slice(0, 16)
var mnemonic = bip39.generateMnemonic(undefined, () => { return hashedEntropy })
var keystore = await this.restoreFromMnemonic(mnemonic, password)
return keystore
}
async restoreFromMnemonic (mnemonic, password) {
if (typeof mnemonic !== 'string' || typeof password !== 'string') {
throw new Error('mnemonic and password must both be strings')
}
if (!module.exports.isMnemonicValid(mnemonic)) throw new Error('invalid mnemonic')
var seed = bip39.mnemonicToSeed(mnemonic)
var wallet = hdkey.fromMasterSeed(seed).derivePath(`m/44'/60'/0'/0`).deriveChild(0).getWallet()
// salt should be the same size as the hash function output
this.salt = await this.rng(keySize)
var key = this.keyFromPassword(password)
this.address = wallet.getAddressString()
this.encodedMnemonic = await this.encryptString(mnemonic, key)
this.encodedPrivateKey = await this.encryptString(wallet.getPrivateKeyString(), key)
return this
}
restorefromSerialized (serializedKeystore) {
var variables = JSON.parse(serializedKeystore)
this.salt = variables.salt
this.address = variables.address
this.encodedMnemonic = variables.encodedMnemonic
this.encodedPrivateKey = variables.encodedPrivateKey
return this
}
keyFromPassword (password) {
return CryptoJS.PBKDF2(password, this.salt, {
keySize: keySize / 4, // 1 word := 4 bytes
hasher: CryptoJS.algo.SHA256,
iterations: iterations
})
}
async encryptString (string, password) {
var randomBytes = await this.rng(AESBlockSize)
var words = []
for (var i = 0; i < AESBlockSize * 2; i += 8) {
words.push('0x' + randomBytes.substring(i, i + 8))
}
var iv = new CryptoJS.lib.WordArray.init(words, AESBlockSize)
var ciphertext = CryptoJS.AES.encrypt(string, this.keyFromPassword(password), { iv: iv })
return {
ciphertext: ciphertext.toString(),
iv: iv
}
}
decryptString (encrypted, password) {
var decrypted = CryptoJS.AES.decrypt(encrypted.ciphertext, this.keyFromPassword(password), { iv: encrypted.iv })
return decrypted.toString(CryptoJS.enc.Utf8)
}
serialize () {
return JSON.stringify({
salt: this.salt,
address: this.address,
encodedMnemonic: this.encodedMnemonic,
encodedPrivateKey: this.encodedPrivateKey
})
}
signMessageHash (messageHash, password) {
var privateKey = this.getPrivateKey(password)
return ethUtil.ecsign(
Buffer.from(ethUtil.stripHexPrefix(messageHash), 'hex'),
Buffer.from(privateKey.substring(2), 'hex')
)
}
getMnemonic (password) {
return this.decryptString(this.encodedMnemonic, this.keyFromPassword(password))
}
getPrivateKey (password) {
return this.decryptString(this.encodedPrivateKey, this.keyFromPassword(password))
}
getAddress () {
return this.address
}
}