Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ced2c0ee80 | ||
|
|
d2c134feac | ||
|
|
70ce332efe | ||
|
|
e5d6b9b395 | ||
|
|
3de4739fb8 | ||
|
|
6be4dc0c9e | ||
|
|
10cd0f34f6 | ||
|
|
f20efa6cfe | ||
|
|
b5d09f78fa | ||
|
|
73241fa0db | ||
|
|
fe962256a7 | ||
|
|
177a71dfbc | ||
|
|
4830f4ff25 | ||
|
|
3883bd2712 | ||
|
|
3d92f992ab | ||
|
|
8f007a4127 | ||
|
|
c136fe13ab | ||
|
|
6b181d629c | ||
|
|
449bc82fdf | ||
|
|
7a59d4d17e | ||
|
|
30345b382c | ||
|
|
1d4bb6a1c7 | ||
|
|
5e749858f3 | ||
|
|
1bac899785 | ||
|
|
14842a1ce3 | ||
|
|
f2ba5703df |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
logs
|
||||
coverage
|
||||
.nyc_output
|
||||
node_modules
|
||||
*.rdb
|
||||
*.log
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
2.0.3 / 2019-05-22
|
||||
------------------
|
||||
- made compatible for Electron v4
|
||||
|
||||
2.0.0 / 2016-12-20
|
||||
------------------
|
||||
- removed class instantiation. Removed `coinstring` dep.
|
||||
|
||||
@ -29,6 +29,12 @@ BIP38 is a standard process to encrypt Bitcoin and crypto currency private keys
|
||||
npm install --save bip38
|
||||
|
||||
|
||||
### Async methods
|
||||
|
||||
Async methods are available, but using them will be slower, but free up the event loop in intervals you choose.
|
||||
|
||||
For benchmark results, please see the section in the README of the [scryptsy](https://github.com/cryptocoinjs/scryptsy/tree/395c3b09b21e06ea4a6cc2933e046c0984a414c5#benchmarks) library. Increasing the interval will decrease the performance hit, but increase the span between event loop free ups (UI drawing etc.) promiseInterval is the last optional parameter after scryptParams. There is no recommendation currently, as the performance trade off is app specific.
|
||||
|
||||
### API
|
||||
### encrypt(buffer, compressed, passphrase[, progressCallback, scryptParams])
|
||||
|
||||
@ -67,4 +73,3 @@ console.log(wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed))
|
||||
- https://github.com/casascius/Bitcoin-Address-Utility/tree/master/Model
|
||||
- https://github.com/nomorecoin/python-bip38-testing/blob/master/bip38.py
|
||||
- https://github.com/pointbiz/bitaddress.org/blob/master/src/ninja.key.js
|
||||
|
||||
|
||||
211
index.js
211
index.js
@ -20,7 +20,13 @@ var SCRYPT_PARAMS = {
|
||||
var NULL = Buffer.alloc(0)
|
||||
|
||||
function hash160 (buffer) {
|
||||
return createHash('rmd160').update(
|
||||
var hash
|
||||
try {
|
||||
hash = createHash('rmd160')
|
||||
} catch (e) {
|
||||
hash = createHash('ripemd160')
|
||||
}
|
||||
return hash.update(
|
||||
createHash('sha256').update(buffer).digest()
|
||||
).digest()
|
||||
}
|
||||
@ -41,20 +47,28 @@ function getAddress (d, compressed) {
|
||||
return bs58check.encode(payload)
|
||||
}
|
||||
|
||||
function encryptRaw (buffer, compressed, passphrase, progressCallback, scryptParams) {
|
||||
function prepareEncryptRaw (buffer, compressed, passphrase, scryptParams) {
|
||||
if (buffer.length !== 32) throw new Error('Invalid private key length')
|
||||
scryptParams = scryptParams || SCRYPT_PARAMS
|
||||
|
||||
var d = BigInteger.fromBuffer(buffer)
|
||||
var address = getAddress(d, compressed)
|
||||
var secret = Buffer.from(passphrase, 'utf8')
|
||||
var secret = Buffer.from(passphrase.normalize('NFC'), 'utf8')
|
||||
var salt = hash256(address).slice(0, 4)
|
||||
|
||||
var N = scryptParams.N
|
||||
var r = scryptParams.r
|
||||
var p = scryptParams.p
|
||||
|
||||
var scryptBuf = scrypt(secret, salt, N, r, p, 64, progressCallback)
|
||||
return {
|
||||
secret,
|
||||
salt,
|
||||
N,
|
||||
r,
|
||||
p
|
||||
}
|
||||
}
|
||||
|
||||
function finishEncryptRaw (buffer, compressed, salt, scryptBuf) {
|
||||
var derivedHalf1 = scryptBuf.slice(0, 32)
|
||||
var derivedHalf2 = scryptBuf.slice(32, 64)
|
||||
|
||||
@ -76,24 +90,54 @@ function encryptRaw (buffer, compressed, passphrase, progressCallback, scryptPar
|
||||
return result
|
||||
}
|
||||
|
||||
async function encryptRawAsync (buffer, compressed, passphrase, progressCallback, scryptParams, promiseInterval) {
|
||||
scryptParams = scryptParams || SCRYPT_PARAMS
|
||||
const {
|
||||
secret,
|
||||
salt,
|
||||
N,
|
||||
r,
|
||||
p
|
||||
} = prepareEncryptRaw(buffer, compressed, passphrase, scryptParams)
|
||||
|
||||
var scryptBuf = await scrypt.async(secret, salt, N, r, p, 64, progressCallback, promiseInterval)
|
||||
|
||||
return finishEncryptRaw(buffer, compressed, salt, scryptBuf)
|
||||
}
|
||||
|
||||
function encryptRaw (buffer, compressed, passphrase, progressCallback, scryptParams) {
|
||||
scryptParams = scryptParams || SCRYPT_PARAMS
|
||||
const {
|
||||
secret,
|
||||
salt,
|
||||
N,
|
||||
r,
|
||||
p
|
||||
} = prepareEncryptRaw(buffer, compressed, passphrase, scryptParams)
|
||||
|
||||
var scryptBuf = scrypt(secret, salt, N, r, p, 64, progressCallback)
|
||||
|
||||
return finishEncryptRaw(buffer, compressed, salt, scryptBuf)
|
||||
}
|
||||
|
||||
async function encryptAsync (buffer, compressed, passphrase, progressCallback, scryptParams, promiseInterval) {
|
||||
return bs58check.encode(await encryptRawAsync(buffer, compressed, passphrase, progressCallback, scryptParams, promiseInterval))
|
||||
}
|
||||
|
||||
function encrypt (buffer, compressed, passphrase, progressCallback, scryptParams) {
|
||||
return bs58check.encode(encryptRaw(buffer, compressed, passphrase, progressCallback, scryptParams))
|
||||
}
|
||||
|
||||
// some of the techniques borrowed from: https://github.com/pointbiz/bitaddress.org
|
||||
function decryptRaw (buffer, passphrase, progressCallback, scryptParams) {
|
||||
function prepareDecryptRaw (buffer, progressCallback, scryptParams) {
|
||||
// 39 bytes: 2 bytes prefix, 37 bytes payload
|
||||
if (buffer.length !== 39) throw new Error('Invalid BIP38 data length')
|
||||
if (buffer.readUInt8(0) !== 0x01) throw new Error('Invalid BIP38 prefix')
|
||||
scryptParams = scryptParams || SCRYPT_PARAMS
|
||||
|
||||
// check if BIP38 EC multiply
|
||||
var type = buffer.readUInt8(1)
|
||||
if (type === 0x43) return decryptECMult(buffer, passphrase, progressCallback, scryptParams)
|
||||
if (type === 0x43) return { decryptEC: true }
|
||||
if (type !== 0x42) throw new Error('Invalid BIP38 type')
|
||||
|
||||
passphrase = Buffer.from(passphrase, 'utf8')
|
||||
|
||||
var flagByte = buffer.readUInt8(2)
|
||||
var compressed = flagByte === 0xe0
|
||||
if (!compressed && flagByte !== 0xc0) throw new Error('Invalid BIP38 compression flag')
|
||||
@ -103,7 +147,16 @@ function decryptRaw (buffer, passphrase, progressCallback, scryptParams) {
|
||||
var p = scryptParams.p
|
||||
|
||||
var salt = buffer.slice(3, 7)
|
||||
var scryptBuf = scrypt(passphrase, salt, N, r, p, 64, progressCallback)
|
||||
return {
|
||||
salt,
|
||||
compressed,
|
||||
N,
|
||||
r,
|
||||
p
|
||||
}
|
||||
}
|
||||
|
||||
function finishDecryptRaw (buffer, salt, compressed, scryptBuf) {
|
||||
var derivedHalf1 = scryptBuf.slice(0, 32)
|
||||
var derivedHalf2 = scryptBuf.slice(32, 64)
|
||||
|
||||
@ -119,7 +172,7 @@ function decryptRaw (buffer, passphrase, progressCallback, scryptParams) {
|
||||
var d = BigInteger.fromBuffer(privateKey)
|
||||
var address = getAddress(d, compressed)
|
||||
var checksum = hash256(address).slice(0, 4)
|
||||
assert.deepEqual(salt, checksum)
|
||||
assert.deepStrictEqual(salt, checksum)
|
||||
|
||||
return {
|
||||
privateKey: privateKey,
|
||||
@ -127,20 +180,52 @@ function decryptRaw (buffer, passphrase, progressCallback, scryptParams) {
|
||||
}
|
||||
}
|
||||
|
||||
async function decryptRawAsync (buffer, passphrase, progressCallback, scryptParams, promiseInterval) {
|
||||
scryptParams = scryptParams || SCRYPT_PARAMS
|
||||
const {
|
||||
salt,
|
||||
compressed,
|
||||
N,
|
||||
r,
|
||||
p,
|
||||
decryptEC
|
||||
} = prepareDecryptRaw(buffer, progressCallback, scryptParams)
|
||||
if (decryptEC === true) return decryptECMultAsync(buffer, passphrase, progressCallback, scryptParams, promiseInterval)
|
||||
|
||||
var scryptBuf = await scrypt.async(passphrase.normalize('NFC'), salt, N, r, p, 64, progressCallback, promiseInterval)
|
||||
return finishDecryptRaw(buffer, salt, compressed, scryptBuf)
|
||||
}
|
||||
|
||||
// some of the techniques borrowed from: https://github.com/pointbiz/bitaddress.org
|
||||
function decryptRaw (buffer, passphrase, progressCallback, scryptParams) {
|
||||
scryptParams = scryptParams || SCRYPT_PARAMS
|
||||
const {
|
||||
salt,
|
||||
compressed,
|
||||
N,
|
||||
r,
|
||||
p,
|
||||
decryptEC
|
||||
} = prepareDecryptRaw(buffer, progressCallback, scryptParams)
|
||||
if (decryptEC === true) return decryptECMult(buffer, passphrase, progressCallback, scryptParams)
|
||||
var scryptBuf = scrypt(passphrase.normalize('NFC'), salt, N, r, p, 64, progressCallback)
|
||||
return finishDecryptRaw(buffer, salt, compressed, scryptBuf)
|
||||
}
|
||||
|
||||
async function decryptAsync (string, passphrase, progressCallback, scryptParams, promiseInterval) {
|
||||
return decryptRawAsync(bs58check.decode(string), passphrase, progressCallback, scryptParams, promiseInterval)
|
||||
}
|
||||
|
||||
function decrypt (string, passphrase, progressCallback, scryptParams) {
|
||||
return decryptRaw(bs58check.decode(string), passphrase, progressCallback, scryptParams)
|
||||
}
|
||||
|
||||
function decryptECMult (buffer, passphrase, progressCallback, scryptParams) {
|
||||
passphrase = Buffer.from(passphrase, 'utf8')
|
||||
buffer = buffer.slice(1) // FIXME: we can avoid this
|
||||
scryptParams = scryptParams || SCRYPT_PARAMS
|
||||
|
||||
function prepareDecryptECMult (buffer, passphrase, progressCallback, scryptParams) {
|
||||
var flag = buffer.readUInt8(1)
|
||||
var compressed = (flag & 0x20) !== 0
|
||||
var hasLotSeq = (flag & 0x04) !== 0
|
||||
|
||||
assert.equal((flag & 0x24), flag, 'Invalid private key.')
|
||||
assert.strictEqual((flag & 0x24), flag, 'Invalid private key.')
|
||||
|
||||
var addressHash = buffer.slice(2, 6)
|
||||
var ownerEntropy = buffer.slice(6, 14)
|
||||
@ -161,8 +246,21 @@ function decryptECMult (buffer, passphrase, progressCallback, scryptParams) {
|
||||
var N = scryptParams.N
|
||||
var r = scryptParams.r
|
||||
var p = scryptParams.p
|
||||
var preFactor = scrypt(passphrase, ownerSalt, N, r, p, 32, progressCallback)
|
||||
return {
|
||||
addressHash,
|
||||
encryptedPart1,
|
||||
encryptedPart2,
|
||||
ownerEntropy,
|
||||
ownerSalt,
|
||||
hasLotSeq,
|
||||
compressed,
|
||||
N,
|
||||
r,
|
||||
p
|
||||
}
|
||||
}
|
||||
|
||||
function getPassIntAndPoint (preFactor, ownerEntropy, hasLotSeq) {
|
||||
var passFactor
|
||||
if (hasLotSeq) {
|
||||
var hashTarget = Buffer.concat([preFactor, ownerEntropy])
|
||||
@ -170,11 +268,14 @@ function decryptECMult (buffer, passphrase, progressCallback, scryptParams) {
|
||||
} else {
|
||||
passFactor = preFactor
|
||||
}
|
||||
const passInt = BigInteger.fromBuffer(passFactor)
|
||||
return {
|
||||
passInt,
|
||||
passPoint: curve.G.multiply(passInt).getEncoded(true)
|
||||
}
|
||||
}
|
||||
|
||||
var passInt = BigInteger.fromBuffer(passFactor)
|
||||
var passPoint = curve.G.multiply(passInt).getEncoded(true)
|
||||
|
||||
var seedBPass = scrypt(passPoint, Buffer.concat([addressHash, ownerEntropy]), 1024, 1, 1, 64)
|
||||
function finishDecryptECMult (seedBPass, encryptedPart1, encryptedPart2, passInt, compressed) {
|
||||
var derivedHalf1 = seedBPass.slice(0, 32)
|
||||
var derivedHalf2 = seedBPass.slice(32, 64)
|
||||
|
||||
@ -204,6 +305,63 @@ function decryptECMult (buffer, passphrase, progressCallback, scryptParams) {
|
||||
}
|
||||
}
|
||||
|
||||
async function decryptECMultAsync (buffer, passphrase, progressCallback, scryptParams, promiseInterval) {
|
||||
buffer = buffer.slice(1) // FIXME: we can avoid this
|
||||
passphrase = Buffer.from(passphrase.normalize('NFC'), 'utf8')
|
||||
scryptParams = scryptParams || SCRYPT_PARAMS
|
||||
const {
|
||||
addressHash,
|
||||
encryptedPart1,
|
||||
encryptedPart2,
|
||||
ownerEntropy,
|
||||
ownerSalt,
|
||||
hasLotSeq,
|
||||
compressed,
|
||||
N,
|
||||
r,
|
||||
p
|
||||
} = prepareDecryptECMult(buffer, passphrase, progressCallback, scryptParams)
|
||||
|
||||
var preFactor = await scrypt.async(passphrase, ownerSalt, N, r, p, 32, progressCallback, promiseInterval)
|
||||
|
||||
const {
|
||||
passInt,
|
||||
passPoint
|
||||
} = getPassIntAndPoint(preFactor, ownerEntropy, hasLotSeq)
|
||||
|
||||
var seedBPass = await scrypt.async(passPoint, Buffer.concat([addressHash, ownerEntropy]), 1024, 1, 1, 64, undefined, promiseInterval)
|
||||
|
||||
return finishDecryptECMult(seedBPass, encryptedPart1, encryptedPart2, passInt, compressed)
|
||||
}
|
||||
|
||||
function decryptECMult (buffer, passphrase, progressCallback, scryptParams) {
|
||||
buffer = buffer.slice(1) // FIXME: we can avoid this
|
||||
passphrase = Buffer.from(passphrase.normalize('NFC'), 'utf8')
|
||||
scryptParams = scryptParams || SCRYPT_PARAMS
|
||||
const {
|
||||
addressHash,
|
||||
encryptedPart1,
|
||||
encryptedPart2,
|
||||
ownerEntropy,
|
||||
ownerSalt,
|
||||
hasLotSeq,
|
||||
compressed,
|
||||
N,
|
||||
r,
|
||||
p
|
||||
} = prepareDecryptECMult(buffer, passphrase, progressCallback, scryptParams)
|
||||
var preFactor = scrypt(passphrase, ownerSalt, N, r, p, 32, progressCallback)
|
||||
|
||||
const {
|
||||
passInt,
|
||||
passPoint
|
||||
} = getPassIntAndPoint(preFactor, ownerEntropy, hasLotSeq)
|
||||
|
||||
var seedBPass = scrypt(passPoint, Buffer.concat([addressHash, ownerEntropy]), 1024, 1, 1, 64)
|
||||
|
||||
return finishDecryptECMult(seedBPass, encryptedPart1, encryptedPart2, passInt, compressed)
|
||||
}
|
||||
|
||||
function verify (string) {
|
||||
var decoded = bs58check.decodeUnsafe(string)
|
||||
if (!decoded) return false
|
||||
@ -234,5 +392,10 @@ module.exports = {
|
||||
decryptRaw: decryptRaw,
|
||||
encrypt: encrypt,
|
||||
encryptRaw: encryptRaw,
|
||||
decryptAsync: decryptAsync,
|
||||
decryptECMultAsync: decryptECMultAsync,
|
||||
decryptRawAsync: decryptRawAsync,
|
||||
encryptAsync: encryptAsync,
|
||||
encryptRawAsync: encryptRawAsync,
|
||||
verify: verify
|
||||
}
|
||||
|
||||
7479
package-lock.json
generated
Normal file
7479
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bip38",
|
||||
"version": "2.0.2",
|
||||
"version": "3.1.0",
|
||||
"description": "BIP38 is a standard process to encrypt Bitcoin and crypto currency private keys that is impervious to brute force attacks thus protecting the user.",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
@ -18,14 +18,14 @@
|
||||
"buffer-xor": "^1.0.2",
|
||||
"create-hash": "^1.1.1",
|
||||
"ecurve": "^1.0.0",
|
||||
"scryptsy": "^2.0.0"
|
||||
"scryptsy": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"coveralls": "^2.10.0",
|
||||
"istanbul": "^0.2.11",
|
||||
"mocha": "^2.3.3",
|
||||
"mochify": "^2.1.1",
|
||||
"standard": "^9.0.2",
|
||||
"coveralls": "^3.0.4",
|
||||
"mocha": "^6.1.4",
|
||||
"mochify": "^6.2.0",
|
||||
"nyc": "^14.1.1",
|
||||
"standard": "^12.0.1",
|
||||
"wif": "^2.0.1"
|
||||
},
|
||||
"repository": {
|
||||
@ -35,7 +35,7 @@
|
||||
"scripts": {
|
||||
"browser-test": "mochify --wd -R spec --timeout 100000",
|
||||
"ci:test": "npm run standard && npm run coveralls",
|
||||
"coverage": "istanbul cover _mocha -- --reporter list test/*.js",
|
||||
"coverage": "nyc --check-coverage --reporter=lcov --reporter=text mocha",
|
||||
"coveralls": "npm run-script coverage && coveralls < coverage/lcov.info",
|
||||
"standard": "standard",
|
||||
"test": "npm run standard && npm run unit",
|
||||
|
||||
@ -14,6 +14,13 @@
|
||||
"address": "1AvKt49sui9zfzGeo8EyL8ypvAhtR2KwbL",
|
||||
"description": "no EC multiply / no compression #2"
|
||||
},
|
||||
{
|
||||
"passphrase": "\\u03D2\\u0301\\u{0000}\\u{00010400}\\u{0001F4A9}",
|
||||
"bip38": "6PRW5o9FLp4gJDDVqJQKJFTpMvdsSGJxMYHtHaQBF3ooa8mwD69bapcDQn",
|
||||
"wif": "5Jajm8eQ22H3pGWLEVCXyvND8dQZhiQhoLJNKjYXk9roUFTMSZ4",
|
||||
"address": "16ktGzmfrurhbhi6JGqsMWf7TyqK9HNAeF",
|
||||
"description": "no EC multiply / no compression #3"
|
||||
},
|
||||
{
|
||||
"passphrase": "TestingOneTwoThree",
|
||||
"bip38": "6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo",
|
||||
|
||||
@ -6,15 +6,23 @@ var bs58check = require('bs58check')
|
||||
var fixtures = require('./fixtures')
|
||||
var wif = require('wif')
|
||||
|
||||
function replaceUnicode (str) {
|
||||
var map = {
|
||||
'\\u03D2\\u0301\\u{0000}\\u{00010400}\\u{0001F4A9}': '\u03D2\u0301\u{0000}\u{00010400}\u{0001F4A9}'
|
||||
}
|
||||
if (map[str]) str = map[str]
|
||||
return str
|
||||
}
|
||||
|
||||
describe('bip38', function () {
|
||||
this.timeout(200000)
|
||||
|
||||
describe('decrypt', function () {
|
||||
fixtures.valid.forEach(function (f) {
|
||||
it('should decrypt ' + f.description, function () {
|
||||
var result = bip38.decrypt(f.bip38, f.passphrase)
|
||||
var result = bip38.decrypt(f.bip38, replaceUnicode(f.passphrase))
|
||||
|
||||
assert.equal(wif.encode(0x80, result.privateKey, result.compressed), f.wif)
|
||||
assert.strictEqual(wif.encode(0x80, result.privateKey, result.compressed), f.wif)
|
||||
})
|
||||
})
|
||||
|
||||
@ -42,7 +50,45 @@ describe('bip38', function () {
|
||||
it('should encrypt ' + f.description, function () {
|
||||
var buffer = bs58check.decode(f.wif)
|
||||
|
||||
assert.equal(bip38.encrypt(buffer.slice(1, 33), !!buffer[33], f.passphrase), f.bip38)
|
||||
assert.strictEqual(bip38.encrypt(buffer.slice(1, 33), !!buffer[33], replaceUnicode(f.passphrase)), f.bip38)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('decryptAsync', function () {
|
||||
fixtures.valid.forEach(function (f) {
|
||||
it('should decrypt ' + f.description, async function () {
|
||||
var result = await bip38.decryptAsync(f.bip38, replaceUnicode(f.passphrase))
|
||||
|
||||
assert.strictEqual(wif.encode(0x80, result.privateKey, result.compressed), f.wif)
|
||||
})
|
||||
})
|
||||
|
||||
fixtures.invalid.decrypt.forEach(function (f) {
|
||||
it('should throw ' + f.description, async function () {
|
||||
assert.rejects(async function () {
|
||||
await bip38.decryptAsync(f.bip38, replaceUnicode(f.passphrase))
|
||||
}, new RegExp(f.description, 'i'))
|
||||
})
|
||||
})
|
||||
|
||||
fixtures.invalid.verify.forEach(function (f) {
|
||||
it('should throw because ' + f.description, async function () {
|
||||
assert.rejects(async function () {
|
||||
await bip38.decryptAsync(f.base58, 'foobar')
|
||||
}, new RegExp(f.exception))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('encryptAsync', function () {
|
||||
fixtures.valid.forEach(function (f) {
|
||||
if (f.decryptOnly) return
|
||||
|
||||
it('should encrypt ' + f.description, async function () {
|
||||
var buffer = bs58check.decode(f.wif)
|
||||
|
||||
assert.strictEqual(await bip38.encryptAsync(buffer.slice(1, 33), !!buffer[33], replaceUnicode(f.passphrase)), f.bip38)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user