]>
git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - libs/bitcoinjs-bip38/index.js
1 var aes
= require('browserify-aes')
2 var assert
= require('assert')
3 var Buffer
= require('safe-buffer').Buffer
4 var bs58check
= require('bs58check')
5 var createHash
= require('create-hash')
6 var scrypt
= require('scryptsy')
7 var xor
= require('buffer-xor/inplace')
9 var ecurve
= require('ecurve')
10 var curve
= ecurve
.getCurveByName('secp256k1')
12 var BigInteger
= require('bigi')
16 N: 16384, // specified by BIP38
20 var NULL
= Buffer
.alloc(0)
22 function hash160 (buffer
) {
25 hash
= createHash('rmd160')
27 hash
= createHash('ripemd160')
30 createHash('sha256').update(buffer
).digest()
34 function hash256 (buffer
) {
35 return createHash('sha256').update(
36 createHash('sha256').update(buffer
).digest()
40 function getAddress (d
, compressed
) {
41 var Q
= curve
.G
.multiply(d
).getEncoded(compressed
)
43 var payload
= Buffer
.allocUnsafe(21)
44 payload
.writeUInt8(0x00, 0) // XXX TODO FIXME bitcoin only??? damn you BIP38
47 return bs58check
.encode(payload
)
50 function encryptRaw (buffer
, compressed
, passphrase
, progressCallback
, scryptParams
) {
51 if (buffer
.length
!== 32) throw new Error('Invalid private key length')
52 scryptParams
= scryptParams
|| SCRYPT_PARAMS
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)
59 var N
= scryptParams
.N
60 var r
= scryptParams
.r
61 var p
= scryptParams
.p
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)
67 var xorBuf
= xor(derivedHalf1
, buffer
)
68 var cipher
= aes
.createCipheriv('aes-256-ecb', derivedHalf2
, NULL
)
69 cipher
.setAutoPadding(false)
72 var cipherText
= cipher
.read()
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)
80 cipherText
.copy(result
, 7)
85 function encrypt (buffer
, compressed
, passphrase
, progressCallback
, scryptParams
) {
86 return bs58check
.encode(encryptRaw(buffer
, compressed
, passphrase
, progressCallback
, scryptParams
))
89 // some of the techniques borrowed from: https://github.com/pointbiz/bitaddress.org
90 function 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
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')
101 passphrase
= Buffer
.from(passphrase
, 'utf8')
103 var flagByte
= buffer
.readUInt8(2)
104 var compressed
= flagByte
=== 0xe0
105 if (!compressed
&& flagByte
!== 0xc0) throw new Error('Invalid BIP38 compression flag')
107 var N
= scryptParams
.N
108 var r
= scryptParams
.r
109 var p
= scryptParams
.p
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)
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
)
121 var plainText
= decipher
.read()
122 var privateKey
= xor(derivedHalf1
, plainText
)
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
)
131 privateKey: privateKey
,
132 compressed: compressed
136 function decrypt (string
, passphrase
, progressCallback
, scryptParams
) {
137 return decryptRaw(bs58check
.decode(string
), passphrase
, progressCallback
, scryptParams
)
140 function 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
145 var flag
= buffer
.readUInt8(1)
146 var compressed
= (flag
& 0x20) !== 0
147 var hasLotSeq
= (flag
& 0x04) !== 0
149 assert
.strictEqual((flag
& 0x24), flag
, 'Invalid private key.')
151 var addressHash
= buffer
.slice(2, 6)
152 var ownerEntropy
= buffer
.slice(6, 14)
155 // 4 bytes ownerSalt if 4 bytes lot/sequence
157 ownerSalt
= ownerEntropy
.slice(0, 4)
159 // else, 8 bytes ownerSalt
161 ownerSalt
= ownerEntropy
164 var encryptedPart1
= buffer
.slice(14, 22) // First 8 bytes
165 var encryptedPart2
= buffer
.slice(22, 38) // 16 bytes
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
)
174 var hashTarget
= Buffer
.concat([preFactor
, ownerEntropy
])
175 passFactor
= hash256(hashTarget
)
177 passFactor
= preFactor
180 var passInt
= BigInteger
.fromBuffer(passFactor
)
181 var passPoint
= curve
.G
.multiply(passInt
).getEncoded(true)
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)
187 var decipher
= aes
.createDecipheriv('aes-256-ecb', derivedHalf2
, Buffer
.alloc(0))
188 decipher
.setAutoPadding(false)
189 decipher
.end(encryptedPart2
)
191 var decryptedPart2
= decipher
.read()
192 var tmp
= xor(decryptedPart2
, derivedHalf1
.slice(16, 32))
193 var seedBPart2
= tmp
.slice(8, 16)
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
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
))
204 // d = passFactor * factorB (mod n)
205 var d
= passInt
.multiply(factorB
).mod(curve
.n
)
208 privateKey: d
.toBuffer(32),
209 compressed: compressed
213 function verify (string
) {
214 var decoded
= bs58check
.decodeUnsafe(string
)
215 if (!decoded
) return false
217 if (decoded
.length
!== 39) return false
218 if (decoded
.readUInt8(0) !== 0x01) return false
220 var type
= decoded
.readUInt8(1)
221 var flag
= decoded
.readUInt8(2)
225 if (flag
!== 0xc0 && flag
!== 0xe0) return false
228 } else if (type
=== 0x43) {
229 if ((flag
& ~0x24)) return false
239 decryptECMult: decryptECMult
,
240 decryptRaw: decryptRaw
,
242 encryptRaw: encryptRaw
,