Implement address decoding

This commit is contained in:
Kagami Hiiragi 2014-12-18 19:47:18 +03:00
parent e425730021
commit 1ccd56f17a
9 changed files with 219 additions and 10 deletions

View File

@ -7,7 +7,7 @@
// Enforcing
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : false, // true: Identifiers must be in camelCase
"curly" : false, // true: Require {} for every new block or scope
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()

72
lib/address.js Normal file
View File

@ -0,0 +1,72 @@
/**
* Working with Bitmessage addresses.
* @module bitmessage/address
*/
"use strict";
var bufferEqual = require("buffer-equal");
var bs58 = require("bs58");
var varint = require("./varint");
var bmcrypto = require("./crypto");
var assert = require("./utils").assert;
/**
* Parse Bitmessage Base58 encoded address (with or without `BM-`
* prefix) into address object.
*/
exports.decode = function(str) {
str = str.trim();
if (str.slice(0, 3) === "BM-") {
str = str.slice(3);
}
var bytes;
try {
bytes = bs58.decode(str);
} catch(e) {
return Promise.reject(e);
}
// Checksum validating.
var data = new Buffer(bytes.slice(0, -4));
var checksum = new Buffer(bytes.slice(-4));
return bmcrypto.sha512(data).then(bmcrypto.sha512).then(function(dhash) {
assert(bufferEqual(dhash.slice(0, 4), checksum), "Bad checkum");
var decoded = varint.decode(data);
var version = decoded.value;
assert(version <= 4, "Version too high");
assert(version >= 1, "Version too low");
data = decoded.rest;
decoded = varint.decode(data);
var stream = decoded.value;
var ripe = decoded.rest;
var ripelen = ripe.length;
switch (version) {
case 1:
assert(ripelen === 20);
break;
case 2:
case 3:
assert(ripelen >= 18, "Ripe too short");
assert(ripelen <= 20, "Ripe too long");
break;
case 4:
assert(ripelen >= 4, "Ripe too short");
assert(ripelen <= 20, "Ripe too long");
assert(ripe[0] !== 0, "Ripe encode error");
break;
}
// Prevent extra allocation. God, kill me please for premature
// optimizations.
if (ripelen < 20) {
var zeroes = new Buffer(Array(20 - ripelen));
ripe = Buffer.concat([zeroes, ripe]);
}
return {version: version, stream: stream, ripe: ripe};
});
};

View File

@ -1,12 +1,28 @@
/**
* Browser version of the crypto for Bitmessage JS implementation.
* @module bitmessage/lib/crypto.browser
*
* Documentation: <http://www.w3.org/TR/WebCryptoAPI/>
* Browsers support: <http://caniuse.com/#feat=cryptography>
* Blink implementation details: <https://sites.google.com/a/chromium.org/dev/blink/webcrypto>
*
* @module bitmessage/crypto.browser
*/
// FIXME(Kagami): Support webkit subtle prefix!
// TODO(Kagami): Try to support IE11.
"use strict";
require("es6-promise").polyfill();
var ripemd160 = require("ripemd160");
exports.sha512 = function(buf) {
return window.crypto.subtle.digest({name: "SHA-512"}, buf).then(function(arr) {
return new Buffer(new Uint8Array(arr));
});
};
exports.ripemd160 = function(buf) {
// XXX(Kagami): No support in browsers via Web Crypto API currently,
// so use module.
return Promise.resolve(ripemd160(buf));
};

View File

