Compare commits

..

12 Commits

Author SHA1 Message Date
junderw
ced2c0ee80
3.1.0 2020-04-09 12:46:25 +09:00
junderw
d2c134feac
Add mention of Async 2020-04-09 12:43:18 +09:00
junderw
70ce332efe
Fix normalization for Async 2020-04-09 12:35:37 +09:00
junderw
e5d6b9b395
Switch to nyc 2020-04-09 11:44:08 +09:00
junderw
3de4739fb8
Fix standard 2020-04-09 11:42:28 +09:00
junderw
6be4dc0c9e
Add async 2020-04-09 11:42:28 +09:00
junderw
10cd0f34f6
Fix dependencies for vuln 2020-03-19 07:56:32 +09:00
Jonathan Underwood
f20efa6cfe
Merge pull request #58 from bitcoinjs/dependabot/npm_and_yarn/acorn-5.7.4
build(deps): bump acorn from 5.7.3 to 5.7.4
2020-03-19 07:11:43 +09:00
dependabot[bot]
b5d09f78fa
build(deps): bump acorn from 5.7.3 to 5.7.4
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-14 06:22:50 +00:00
Jonathan Underwood
73241fa0db
Merge pull request #57 from bitcoinjs/dependabot/npm_and_yarn/handlebars-4.5.3
build(deps): bump handlebars from 4.1.2 to 4.5.3
2020-01-06 11:37:21 +09:00
dependabot[bot]
fe962256a7
build(deps): bump handlebars from 4.1.2 to 4.5.3
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.1.2 to 4.5.3.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.1.2...v4.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-28 03:22:22 +00:00
Jonathan Underwood
177a71dfbc
Merge pull request #56 from bitcoinjs/fixNormalization
Fix normalization
2019-09-12 15:02:37 +09:00
6 changed files with 1074 additions and 254 deletions

1
.gitignore vendored
View File

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

View File

@ -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

197
index.js
View File

@ -47,9 +47,8 @@ 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)
@ -60,7 +59,16 @@ function encryptRaw (buffer, compressed, passphrase, progressCallback, scryptPar
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)
@ -82,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.normalize('NFC'), 'utf8')
var flagByte = buffer.readUInt8(2)
var compressed = flagByte === 0xe0
if (!compressed && flagByte !== 0xc0) throw new Error('Invalid BIP38 compression flag')
@ -109,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)
@ -133,15 +180,47 @@ 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.normalize('NFC'), '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
@ -167,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])
@ -176,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)
@ -210,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
@ -240,5 +392,10 @@ module.exports = {
decryptRaw: decryptRaw,
encrypt: encrypt,
encryptRaw: encryptRaw,
decryptAsync: decryptAsync,
decryptECMultAsync: decryptECMultAsync,
decryptRawAsync: decryptRawAsync,
encryptAsync: encryptAsync,
encryptRawAsync: encryptRawAsync,
verify: verify
}

1077
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "bip38",
"version": "3.0.0",
"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,13 +18,13 @@
"buffer-xor": "^1.0.2",
"create-hash": "^1.1.1",
"ecurve": "^1.0.0",
"scryptsy": "^2.0.0"
"scryptsy": "^2.1.0"
},
"devDependencies": {
"coveralls": "^3.0.4",
"istanbul": "^0.4.5",
"mocha": "^6.1.4",
"mochify": "^6.2.0",
"nyc": "^14.1.1",
"standard": "^12.0.1",
"wif": "^2.0.1"
},
@ -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",

View File

@ -55,6 +55,44 @@ describe('bip38', function () {
})
})
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)
})
})
})
describe('verify', function () {
fixtures.valid.forEach(function (f) {
it('should return true for ' + f.bip38, function () {