Do not use promises in crypto
Because sha.js is faster than WebCryptoAPI for POW.
This commit is contained in:
parent
f6165d891b
commit
bf8b663c5d
17
README.md
17
README.md
|
@ -12,14 +12,6 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
|
||||||
* [Protocol specification](https://bitmessage.org/wiki/Protocol_specification)
|
* [Protocol specification](https://bitmessage.org/wiki/Protocol_specification)
|
||||||
* [Whitepaper](https://bitmessage.org/bitmessage.pdf)
|
* [Whitepaper](https://bitmessage.org/bitmessage.pdf)
|
||||||
|
|
||||||
## Implementation details
|
|
||||||
|
|
||||||
With the help of browserify `bitmessage` provides different implementations for Browser and Node.js with the same API. Because WebCryptoAPI defines asynchronous promise-driven API, implementation for Node needs to use promises too.
|
|
||||||
|
|
||||||
* Use Node.js crypto module/library bindings where possible
|
|
||||||
* Use WebCryptoAPI where possible
|
|
||||||
* Promise-driven API
|
|
||||||
|
|
||||||
## Feature matrix (both Browser and Node)
|
## Feature matrix (both Browser and Node)
|
||||||
|
|
||||||
- [ ] crypto
|
- [ ] crypto
|
||||||
|
@ -83,13 +75,10 @@ With the help of browserify `bitmessage` provides different implementations for
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// Generating a new Bitmessage identity.
|
// Generate a new random Bitmessage identity.
|
||||||
var Address = require("bitmessage").Address;
|
var Address = require("bitmessage").Address;
|
||||||
Address.fromRandom().then(function(addr) {
|
var addr = Address.fromRandom();
|
||||||
addr.encode().then(function(str) {
|
console.log("New random Bitmessage address:", addr.encode());
|
||||||
console.log("New random Bitmessage address:", str);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
218
lib/address.js
218
lib/address.js
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
require("es6-promise").polyfill();
|
|
||||||
require("object.assign").shim();
|
require("object.assign").shim();
|
||||||
var assert = require("assert");
|
var assert = require("assert");
|
||||||
var bufferEqual = require("buffer-equal");
|
var bufferEqual = require("buffer-equal");
|
||||||
|
@ -30,14 +29,20 @@ function Address(opts) {
|
||||||
assert(this.version >= 1, "Version too low");
|
assert(this.version >= 1, "Version too low");
|
||||||
this.stream = this.stream || 1;
|
this.stream = this.stream || 1;
|
||||||
if (this.ripe) {
|
if (this.ripe) {
|
||||||
assertripelen(getripelen(this.ripe), this.version);
|
assertripelen(getripelen(this.ripe), this.version, this.ripe);
|
||||||
|
if (this.ripe.length < 20) {
|
||||||
|
var fullripe = new Buffer(20);
|
||||||
|
fullripe.fill(0);
|
||||||
|
this.ripe.copy(fullripe, 20 - this.ripe.length);
|
||||||
|
this.ripe = fullripe;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse Bitmessage address into address object.
|
* Parse Bitmessage address into address object.
|
||||||
* @param {String} str - Address string (with or without `BM-` prefix)
|
* @param {String} str - Address string (with or without `BM-` prefix)
|
||||||
* @return {Promise.<Address>} Decoded address object.
|
* @return {Address} Decoded address object.
|
||||||
*/
|
*/
|
||||||
Address.decode = function(str) {
|
Address.decode = function(str) {
|
||||||
str = str.trim();
|
str = str.trim();
|
||||||
|
@ -45,47 +50,29 @@ Address.decode = function(str) {
|
||||||
str = str.slice(3);
|
str = str.slice(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytes;
|
var bytes = bs58.decode(str);
|
||||||
try {
|
|
||||||
bytes = bs58.decode(str);
|
|
||||||
} catch(e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checksum validating.
|
|
||||||
var data = new Buffer(bytes.slice(0, -4));
|
var data = new Buffer(bytes.slice(0, -4));
|
||||||
var checksum = new Buffer(bytes.slice(-4));
|
var checksum = new Buffer(bytes.slice(-4));
|
||||||
return getchecksum(data).then(function(realchecksum) {
|
assert(bufferEqual(checksum, getchecksum(data)), "Bad checkum");
|
||||||
assert(bufferEqual(checksum, realchecksum), "Bad checkum");
|
|
||||||
|
|
||||||
var decoded = var_int.decode(data);
|
var decoded = var_int.decode(data);
|
||||||
var version = decoded.value;
|
var version = decoded.value;
|
||||||
|
|
||||||
data = decoded.rest;
|
data = decoded.rest;
|
||||||
decoded = var_int.decode(data);
|
decoded = var_int.decode(data);
|
||||||
var stream = decoded.value;
|
var stream = decoded.value;
|
||||||
|
|
||||||
var ripe = decoded.rest;
|
var ripe = decoded.rest;
|
||||||
var ripelen = ripe.length;
|
if (version === 4) {
|
||||||
if (version === 4) {
|
assert(ripe[0] !== 0, "Ripe encode error");
|
||||||
assert(ripe[0] !== 0, "Ripe encode error");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent extra allocation. God, kill me please for premature
|
return new Address({version: version, stream: stream, ripe: ripe});
|
||||||
// optimizations.
|
|
||||||
if (ripelen < 20) {
|
|
||||||
var zeroes = new Buffer(Array(20 - ripelen));
|
|
||||||
ripe = Buffer.concat([zeroes, ripe]);
|
|
||||||
}
|
|
||||||
return new Address({version: version, stream: stream, ripe: ripe});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compute the Bitmessage checksum for the given data.
|
// Compute the Bitmessage checksum for the given data.
|
||||||
function getchecksum(data) {
|
function getchecksum(data) {
|
||||||
return bmcrypto.sha512(data).then(bmcrypto.sha512).then(function(dhash) {
|
return bmcrypto.sha512(bmcrypto.sha512(data)).slice(0, 4);
|
||||||
return dhash.slice(0, 4);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get RIPEMD160(SHA512(SIGN_PUBLIC_KEY || ENC_PUBLIC_KEY)).
|
// Get RIPEMD160(SHA512(SIGN_PUBLIC_KEY || ENC_PUBLIC_KEY)).
|
||||||
|
@ -104,39 +91,36 @@ function keys2ripe(signKey, encKey) {
|
||||||
encPublicKey = encKey;
|
encPublicKey = encKey;
|
||||||
}
|
}
|
||||||
var concat = Buffer.concat([signPublicKey, encPublicKey]);
|
var concat = Buffer.concat([signPublicKey, encPublicKey]);
|
||||||
return bmcrypto.sha512(concat).then(bmcrypto.ripemd160);
|
return bmcrypto.ripemd160(bmcrypto.sha512(concat));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the Ripe hash of the address.
|
* Calculate the ripe hash of the address.
|
||||||
* @param {?Object} opts - Options
|
* @param {?Object} opts - Options
|
||||||
* @return {Promise.<Buffer>} Resulting Ripe hash.
|
* @return {Buffer} Resulting ripe hash.
|
||||||
*/
|
*/
|
||||||
Address.prototype.getRipe = function(opts) {
|
Address.prototype.getRipe = function(opts) {
|
||||||
var self = this;
|
var ripe;
|
||||||
var ripepromise;
|
opts = opts || {};
|
||||||
if (self.ripe) {
|
if (this.ripe) {
|
||||||
ripepromise = Promise.resolve(self.ripe);
|
ripe = this.ripe;
|
||||||
} else {
|
} else {
|
||||||
opts = opts || {};
|
var signKey = this.signPrivateKey || this.signPublicKey;
|
||||||
var signKey = self.signPrivateKey || self.signPublicKey;
|
|
||||||
assert(signKey, "No signing key");
|
assert(signKey, "No signing key");
|
||||||
var encKey = self.encPrivateKey || self.encPublicKey;
|
var encKey = this.encPrivateKey || this.encPublicKey;
|
||||||
assert(encKey, "No encryption key");
|
assert(encKey, "No encryption key");
|
||||||
ripepromise = keys2ripe(signKey, encKey);
|
ripe = keys2ripe(signKey, encKey);
|
||||||
|
}
|
||||||
|
var ripelen = getripelen(ripe);
|
||||||
|
assertripelen(ripelen, this.version, ripe);
|
||||||
|
if (opts.short) {
|
||||||
|
return ripe.slice(20 - ripelen);
|
||||||
|
} else {
|
||||||
|
return ripe;
|
||||||
}
|
}
|
||||||
return ripepromise.then(function(ripe) {
|
|
||||||
var ripelen = getripelen(ripe);
|
|
||||||
assertripelen(ripelen, self.version);
|
|
||||||
if (opts.short) {
|
|
||||||
return ripe.slice(20 - ripelen);
|
|
||||||
} else {
|
|
||||||
return ripe;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get truncated Ripe hash length.
|
// Get truncated ripe hash length.
|
||||||
function getripelen(ripe) {
|
function getripelen(ripe) {
|
||||||
var zeroes = 0;
|
var zeroes = 0;
|
||||||
for (var i = 0; i < 20, ripe[i] === 0; i++) {
|
for (var i = 0; i < 20, ripe[i] === 0; i++) {
|
||||||
|
@ -145,21 +129,24 @@ function getripelen(ripe) {
|
||||||
return 20 - zeroes;
|
return 20 - zeroes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do neccessary checkings of the truncated Ripe hash length depending
|
// Do neccessary checkings of the truncated ripe hash length depending
|
||||||
// on the address version.
|
// on the address version.
|
||||||
function assertripelen(ripelen, version) {
|
function assertripelen(ripelen, version, ripe) {
|
||||||
|
if (ripe) {
|
||||||
|
assert(ripe.length <= 20, "Bad ripe");
|
||||||
|
}
|
||||||
switch (version) {
|
switch (version) {
|
||||||
case 1:
|
case 1:
|
||||||
assert(ripelen === 20, "Bad ripe length");
|
assert(ripelen === 20, "Bad ripe length");
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
case 3:
|
case 3:
|
||||||
assert(ripelen >= 18, "Ripe too short");
|
assert(ripelen >= 18, "Ripe is too short");
|
||||||
assert(ripelen <= 20, "Ripe too long");
|
assert(ripelen <= 20, "Ripe is too long");
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
assert(ripelen >= 4, "Ripe too short");
|
assert(ripelen >= 4, "Ripe is too short");
|
||||||
assert(ripelen <= 20, "Ripe too long");
|
assert(ripelen <= 20, "Ripe is too long");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Bad version");
|
throw new Error("Bad version");
|
||||||
|
@ -179,82 +166,67 @@ function checkripelen(ripelen, version) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode Bitmessage address object into address string.
|
* Encode Bitmessage address object into address string.
|
||||||
* @return {Promise.<string>} Address string.
|
* @return {string} Address string.
|
||||||
*/
|
*/
|
||||||
Address.prototype.encode = function() {
|
Address.prototype.encode = function() {
|
||||||
var self = this;
|
var ripe = this.getRipe({short: true});
|
||||||
return self.getRipe({short: true}).then(function(ripe) {
|
var data = Buffer.concat([
|
||||||
var data = Buffer.concat([
|
var_int.encode(this.version),
|
||||||
var_int.encode(self.version),
|
var_int.encode(this.stream),
|
||||||
var_int.encode(self.stream),
|
ripe,
|
||||||
ripe,
|
]);
|
||||||
]);
|
var addr = Buffer.concat([data, getchecksum(data)]);
|
||||||
return getchecksum(data).then(function(checksum) {
|
return "BM-" + bs58.encode(addr);
|
||||||
var addr = Buffer.concat([data, checksum]);
|
|
||||||
return "BM-" + bs58.encode(addr);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function popkey(obj, key) {
|
||||||
|
var value = obj[key];
|
||||||
|
delete obj[key];
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new Bitmessage address from random encryption and signing
|
* Create new Bitmessage address from random encryption and signing
|
||||||
* private keys.
|
* private keys.
|
||||||
* @param {?Object} opts - Address options
|
* @param {?Object} opts - Address options
|
||||||
* @return {Promise.<Address>} Generated address object.
|
* @return {Address} Generated address object.
|
||||||
*/
|
*/
|
||||||
Address.fromRandom = function(opts) {
|
Address.fromRandom = function(opts) {
|
||||||
opts = opts || {};
|
opts = Object.assign({}, opts);
|
||||||
var version = opts.version || 4;
|
var version = opts.version = opts.version || 4;
|
||||||
var ripelen = opts.ripelen || 19;
|
var ripelen = popkey(opts, "ripelen") || 19;
|
||||||
try {
|
assertripelen(ripelen, version);
|
||||||
assertripelen(ripelen, version);
|
// Should the generated ripe length be strictly equal to the specified
|
||||||
} catch(e) {
|
// (less or equal by default).
|
||||||
return Promise.reject(e);
|
var strictripelen = !!popkey(opts, "strictripelen");
|
||||||
}
|
|
||||||
// Should the generated Ripe length be strictly equal to the specified
|
|
||||||
// (less-or-equal by default);
|
|
||||||
var strictripelen = !!opts.strictripelen;
|
|
||||||
var nextTick = typeof setImmediate === "undefined" ?
|
|
||||||
process.nextTick :
|
|
||||||
setImmediate;
|
|
||||||
|
|
||||||
|
// TODO(Kagami): Speed it up using web workers in Browser.
|
||||||
|
// TODO(Kagami): Bind to C++ version of this code in Node.
|
||||||
|
var encPrivateKey, encPublicKey, ripe;
|
||||||
var signPrivateKey = bmcrypto.getPrivate();
|
var signPrivateKey = bmcrypto.getPrivate();
|
||||||
var signPublicKey = bmcrypto.getPublic(signPrivateKey);
|
var signPublicKey = bmcrypto.getPublic(signPrivateKey);
|
||||||
|
var keysbuf = Buffer(130);
|
||||||
// FIXME(Kagami): This function is rather slow in browsers so
|
signPublicKey.copy(keysbuf);
|
||||||
// generation of ripelen=18 currently is disabled (see `test.js`). It
|
while (true) {
|
||||||
// should be heavily profiled to determine the bottleneck.
|
encPrivateKey = bmcrypto.getPrivate();
|
||||||
// TODO(Kagami): We may want to run this in the web worker to speedup
|
encPublicKey = bmcrypto.getPublic(encPrivateKey);
|
||||||
// the search. Currently WebCryptoAPI is not available in Firefox in
|
encPublicKey.copy(keysbuf, 65);
|
||||||
// web workers (see
|
ripe = bmcrypto.ripemd160(bmcrypto.sha512(keysbuf));
|
||||||
// <https://bugzilla.mozilla.org/show_bug.cgi?id=842818>) but is
|
var len = getripelen(ripe);
|
||||||
// available in Chrome (at least in 39.0+).
|
if (
|
||||||
return new Promise(function(resolve, reject) {
|
(strictripelen && len === ripelen) ||
|
||||||
function tryKey() {
|
(!strictripelen && len <= ripelen && checkripelen(ripelen, version))
|
||||||
var encPrivateKey = bmcrypto.getPrivate();
|
) {
|
||||||
var encPublicKey = bmcrypto.getPublic(encPrivateKey);
|
// TODO(Kagami): Do we need to put all these properties or compute
|
||||||
return keys2ripe(signPublicKey, encPublicKey).then(function(ripe) {
|
// them manually via ECMA5 getters/setters instead?
|
||||||
var len = getripelen(ripe);
|
opts.signPrivateKey = signPrivateKey;
|
||||||
if (
|
opts.signPublicKey = signPublicKey;
|
||||||
(strictripelen && len === ripelen) ||
|
opts.encPrivateKey = encPrivateKey;
|
||||||
(!strictripelen && len <= ripelen && checkripelen(ripelen, version))
|
opts.encPublicKey = encPublicKey;
|
||||||
) {
|
opts.ripe = ripe;
|
||||||
// TODO(Kagami): Do we need to put all these properties or compute
|
return new Address(opts);
|
||||||
// them manually via ECMA5 getters/setters instead?
|
|
||||||
resolve(new Address(Object.assign({
|
|
||||||
signPrivateKey: signPrivateKey,
|
|
||||||
signPublicKey: signPublicKey,
|
|
||||||
encPrivateKey: encPrivateKey,
|
|
||||||
encPublicKey: encPublicKey,
|
|
||||||
ripe: ripe,
|
|
||||||
}, opts)));
|
|
||||||
} else {
|
|
||||||
nextTick(tryKey);
|
|
||||||
}
|
|
||||||
}).catch(reject);
|
|
||||||
}
|
}
|
||||||
tryKey();
|
}
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Address;
|
module.exports = Address;
|
||||||
|
|
|
@ -12,7 +12,7 @@ var platform = require("./platform");
|
||||||
/**
|
/**
|
||||||
* Calculate SHA-512 hash.
|
* Calculate SHA-512 hash.
|
||||||
* @param {Buffer} buf - Input data
|
* @param {Buffer} buf - Input data
|
||||||
* @return {Promise.<Buffer>} Resulting hash.
|
* @return {Buffer} Resulting hash.
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
exports.sha512 = platform.sha512;
|
exports.sha512 = platform.sha512;
|
||||||
|
@ -20,7 +20,7 @@ exports.sha512 = platform.sha512;
|
||||||
/**
|
/**
|
||||||
* Calculate SHA-256 hash.
|
* Calculate SHA-256 hash.
|
||||||
* @param {Buffer} buf - Input data
|
* @param {Buffer} buf - Input data
|
||||||
* @return {Promise.<Buffer>} Resulting hash.
|
* @return {Buffer} Resulting hash.
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
exports.sha256 = platform.sha256;
|
exports.sha256 = platform.sha256;
|
||||||
|
@ -28,7 +28,7 @@ exports.sha256 = platform.sha256;
|
||||||
/**
|
/**
|
||||||
* Calculate RIPEMD-160 hash.
|
* Calculate RIPEMD-160 hash.
|
||||||
* @param {Buffer} buf - Input data
|
* @param {Buffer} buf - Input data
|
||||||
* @return {Promise.<Buffer>} Resulting hash.
|
* @return {Buffer} Resulting hash.
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
exports.ripemd160 = platform.ripemd160;
|
exports.ripemd160 = platform.ripemd160;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Working with messages.
|
* Working with messages.
|
||||||
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Message_types}
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Message_types}
|
||||||
* @module bitmessage/message
|
* @module bitmessage/messages
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Working with objects.
|
* Working with objects.
|
||||||
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Object_types}
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Object_types}
|
||||||
* @module bitmessage/object
|
* @module bitmessage/objects
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,38 +1,21 @@
|
||||||
/**
|
/**
|
||||||
* Browser implementation of platform-specific routines.
|
* Browser implementation of platform-specific routines.
|
||||||
* @see {@link http://www.w3.org/TR/WebCryptoAPI/}
|
|
||||||
* @see {@link http://caniuse.com/#feat=cryptography}
|
|
||||||
* @see {@link https://sites.google.com/a/chromium.org/dev/blink/webcrypto}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
require("es6-promise").polyfill();
|
var createHash = require("sha.js");
|
||||||
var assert = require("assert");
|
|
||||||
var ripemd160 = require("ripemd160");
|
var ripemd160 = require("ripemd160");
|
||||||
|
|
||||||
// Support `webkit` prefix for Safari (not tested yet).
|
|
||||||
// TODO(Kagami): Try to support IE11.
|
|
||||||
var subtle = window.crypto.subtle || window.crypto.webkitSubtle;
|
|
||||||
assert(subtle, "WebCryptoAPI is not supported");
|
|
||||||
|
|
||||||
exports.sha512 = function(buf) {
|
exports.sha512 = function(buf) {
|
||||||
return subtle.digest({name: "SHA-512"}, buf).then(function(arr) {
|
return createHash("sha512").update(buf).digest();
|
||||||
return new Buffer(new Uint8Array(arr));
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.sha256 = function(buf) {
|
exports.sha256 = function(buf) {
|
||||||
return subtle.digest({name: "SHA-256"}, buf).then(function(arr) {
|
return createHash("sha256").update(buf).digest();
|
||||||
return new Buffer(new Uint8Array(arr));
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.ripemd160 = function(buf) {
|
exports.ripemd160 = ripemd160;
|
||||||
// XXX(Kagami): RIPEMD is not defined in WebCryptoAPI so we provide it
|
|
||||||
// using pure JS third-party implementation.
|
|
||||||
return Promise.resolve(ripemd160(buf));
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.randomBytes = function(size) {
|
exports.randomBytes = function(size) {
|
||||||
var arr = new Uint8Array(size);
|
var arr = new Uint8Array(size);
|
||||||
|
|
|
@ -1,29 +1,22 @@
|
||||||
/**
|
/**
|
||||||
* Node.js implementation of platform-specific routines.
|
* Node.js implementation of platform-specific routines.
|
||||||
* @see {@link http://nodejs.org/api/crypto.html}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
require("es6-promise").polyfill();
|
|
||||||
var crypto = require("crypto");
|
var crypto = require("crypto");
|
||||||
|
var createHash = crypto.createHash;
|
||||||
|
|
||||||
exports.sha512 = function(buf) {
|
exports.sha512 = function(buf) {
|
||||||
var hash = crypto.createHash("sha512");
|
return createHash("sha512").update(buf).digest();
|
||||||
hash.update(buf);
|
|
||||||
return Promise.resolve(hash.digest());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.sha256 = function(buf) {
|
exports.sha256 = function(buf) {
|
||||||
var hash = crypto.createHash("sha256");
|
return createHash("sha256").update(buf).digest();
|
||||||
hash.update(buf);
|
|
||||||
return Promise.resolve(hash.digest());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.ripemd160 = function(buf) {
|
exports.ripemd160 = function(buf) {
|
||||||
var hash = crypto.createHash("ripemd160");
|
return createHash("ripemd160").update(buf).digest();
|
||||||
hash.update(buf);
|
|
||||||
return Promise.resolve(hash.digest());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.randomBytes = crypto.randomBytes;
|
exports.randomBytes = crypto.randomBytes;
|
||||||
|
|
31
lib/wif.js
31
lib/wif.js
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
require("es6-promise").polyfill();
|
|
||||||
var assert = require("assert");
|
var assert = require("assert");
|
||||||
var bufferEqual = require("buffer-equal");
|
var bufferEqual = require("buffer-equal");
|
||||||
var bs58 = require("bs58");
|
var bs58 = require("bs58");
|
||||||
|
@ -14,41 +13,31 @@ var bmcrypto = require("./crypto");
|
||||||
|
|
||||||
// Compute the WIF checksum for the given data.
|
// Compute the WIF checksum for the given data.
|
||||||
function getchecksum(data) {
|
function getchecksum(data) {
|
||||||
return bmcrypto.sha256(data).then(bmcrypto.sha256).then(function(dhash) {
|
return bmcrypto.sha256(bmcrypto.sha256(data)).slice(0, 4);
|
||||||
return dhash.slice(0, 4);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode WIF encoded private key.
|
* Decode WIF encoded private key.
|
||||||
* @param {string} wif - Encoded key
|
* @param {string} wif - Encoded key
|
||||||
* @return {Promise.<Buffer>} Private key.
|
* @return {Buffer} Private key.
|
||||||
*/
|
*/
|
||||||
exports.decode = function(wif) {
|
exports.decode = function(wif) {
|
||||||
var bytes;
|
var bytes = bs58.decode(wif);
|
||||||
try {
|
assert(bytes[0] === 0x80, "Bad WIF");
|
||||||
bytes = bs58.decode(wif);
|
|
||||||
assert(bytes[0] === 0x80, "Bad WIF");
|
|
||||||
} catch(e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
|
||||||
var data = new Buffer(bytes.slice(0, -4));
|
var data = new Buffer(bytes.slice(0, -4));
|
||||||
var checksum = new Buffer(bytes.slice(-4));
|
var checksum = new Buffer(bytes.slice(-4));
|
||||||
return getchecksum(data).then(function(realchecksum) {
|
assert(bufferEqual(checksum, getchecksum(data)), "Bad checkum");
|
||||||
assert(bufferEqual(checksum, realchecksum), "Bad checkum");
|
return data.slice(1);
|
||||||
return data.slice(1);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert private key to a WIF.
|
* Convert private key to a WIF.
|
||||||
* @param {Buffer} privateKey - A private key to encode
|
* @param {Buffer} privateKey - A private key to encode
|
||||||
* @return {Promise.<string>} Encoded private key.
|
* @return {string} Encoded private key.
|
||||||
*/
|
*/
|
||||||
exports.encode = function(privateKey) {
|
exports.encode = function(privateKey) {
|
||||||
var data = Buffer.concat([new Buffer([0x80]), privateKey]);
|
var data = Buffer.concat([new Buffer([0x80]), privateKey]);
|
||||||
return getchecksum(data).then(function(checksum) {
|
var checksum = getchecksum(data);
|
||||||
var bytes = Buffer.concat([data, checksum]);
|
var bytes = Buffer.concat([data, checksum]);
|
||||||
return bs58.encode(bytes);
|
return bs58.encode(bytes);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -48,8 +48,8 @@
|
||||||
"bs58": "^2.0.0",
|
"bs58": "^2.0.0",
|
||||||
"buffer-equal": "~0.0.1",
|
"buffer-equal": "~0.0.1",
|
||||||
"eccrypto": "^0.1.1",
|
"eccrypto": "^0.1.1",
|
||||||
"es6-promise": "^2.0.1",
|
|
||||||
"object.assign": "^1.1.1",
|
"object.assign": "^1.1.1",
|
||||||
"ripemd160": "^0.2.0"
|
"ripemd160": "^0.2.0",
|
||||||
|
"sha.js": "^2.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
101
test.js
101
test.js
|
@ -13,21 +13,15 @@ var Address = bitmessage.Address;
|
||||||
|
|
||||||
describe("Crypto", function() {
|
describe("Crypto", function() {
|
||||||
it("should implement SHA-512 hash", function() {
|
it("should implement SHA-512 hash", function() {
|
||||||
return bmcrypto.sha512(Buffer("test")).then(function(res) {
|
expect(bmcrypto.sha512(Buffer("test")).toString("hex")).to.equal("ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff");
|
||||||
expect(res.toString("hex")).to.equal("ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should implement SHA-256 hash", function() {
|
it("should implement SHA-256 hash", function() {
|
||||||
return bmcrypto.sha256(Buffer("test")).then(function(res) {
|
expect(bmcrypto.sha256(Buffer("test")).toString("hex")).to.equal("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08");
|
||||||
expect(res.toString("hex")).to.equal("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should implement RIPEMD-160 hash", function() {
|
it("should implement RIPEMD-160 hash", function() {
|
||||||
return bmcrypto.ripemd160(Buffer("test")).then(function(res) {
|
expect(bmcrypto.ripemd160(Buffer("test")).toString("hex")).to.equal("5e52fee47e6b070565f74372468cdc699de89107");
|
||||||
expect(res.toString("hex")).to.equal("5e52fee47e6b070565f74372468cdc699de89107");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should implement cryptographically secure PRNG", function() {
|
it("should implement cryptographically secure PRNG", function() {
|
||||||
|
@ -165,78 +159,63 @@ describe("WIF", function() {
|
||||||
var encPrivateKey = Buffer("9f9969c93c2d186787a7653f70e49be34c03c4a853e6ad0c867db0946bc433c6", "hex");
|
var encPrivateKey = Buffer("9f9969c93c2d186787a7653f70e49be34c03c4a853e6ad0c867db0946bc433c6", "hex");
|
||||||
|
|
||||||
it("should decode", function() {
|
it("should decode", function() {
|
||||||
return WIF.decode(wifSign)
|
var key1 = WIF.decode(wifSign);
|
||||||
.then(function(key1) {
|
expect(Buffer.isBuffer(key1)).to.be.true;
|
||||||
expect(Buffer.isBuffer(key1)).to.be.true;
|
expect(key1.length).to.equal(32);
|
||||||
expect(key1.length).to.equal(32);
|
expect(key1.toString("hex")).to.equal(signPrivateKey.toString("hex"));
|
||||||
expect(key1.toString("hex")).to.equal(signPrivateKey.toString("hex"));
|
var key2 = WIF.decode(wifEnc);
|
||||||
return WIF.decode(wifEnc).then(function(key2) {
|
expect(Buffer.isBuffer(key2)).to.be.true;
|
||||||
expect(Buffer.isBuffer(key2)).to.be.true;
|
expect(key2.length).to.equal(32);
|
||||||
expect(key2.length).to.equal(32);
|
expect(key2.toString("hex")).to.equal(encPrivateKey.toString("hex"));
|
||||||
expect(key2.toString("hex")).to.equal(encPrivateKey.toString("hex"));
|
var addrStr = Address({signPrivateKey: key1, encPrivateKey: key2}).encode();
|
||||||
return Address({signPrivateKey: key1, encPrivateKey: key2}).encode();
|
expect(addrStr).to.equal("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
|
||||||
});
|
|
||||||
}).then(function(str) {
|
|
||||||
expect(str).to.equal("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should encode", function() {
|
it("should encode", function() {
|
||||||
return WIF.encode(signPrivateKey).then(function(wif1) {
|
var wif1 = WIF.encode(signPrivateKey);
|
||||||
expect(wif1).to.equal(wifSign);
|
expect(wif1).to.equal(wifSign);
|
||||||
return WIF.encode(encPrivateKey);
|
var wif2 = WIF.encode(encPrivateKey);
|
||||||
}).then(function(wif2) {
|
expect(wif2).to.equal(wifEnc);
|
||||||
expect(wif2).to.equal(wifEnc);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Address", function() {
|
describe("Address", function() {
|
||||||
it("should decode Bitmessage address", function() {
|
it("should decode Bitmessage address", function() {
|
||||||
return Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z")
|
var addr = Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z")
|
||||||
.then(function(addr) {
|
expect(addr.version).to.equal(4);
|
||||||
expect(addr.version).to.equal(4);
|
expect(addr.stream).to.equal(1);
|
||||||
expect(addr.stream).to.equal(1);
|
expect(addr.ripe.toString("hex")).to.equal("003ab6655de4bd8c603eba9b00dd5970725fdd56");
|
||||||
expect(addr.ripe.toString("hex")).to.equal("003ab6655de4bd8c603eba9b00dd5970725fdd56");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should decode Bitmessage address badly formatted", function() {
|
it("should decode Bitmessage address badly formatted", function() {
|
||||||
return Address.decode(" 2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z ")
|
var addr = Address.decode(" 2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z ")
|
||||||
.then(function(addr) {
|
expect(addr.version).to.equal(4);
|
||||||
expect(addr.version).to.equal(4);
|
expect(addr.stream).to.equal(1);
|
||||||
expect(addr.stream).to.equal(1);
|
expect(addr.ripe.toString("hex")).to.equal("003ab6655de4bd8c603eba9b00dd5970725fdd56");
|
||||||
expect(addr.ripe.toString("hex")).to.equal("003ab6655de4bd8c603eba9b00dd5970725fdd56");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow to generate new Bitmessage address", function() {
|
it("should allow to generate new Bitmessage address", function() {
|
||||||
this.timeout(10000);
|
this.timeout(10000);
|
||||||
return Address.fromRandom().then(function(addr) {
|
var addr = Address.fromRandom();
|
||||||
expect(addr.version).to.equal(4);
|
expect(addr.version).to.equal(4);
|
||||||
expect(addr.stream).to.equal(1);
|
expect(addr.stream).to.equal(1);
|
||||||
expect(addr.signPrivateKey.length).to.equal(32);
|
expect(addr.signPrivateKey.length).to.equal(32);
|
||||||
expect(addr.encPrivateKey.length).to.equal(32);
|
expect(addr.encPrivateKey.length).to.equal(32);
|
||||||
return addr.encode().then(function(str) {
|
var str = addr.encode();
|
||||||
expect(str.slice(0, 3)).to.equal("BM-");
|
expect(str.slice(0, 3)).to.equal("BM-");
|
||||||
return Address.decode(str).then(function(addr2) {
|
var addr2 = Address.decode(str);
|
||||||
expect(addr2.version).to.equal(4);
|
expect(addr2.version).to.equal(4);
|
||||||
expect(addr2.stream).to.equal(1);
|
expect(addr2.stream).to.equal(1);
|
||||||
expect(addr2.ripe.length).to.equal(20);
|
expect(addr2.ripe.length).to.equal(20);
|
||||||
expect(addr2.ripe[0]).to.equal(0);
|
expect(addr2.ripe[0]).to.equal(0);
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (allTests) {
|
if (allTests) {
|
||||||
it("should allow to generate shorter address", function() {
|
it("should allow to generate shorter address", function() {
|
||||||
this.timeout(60000);
|
this.timeout(60000);
|
||||||
return Address.fromRandom({ripelen: 18}).then(function(addr) {
|
var addr = Address.fromRandom({ripelen: 18});
|
||||||
return addr.getRipe({short: true}).then(function(ripe) {
|
var ripe = addr.getRipe({short: true});
|
||||||
expect(ripe.length).to.be.at.most(18);
|
expect(ripe.length).to.be.at.most(18);
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user