@ -1,13 +1,11 @@
/**
* Node.js version of the crypto for Bitmessage JS implementation.
* Wrap all crypto functions with promises because WebCryptoAPI uses it
* throughout.
* @module bitmessage/lib/crypto
* @module bitmessage/crypto
*/
"use strict";
var Promise = require("es6-promise").Promise; // jshint ignore:line
require("es6-promise").polyfill();
var crypto = require("crypto");
exports.sha512 = function(buf) {
@ -15,3 +13,9 @@ exports.sha512 = function(buf) {
hash.update(buf);
return Promise.resolve(hash.digest());
};
exports.ripemd160 = function(buf) {
var hash = crypto.createHash("ripemd160");
hash.update(buf);
return Promise.resolve(hash.digest());
};

View File

@ -0,0 +1,8 @@
/**
* Main Bitmessage module. Just reexports all public submodules.
* @module bitmessage
*/
"use strict";
exports.Address = require("./address");

7
lib/utils.js Normal file
View File

@ -0,0 +1,7 @@
"use strict";
exports.assert = function(condition, message) {
if (!condition) {
throw new Error(message || "Assertion failed");
}
};

39
lib/varint.js Normal file
View File

@ -0,0 +1,39 @@
/**
* Implement `var_int` encoding/decoding.
* @module bitmessage/varint
*/
"use strict";
// TODO(Kagami): Since `node-int64` and `int64-native` APIs are slightly
// differ, there might be need in platform-dependent wrapper. Also think
// that to do with 64bit arithmetic since `node-int64` doesn't implement
// it.
var Int64 = require("int64-native");
var assert = require("./utils").assert;
exports.decode = function(buf) {
assert(buf.length > 0, "Empty buffer");
var value, length;
switch (buf[0]) {
case 253:
value = buf.readUInt16BE(1);
length = 3;
break;
case 254:
value = buf.readUInt32BE(1);
length = 5;
break;
case 255:
var hi = buf.readUInt32BE(1);
var lo = buf.readUInt32BE(5);
value = new Int64(hi, lo);
length = 9;
break;
default:
value = buf[0];
length = 1;
}
var rest = buf.slice(length);
return {value: value, length: length, rest: rest};
};

View File

@ -4,6 +4,7 @@
"description": "JavaScript Bitmessage library",
"main": "./lib/index.js",
"browser": {
"int64-native": "node-int64",
"./lib/crypto.js": "./lib/crypto.browser.js"
},
"scripts": {
@ -38,6 +39,11 @@
"mocha": "*"
},
"dependencies": {
"es6-promise": "^2.0.1"
"bs58": "^2.0.0",
"buffer-equal": "~0.0.1",
"es6-promise": "^2.0.1",
"int64-native": "^0.3.2",
"node-int64": "^0.3.2",
"ripemd160": "^0.2.0"
}
}

63
test.js
View File

@ -1,11 +1,68 @@
var expect = require("chai").expect;
var bufferEqual = require("buffer-equal");
var bitmessage = require("./lib");
var Address = bitmessage.Address;
var varint = require("./lib/varint");
var bmcrypto = require("./lib/crypto");
describe("Bitmessage crypto", function() {
it("should calculate sha512 hash", function() {
return bmcrypto.sha512(new Buffer("test")).then(function(res) {
describe("var_int", function() {
it("should decode", function() {
var res;
expect(varint.decode.bind(Buffer([]))).to.throw(Error);
res = varint.decode(Buffer([123]));
expect(res.value).to.equal(123);
expect(res.length).to.equal(1);
expect(bufferEqual(res.rest, Buffer([]))).to.be.true;
res = varint.decode(Buffer("fd123456", "hex"));
expect(res.value).to.equal(0x1234);
expect(res.length).to.equal(3);
expect(bufferEqual(res.rest, Buffer("56", "hex"))).to.be.true;
res = varint.decode(Buffer("fe1234567890", "hex"));
expect(res.value).to.equal(0x12345678);
expect(res.length).to.equal(5);
expect(bufferEqual(res.rest, Buffer("90", "hex"))).to.be.true;
res = varint.decode(Buffer("ff0000001234567890", "hex"));
expect(res.value == 0x1234567890).to.be.true;
expect(res.length).to.equal(9);
expect(res.rest.length).to.equal(0);
});
});
describe("Crypto", function() {
it("should implement SHA-512 hash", function() {
return bmcrypto.sha512(Buffer("test")).then(function(res) {
expect(res.toString("hex")).to.equal("ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff");
});
});
it("should implement RIPEMD-160 hash", function() {
return bmcrypto.ripemd160(Buffer("test")).then(function(res) {
expect(res.toString("hex")).to.equal("5e52fee47e6b070565f74372468cdc699de89107");
});
});
});
describe("Address", function() {
it("should decode Bitmessage address", function() {
return Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z")
.then(function(addr) {
expect(addr.version).to.equal(4);
expect(addr.stream).to.equal(1);
expect(bufferEqual(addr.ripe, Buffer("003ab6655de4bd8c603eba9b00dd5970725fdd56", "hex"))).to.be.true;
});
});
it("should decode Bitmessage address badly formatted", function() {
return Address.decode(" 2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z ")
.then(function(addr) {
expect(addr.version).to.equal(4);
expect(addr.stream).to.equal(1);
expect(bufferEqual(addr.ripe, Buffer("003ab6655de4bd8c603eba9b00dd5970725fdd56", "hex"))).to.be.true;
});
});
});