Compare commits
No commits in common. "addAsync" and "fixNormalization" have entirely different histories.
addAsync
...
fixNormali
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,5 @@
|
||||
logs
|
||||
coverage
|
||||
.nyc_output
|
||||
node_modules
|
||||
*.rdb
|
||||
*.log
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
197
index.js
197
index.js
@ -47,8 +47,9 @@ 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)
|
||||
@ -59,16 +60,7 @@ function prepareEncryptRaw (buffer, compressed, passphrase, scryptParams) {
|
||||
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 +82,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.normalize('NFC'), 'utf8')
|
||||
|
||||
var flagByte = buffer.readUInt8(2)
|
||||
var compressed = flagByte === 0xe0
|
||||
if (!compressed && flagByte !== 0xc0) throw new Error('Invalid BIP38 compression flag')
|
||||
@ -147,16 +109,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)
|
||||
|
||||
@ -180,47 +133,15 @@ 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.normalize('NFC'), '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
|
||||
@ -246,21 +167,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 +176,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 +210,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 +240,5 @@ 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
1077
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bip38",
|
||||
"version": "3.1.0",
|
||||
"version": "3.0.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.1.0"
|
||||
"scryptsy": "^2.0.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": "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",
|
||||
|
||||
@ -55,44 +55,6 @@ 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 () {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user