Merge pull request #294 from clark800/baseconverter

Refactor base conversion
This commit is contained in:
wltsmrz
2015-03-03 20:23:08 -08:00
4 changed files with 186 additions and 168 deletions

View File

@@ -1,142 +1,74 @@
var sjcl = require('./utils').sjcl; 'use strict';
var utils = require('./utils'); var _ = require('lodash');
var extend = require('extend'); var sjcl = require('./utils').sjcl;
var utils = require('./utils');
var extend = require('extend');
var convertBase = require('./baseconverter');
var Base = {}; var Base = {};
var alphabets = Base.alphabets = { var alphabets = Base.alphabets = {
ripple: 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz', ripple: 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz',
tipple: 'RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz', tipple: 'RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz',
bitcoin: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' bitcoin: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
}; };
extend(Base, { extend(Base, {
VER_NONE : 1, VER_NONE: 1,
VER_NODE_PUBLIC : 28, VER_NODE_PUBLIC: 28,
VER_NODE_PRIVATE : 32, VER_NODE_PRIVATE: 32,
VER_ACCOUNT_ID : 0, VER_ACCOUNT_ID: 0,
VER_ACCOUNT_PUBLIC : 35, VER_ACCOUNT_PUBLIC: 35,
VER_ACCOUNT_PRIVATE : 34, VER_ACCOUNT_PRIVATE: 34,
VER_FAMILY_GENERATOR : 41, VER_FAMILY_GENERATOR: 41,
VER_FAMILY_SEED : 33 VER_FAMILY_SEED: 33
}); });
function sha256(bytes) { function sha256(bytes) {
return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes))); return sjcl.codec.bytes.fromBits(
sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes)));
} }
function sha256hash(bytes) { function encodeString(alphabet, input) {
return sha256(sha256(bytes)); if (input.length === 0) {
}
function divmod58(number, startAt) {
var remainder = 0;
for (var i = startAt; i < number.length; i++) {
var digit256 = number[i] & 0xFF;
var temp = remainder * 256 + digit256;
number[i] = (temp / 58);
remainder = temp % 58;
}
return remainder;
}
function divmod256(number58, startAt) {
var remainder = 0;
for (var i = startAt; i < number58.length; i++) {
var digit58 = number58[i] & 0xFF;
var temp = remainder * 58 + digit58;
number58[i] = (temp / 256);
remainder = temp % 256;
}
return remainder;
}
function encodeString (alphabet, input) {
if (input.length == 0) {
return []; return [];
} }
// we need to copy the buffer for calc var leadingZeros = _.takeWhile(input, function(d) {
scratch = input.slice(); return d === 0;
});
// Count leading zeroes. var out = convertBase(input, 256, 58).map(function(digit) {
var zeroCount = 0; if (digit < 0 || digit >= alphabet.length) {
while (zeroCount < scratch.length && throw new Error('Value ' + digit + ' is out of bounds for encoding');
scratch[zeroCount] == 0)
++zeroCount;
// The actual encoding.
var out = new Array(scratch.length * 2);
var j = out.length;
var startAt = zeroCount;
while (startAt < scratch.length) {
var mod = divmod58(scratch, startAt);
if (scratch[startAt] == 0) {
++startAt;
} }
out[--j] = alphabet[mod]; return alphabet[digit];
} });
var prefix = leadingZeros.map(function() {
// Strip extra 'r' if there are some after decoding. return alphabet[0];
while (j < out.length && out[j] == alphabet[0]) ++j; });
// Add as many leading 'r' as there were leading zeros. return prefix.concat(out).join('');
while (--zeroCount >= 0) out[--j] = alphabet[0];
while(j--) out.shift();
return out.join('');
} }
function decodeString(indexes, input) { function decodeString(indexes, input) {
var isString = typeof input === 'string'; if (input.length === 0) {
if (input.length == 0) {
return []; return [];
} }
input58 = new Array(input.length); var input58 = input.split('').map(function(c) {
var charCode = c.charCodeAt(0);
// Transform the String to a base58 byte sequence if (charCode >= indexes.length) {
for (var i = 0; i < input.length; ++i) { throw new Error('Character ' + c + ' is not valid for encoding');
if (isString) {
var c = input.charCodeAt(i);
} }
return indexes[charCode];
var digit58 = -1; });
if (c >= 0 && c < 128) { var leadingZeros = _.takeWhile(input58, function(d) {
digit58 = indexes[c]; return d === 0;
} });
if (digit58 < 0) { var out = convertBase(input58, 58, 256);
throw new Error("Illegal character " + c + " at " + i); var prefix = leadingZeros.map(function() {
} return 0;
});
input58[i] = digit58; return prefix.concat(out);
}
// Count leading zeroes
var zeroCount = 0;
while (zeroCount < input58.length && input58[zeroCount] == 0) {
++zeroCount;
}
// The encoding
out = utils.arraySet(input.length, 0);
var j = out.length;
var startAt = zeroCount;
while (startAt < input58.length) {
var mod = divmod256(input58, startAt);
if (input58[startAt] == 0) {
++startAt;
}
out[--j] = mod;
}
// Do no add extra leading zeroes, move j to first non null byte.
while (j < out.length && (out[j] == 0)) ++j;
j -= zeroCount;
while(j--) out.shift();
return out;
} }
function Base58(alphabet) { function Base58(alphabet) {
@@ -151,8 +83,8 @@ function Base58(alphabet) {
} }
Base.encoders = {}; Base.encoders = {};
Object.keys(alphabets).forEach(function(alphabet){ Object.keys(alphabets).forEach(function(alphabet) {
Base.encoders[alphabet] = Base58(alphabets[alphabet]); Base.encoders[alphabet] = new Base58(alphabets[alphabet]);
}); });
// --> input: big-endian array of bytes. // --> input: big-endian array of bytes.
@@ -165,22 +97,21 @@ Base.encode = function(input, alpha) {
// <-- array of bytes or undefined. // <-- array of bytes or undefined.
Base.decode = function(input, alpha) { Base.decode = function(input, alpha) {
if (typeof input !== 'string') { if (typeof input !== 'string') {
return void(0); return undefined;
} }
try { try {
return this.encoders[alpha || 'ripple'].decode(input); return this.encoders[alpha || 'ripple'].decode(input);
} } catch (e) {
catch(e) { return undefined;
return (void 0);
} }
}; };
Base.verify_checksum = function(bytes) { Base.verify_checksum = function(bytes) {
var computed = sha256hash(bytes.slice(0, -4)).slice(0, 4); var computed = sha256(sha256(bytes.slice(0, -4))).slice(0, 4);
var checksum = bytes.slice(-4); var checksum = bytes.slice(-4);
var result = true; var result = true;
for (var i=0; i<4; i++) { for (var i = 0; i < 4; i++) {
if (computed[i] !== checksum[i]) { if (computed[i] !== checksum[i]) {
result = false; result = false;
break; break;
@@ -194,7 +125,7 @@ Base.verify_checksum = function(bytes) {
// <-- String // <-- String
Base.encode_check = function(version, input, alphabet) { Base.encode_check = function(version, input, alphabet) {
var buffer = [].concat(version, input); var buffer = [].concat(version, input);
var check = sha256(sha256(buffer)).slice(0, 4); var check = sha256(sha256(buffer)).slice(0, 4);
return Base.encode([].concat(buffer, check), alphabet); return Base.encode([].concat(buffer, check), alphabet);
}; };
@@ -217,7 +148,7 @@ Base.decode_check = function(version, input, alphabet) {
if (Array.isArray(version)) { if (Array.isArray(version)) {
var match = false; var match = false;
for (var i=0, l=version.length; i<l; i++) { for (var i = 0, l = version.length; i < l; i++) {
match |= version[i] === buffer[0]; match |= version[i] === buffer[0];
} }
@@ -234,7 +165,7 @@ Base.decode_check = function(version, input, alphabet) {
// intrepret the value as a negative number // intrepret the value as a negative number
buffer[0] = 0; buffer[0] = 0;
return sjcl.bn.fromBits ( return sjcl.bn.fromBits(
sjcl.codec.bytes.toBits(buffer.slice(0, -4))); sjcl.codec.bytes.toBits(buffer.slice(0, -4)));
}; };

View File

@@ -0,0 +1,32 @@
'use strict';
function normalize(digitArray) {
while (digitArray[0] === 0) {
digitArray.shift();
}
return digitArray;
}
function divmod(digitArray, base, divisor) {
var remainder = 0;
var quotient = [];
for (var j = 0; j < digitArray.length; j++) {
var temp = remainder * base + parseInt(digitArray[j], 10);
quotient.push(Math.floor(temp / divisor));
remainder = temp % divisor;
}
return {quotient: normalize(quotient), remainder: remainder};
}
function convertBase(digitArray, fromBase, toBase) {
var result = [];
var dividend = digitArray;
while (dividend.length > 0) {
var qr = divmod(dividend, fromBase, toBase);
result.unshift(qr.remainder);
dividend = qr.quotient;
}
return normalize(result);
}
module.exports = convertBase;

View File

@@ -1,25 +1,27 @@
exports.Remote = require('./remote').Remote; 'use strict';
exports.Request = require('./request').Request; exports.Remote = require('./remote').Remote;
exports.Amount = require('./amount').Amount; exports.Request = require('./request').Request;
exports.Account = require('./account').Account; exports.Amount = require('./amount').Amount;
exports.Transaction = require('./transaction').Transaction; exports.Account = require('./account').Account;
exports.Currency = require('./currency').Currency; exports.Transaction = require('./transaction').Transaction;
exports.Base = require('./base').Base; exports.Currency = require('./currency').Currency;
exports.UInt128 = require('./uint128').UInt128; exports.Base = require('./base').Base;
exports.UInt160 = require('./uint160').UInt160; exports.UInt128 = require('./uint128').UInt128;
exports.UInt256 = require('./uint256').UInt256; exports.UInt160 = require('./uint160').UInt160;
exports.Seed = require('./seed').Seed; exports.UInt256 = require('./uint256').UInt256;
exports.Meta = require('./meta').Meta; exports.Seed = require('./seed').Seed;
exports.Meta = require('./meta').Meta;
exports.SerializedObject = require('./serializedobject').SerializedObject; exports.SerializedObject = require('./serializedobject').SerializedObject;
exports.RippleError = require('./rippleerror').RippleError; exports.RippleError = require('./rippleerror').RippleError;
exports.Message = require('./message').Message; exports.Message = require('./message').Message;
exports.binformat = require('./binformat'); exports.binformat = require('./binformat');
exports.utils = require('./utils'); exports.utils = require('./utils');
exports.Server = require('./server').Server; exports.Server = require('./server').Server;
exports.Wallet = require('./wallet'); exports.Wallet = require('./wallet');
exports.Ledger = require('./ledger').Ledger; exports.Ledger = require('./ledger').Ledger;
exports.TransactionQueue = require('./transactionqueue').TransactionQueue; exports.TransactionQueue = require('./transactionqueue').TransactionQueue;
exports.RangeSet = require('./rangeset').RangeSet; exports.RangeSet = require('./rangeset').RangeSet;
exports.convertBase = require('./baseconverter');
// Important: We do not guarantee any specific version of SJCL or for any // Important: We do not guarantee any specific version of SJCL or for any
// specific features to be included. The version and configuration may change at // specific features to be included. The version and configuration may change at
@@ -28,35 +30,35 @@ exports.RangeSet = require('./rangeset').RangeSet;
// However, for programs that are tied to a specific version of ripple.js like // However, for programs that are tied to a specific version of ripple.js like
// the official client, it makes sense to expose the SJCL instance so we don't // the official client, it makes sense to expose the SJCL instance so we don't
// have to include it twice. // have to include it twice.
exports.sjcl = require('./utils').sjcl; exports.sjcl = require('./utils').sjcl;
exports.types = require('./serializedtypes'); exports.types = require('./serializedtypes');
exports.config = require('./config'); exports.config = require('./config');
// camelCase to under_scored API conversion // camelCase to under_scored API conversion
function attachUnderscored(c) { function attachUnderscored(name) {
var o = exports[c]; var o = exports[name];
Object.keys(o.prototype).forEach(function(key) { Object.keys(o.prototype).forEach(function(key) {
var UPPERCASE = /([A-Z]{1})[a-z]+/g; var UPPERCASE = /([A-Z]{1})[a-z]+/g;
if (!UPPERCASE.test(key)) { if (!UPPERCASE.test(key)) {
return; return;
} }
var underscored = key.replace(UPPERCASE, function(c) { var underscored = key.replace(UPPERCASE, function(c) {
return '_' + c.toLowerCase(); return '_' + c.toLowerCase();
}); });
o.prototype[underscored] = o.prototype[key]; o.prototype[underscored] = o.prototype[key];
}); });
}; }
[ 'Remote', ['Remote',
'Request', 'Request',
'Transaction', 'Transaction',
'Account', 'Account',
'Server' 'Server'
].forEach(attachUnderscored); ].forEach(attachUnderscored);
// vim:sw=2:sts=2:ts=8:et // vim:sw=2:sts=2:ts=8:et

View File

@@ -0,0 +1,53 @@
'use strict';
var assert = require('assert');
var convertBase = require('ripple-lib').convertBase;
// Test cases from RFC-1924 (a joke RFC)
var BASE85 = ('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ 'abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~');
var BASE10 = BASE85.slice(0, 10);
var BASE16 = BASE85.slice(0, 16);
var DATA16 = '108000000000000000080800200C417A';
var DATA10 = '21932261930451111902915077091070067066';
var DATA85 = '4)+k&C#VzJ4br>0wv%Yp';
function encode(digitArray, encoding) {
return digitArray.map(function(i) {
return encoding.charAt(i);
}).join('');
}
function decode(encoded, encoding) {
return encoded.split('').map(function(c) {
return encoding.indexOf(c);
});
}
function convertBaseEncoded(value, fromEncoding, toEncoding) {
var digitArray = decode(value, fromEncoding);
var converted = convertBase(digitArray, fromEncoding.length,
toEncoding.length);
return encode(converted, toEncoding);
}
describe('convertBase', function() {
it('DEC -> HEX', function () {
assert.strictEqual(convertBaseEncoded(DATA10, BASE10, BASE16), DATA16);
});
it('HEX -> DEC', function () {
assert.strictEqual(convertBaseEncoded(DATA16, BASE16, BASE10), DATA10);
});
it('DEC -> B85', function () {
assert.strictEqual(convertBaseEncoded(DATA10, BASE10, BASE85), DATA85);
});
it('HEX -> B85', function () {
assert.strictEqual(convertBaseEncoded(DATA16, BASE16, BASE85), DATA85);
});
it('B85 -> DEC', function () {
assert.strictEqual(convertBaseEncoded(DATA85, BASE85, BASE10), DATA10);
});
it('B85 -> HEX', function () {
assert.strictEqual(convertBaseEncoded(DATA85, BASE85, BASE16), DATA16);
});
});