aboutsummaryrefslogtreecommitdiff
path: root/libs/bitcoinjs-bip38/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'libs/bitcoinjs-bip38/index.js')
-rw-r--r--libs/bitcoinjs-bip38/index.js244
1 files changed, 244 insertions, 0 deletions
diff --git a/libs/bitcoinjs-bip38/index.js b/libs/bitcoinjs-bip38/index.js
new file mode 100644
index 0000000..51c20e4
--- /dev/null
+++ b/libs/bitcoinjs-bip38/index.js
@@ -0,0 +1,244 @@
1var aes = require('browserify-aes')
2var assert = require('assert')
3var Buffer = require('safe-buffer').Buffer
4var bs58check = require('bs58check')
5var createHash = require('create-hash')
6var scrypt = require('scryptsy')
7var xor = require('buffer-xor/inplace')
8
9var ecurve = require('ecurve')
10var curve = ecurve.getCurveByName('secp256k1')
11
12var BigInteger = require('bigi')
13
14// constants
15var SCRYPT_PARAMS = {
16 N: 16384, // specified by BIP38
17 r: 8,
18 p: 8
19}
20var NULL = Buffer.alloc(0)
21
22function hash160 (buffer) {
23 var hash
24 try {
25 hash = createHash('rmd160')
26 } catch (e) {
27 hash = createHash('ripemd160')
28 }
29 return hash.update(
30 createHash('sha256').update(buffer).digest()
31 ).digest()
32}
33
34function hash256 (buffer) {
35 return createHash('sha256').update(
36 createHash('sha256').update(buffer).digest()
37 ).digest()
38}
39
40function getAddress (d, compressed) {
41 var Q = curve.G.multiply(d).getEncoded(compressed)
42 var hash = hash160(Q)
43 var payload = Buffer.allocUnsafe(21)
44 payload.writeUInt8(0x00, 0) // XXX TODO FIXME bitcoin only??? damn you BIP38
45 hash.copy(payload, 1)
46
47 return bs58check.encode(payload)
48}
49
50function encryptRaw (buffer, compressed, passphrase, progressCallback, scryptParams) {
51 if (buffer.length !== 32) throw new Error('Invalid private key length')
52 scryptParams = scryptParams || SCRYPT_PARAMS
53
54 var d = BigInteger.fromBuffer(buffer)
55 var address = getAddress(d, compressed)
56 var secret = Buffer.from(passphrase, 'utf8')
57 var salt = hash256(address).slice(0, 4)
58
59 var N = scryptParams.N
60 var r = scryptParams.r
61 var p = scryptParams.p
62
63 var scryptBuf = scrypt(secret, salt, N, r, p, 64, progressCallback)
64 var derivedHalf1 = scryptBuf.slice(0, 32)
65 var derivedHalf2 = scryptBuf.slice(32, 64)
66
67 var xorBuf = xor(derivedHalf1, buffer)
68 var cipher = aes.createCipheriv('aes-256-ecb', derivedHalf2, NULL)
69 cipher.setAutoPadding(false)
70 cipher.end(xorBuf)
71
72 var cipherText = cipher.read()
73
74 // 0x01 | 0x42 | flagByte | salt (4) | cipherText (32)
75 var result = Buffer.allocUnsafe(7 + 32)
76 result.writeUInt8(0x01, 0)
77 result.writeUInt8(0x42, 1)
78 result.writeUInt8(compressed ? 0xe0 : 0xc0, 2)
79 salt.copy(result, 3)
80 cipherText.copy(result, 7)
81
82 return result
83}
84
85function encrypt (buffer, compressed, passphrase, progressCallback, scryptParams) {
86 return bs58check.encode(encryptRaw(buffer, compressed, passphrase, progressCallback, scryptParams))
87}
88
89// some of the techniques borrowed from: https://github.com/pointbiz/bitaddress.org
90function decryptRaw (buffer, passphrase, progressCallback, scryptParams) {
91 // 39 bytes: 2 bytes prefix, 37 bytes payload
92 if (buffer.length !== 39) throw new Error('Invalid BIP38 data length')
93 if (buffer.readUInt8(0) !== 0x01) throw new Error('Invalid BIP38 prefix')
94 scryptParams = scryptParams || SCRYPT_PARAMS
95
96 // check if BIP38 EC multiply
97 var type = buffer.readUInt8(1)
98 if (type === 0x43) return decryptECMult(buffer, passphrase, progressCallback, scryptParams)
99 if (type !== 0x42) throw new Error('Invalid BIP38 type')
100
101 passphrase = Buffer.from(passphrase, 'utf8')
102
103 var flagByte = buffer.readUInt8(2)
104 var compressed = flagByte === 0xe0
105 if (!compressed && flagByte !== 0xc0) throw new Error('Invalid BIP38 compression flag')
106
107 var N = scryptParams.N
108 var r = scryptParams.r
109 var p = scryptParams.p
110
111 var salt = buffer.slice(3, 7)
112 var scryptBuf = scrypt(passphrase, salt, N, r, p, 64, progressCallback)
113 var derivedHalf1 = scryptBuf.slice(0, 32)
114 var derivedHalf2 = scryptBuf.slice(32, 64)
115
116 var privKeyBuf = buffer.slice(7, 7 + 32)
117 var decipher = aes.createDecipheriv('aes-256-ecb', derivedHalf2, NULL)
118 decipher.setAutoPadding(false)
119 decipher.end(privKeyBuf)
120
121 var plainText = decipher.read()
122 var privateKey = xor(derivedHalf1, plainText)
123
124 // verify salt matches address
125 var d = BigInteger.fromBuffer(privateKey)
126 var address = getAddress(d, compressed)
127 var checksum = hash256(address).slice(0, 4)
128 assert.deepStrictEqual(salt, checksum)
129
130 return {
131 privateKey: privateKey,
132 compressed: compressed
133 }
134}
135
136function decrypt (string, passphrase, progressCallback, scryptParams) {
137 return decryptRaw(bs58check.decode(string), passphrase, progressCallback, scryptParams)
138}
139
140function decryptECMult (buffer, passphrase, progressCallback, scryptParams) {
141 passphrase = Buffer.from(passphrase, 'utf8')
142 buffer = buffer.slice(1) // FIXME: we can avoid this
143 scryptParams = scryptParams || SCRYPT_PARAMS
144
145 var flag = buffer.readUInt8(1)
146 var compressed = (flag & 0x20) !== 0
147 var hasLotSeq = (flag & 0x04) !== 0
148
149 assert.strictEqual((flag & 0x24), flag, 'Invalid private key.')
150
151 var addressHash = buffer.slice(2, 6)
152 var ownerEntropy = buffer.slice(6, 14)
153 var ownerSalt
154
155 // 4 bytes ownerSalt if 4 bytes lot/sequence
156 if (hasLotSeq) {
157 ownerSalt = ownerEntropy.slice(0, 4)
158
159 // else, 8 bytes ownerSalt
160 } else {
161 ownerSalt = ownerEntropy
162 }
163
164 var encryptedPart1 = buffer.slice(14, 22) // First 8 bytes
165 var encryptedPart2 = buffer.slice(22, 38) // 16 bytes
166
167 var N = scryptParams.N
168 var r = scryptParams.r
169 var p = scryptParams.p
170 var preFactor = scrypt(passphrase, ownerSalt, N, r, p, 32, progressCallback)
171
172 var passFactor
173 if (hasLotSeq) {
174 var hashTarget = Buffer.concat([preFactor, ownerEntropy])
175 passFactor = hash256(hashTarget)
176 } else {
177 passFactor = preFactor
178 }
179
180 var passInt = BigInteger.fromBuffer(passFactor)
181 var passPoint = curve.G.multiply(passInt).getEncoded(true)
182
183 var seedBPass = scrypt(passPoint, Buffer.concat([addressHash, ownerEntropy]), 1024, 1, 1, 64)
184 var derivedHalf1 = seedBPass.slice(0, 32)
185 var derivedHalf2 = seedBPass.slice(32, 64)
186
187 var decipher = aes.createDecipheriv('aes-256-ecb', derivedHalf2, Buffer.alloc(0))
188 decipher.setAutoPadding(false)
189 decipher.end(encryptedPart2)
190
191 var decryptedPart2 = decipher.read()
192 var tmp = xor(decryptedPart2, derivedHalf1.slice(16, 32))
193 var seedBPart2 = tmp.slice(8, 16)
194
195 var decipher2 = aes.createDecipheriv('aes-256-ecb', derivedHalf2, Buffer.alloc(0))
196 decipher2.setAutoPadding(false)
197 decipher2.write(encryptedPart1) // first 8 bytes
198 decipher2.end(tmp.slice(0, 8)) // last 8 bytes
199
200 var seedBPart1 = xor(decipher2.read(), derivedHalf1.slice(0, 16))
201 var seedB = Buffer.concat([seedBPart1, seedBPart2], 24)
202 var factorB = BigInteger.fromBuffer(hash256(seedB))
203
204 // d = passFactor * factorB (mod n)
205 var d = passInt.multiply(factorB).mod(curve.n)
206
207 return {
208 privateKey: d.toBuffer(32),
209 compressed: compressed
210 }
211}
212
213function verify (string) {
214 var decoded = bs58check.decodeUnsafe(string)
215 if (!decoded) return false
216
217 if (decoded.length !== 39) return false
218 if (decoded.readUInt8(0) !== 0x01) return false
219
220 var type = decoded.readUInt8(1)
221 var flag = decoded.readUInt8(2)
222
223 // encrypted WIF
224 if (type === 0x42) {
225 if (flag !== 0xc0 && flag !== 0xe0) return false
226
227 // EC mult
228 } else if (type === 0x43) {
229 if ((flag & ~0x24)) return false
230 } else {
231 return false
232 }
233
234 return true
235}
236
237module.exports = {
238 decrypt: decrypt,
239 decryptECMult: decryptECMult,
240 decryptRaw: decryptRaw,
241 encrypt: encrypt,
242 encryptRaw: encryptRaw,
243 verify: verify
244}