Compare commits

..

No commits in common. "addAsync" and "fixTravis" have entirely different histories.

8 changed files with 36 additions and 7741 deletions

1
.gitignore vendored
View File

@ -1,6 +1,5 @@
logs
coverage
.nyc_output
node_modules
*.rdb
*.log

View File

@ -1,7 +1,3 @@
2.0.3 / 2019-05-22
------------------
- made compatible for Electron v4
2.0.0 / 2016-12-20
------------------
- removed class instantiation. Removed `coinstring` dep.

View File

@ -29,12 +29,6 @@ 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])
@ -73,3 +67,4 @@ 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
View File

@ -20,13 +20,7 @@ var SCRYPT_PARAMS = {
var NULL = Buffer.alloc(0)
function hash160 (buffer) {
var hash
try {
hash = createHash('rmd160')
} catch (e) {
hash = createHash('ripemd160')
}
return hash.update(
return createHash('rmd160').update(
createHash('sha256').update(buffer).digest()
).digest()
}
@ -47,28 +41,20 @@ function getAddress (d, compressed) {
return bs58check.encode(payload)
}
function prepareEncryptRaw (buffer, compressed, passphrase, scryptParams) {
function encryptRaw (buffer, compressed, passphrase, progressCallback, 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.normalize('NFC'), 'utf8')
var secret = Buffer.from(passphrase, 'utf8')
var salt = hash256(address).slice(0, 4)
var N = scryptParams.N
var r = scryptParams.r
var p = scryptParams.p
return {
secret,
salt,
N,
r,
p
}
}
function finishEncryptRaw (buffer, compressed, salt, scryptBuf) {
var scryptBuf = scrypt(secret, salt, N, r, p, 64, progressCallback)
var derivedHalf1 = scryptBuf.slice(0, 32)
var derivedHalf2 = scryptBuf.slice(32, 64)
@ -90,54 +76,24 @@ function finishEncryptRaw (buffer, compressed, salt, scryptBuf) {
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))
}
function prepareDecryptRaw (buffer, progressCallback, scryptParams) {
// some of the techniques borrowed from: https://github.com/pointbiz/bitaddress.org
function decryptRaw (buffer, passphrase, 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 { decryptEC: true }
if (type === 0x43) return decryptECMult(buffer, passphrase, progressCallback, scryptParams)
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')
@ -147,16 +103,7 @@ function prepareDecryptRaw (buffer, progressCallback, scryptParams) {
var p = scryptParams.p
var salt = buffer.slice(3, 7)
return {
salt,
compressed,
N,
r,
p
}
}
function finishDecryptRaw (buffer, salt, compressed, scryptBuf) {
var scryptBuf = scrypt(passphrase, salt, N, r, p, 64, progressCallback)
var derivedHalf1 = scryptBuf.slice(0, 32)
var derivedHalf2 = scryptBuf.slice(32, 64)
@ -172,7 +119,7 @@ function finishDecryptRaw (buffer, salt, compressed, scryptBuf) {
var d = BigInteger.fromBuffer(privateKey)
var address = getAddress(d, compressed)
var checksum = hash256(address).slice(0, 4)
assert.deepStrictEqual(salt, checksum)
assert.deepEqual(salt, checksum)
return {
privateKey: privateKey,
@ -180,52 +127,20 @@ function finishDecryptRaw (buffer, salt, compressed, scryptBuf) {
}
}
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 prepareDecryptECMult (buffer, 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
var flag = buffer.readUInt8(1)
var compressed = (flag & 0x20) !== 0
var hasLotSeq = (flag & 0x04) !== 0
assert.strictEqual((flag & 0x24), flag, 'Invalid private key.')
assert.equal((flag & 0x24), flag, 'Invalid private key.')
var addressHash = buffer.slice(2, 6)
var ownerEntropy = buffer.slice(6, 14)
@ -246,21 +161,8 @@ function prepareDecryptECMult (buffer, passphrase, progressCallback, scryptParam
var N = scryptParams.N
var r = scryptParams.r
var p = scryptParams.p
return {
addressHash,
encryptedPart1,
encryptedPart2,
ownerEntropy,
ownerSalt,
hasLotSeq,
compressed,
N,
r,
p
}
}
var preFactor = scrypt(passphrase, ownerSalt, N, r, p, 32, progressCallback)
function getPassIntAndPoint (preFactor, ownerEntropy, hasLotSeq) {
var passFactor
if (hasLotSeq) {
var hashTarget = Buffer.concat([preFactor, ownerEntropy])
@ -268,14 +170,11 @@ function getPassIntAndPoint (preFactor, ownerEntropy, hasLotSeq) {
} else {
passFactor = preFactor
}
const passInt = BigInteger.fromBuffer(passFactor)
return {
passInt,
passPoint: curve.G.multiply(passInt).getEncoded(true)
}
}
function finishDecryptECMult (seedBPass, encryptedPart1, encryptedPart2, passInt, compressed) {
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)
var derivedHalf1 = seedBPass.slice(0, 32)
var derivedHalf2 = seedBPass.slice(32, 64)
@ -305,63 +204,6 @@ function finishDecryptECMult (seedBPass, encryptedPart1, encryptedPart2, passInt
}
}
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
@ -392,10 +234,5 @@ 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

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "bip38",
"version": "3.1.0",
"version": "2.0.2",
"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.1.0"
"scryptsy": "^2.0.0"
},
"devDependencies": {
"coveralls": "^3.0.4",
"mocha": "^6.1.4",
"mochify": "^6.2.0",
"nyc": "^14.1.1",
"standard": "^12.0.1",
"coveralls": "^2.10.0",
"istanbul": "^0.2.11",
"mocha": "^2.3.3",
"mochify": "^2.1.1",
"standard": "^9.0.2",
"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": "nyc --check-coverage --reporter=lcov --reporter=text mocha",
"coverage": "istanbul cover _mocha -- --reporter list test/*.js",
"coveralls": "npm run-script coverage && coveralls < coverage/lcov.info",
"standard": "standard",
"test": "npm run standard && npm run unit",

View File

@ -14,13 +14,6 @@
"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",

View File

@ -6,23 +6,15 @@ 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, replaceUnicode(f.passphrase))
var result = bip38.decrypt(f.bip38, f.passphrase)
assert.strictEqual(wif.encode(0x80, result.privateKey, result.compressed), f.wif)
assert.equal(wif.encode(0x80, result.privateKey, result.compressed), f.wif)
})
})
@ -50,45 +42,7 @@ describe('bip38', function () {
it('should encrypt ' + f.description, function () {
var buffer = bs58check.decode(f.wif)
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)
assert.equal(bip38.encrypt(buffer.slice(1, 33), !!buffer[33], f.passphrase), f.bip38)
})
})
})