ripplelib
Version:
A JavaScript API for interacting with Ripple in Node.js and the browser
152 lines (129 loc) • 4.04 kB
JavaScript
;
var _ = require('lodash');
var sjcl = require('./utils').sjcl;
var utils = require('./utils');
var extend = require('extend');
var convertBase = require('./baseconverter');
var Base = {};
var alphabets = Base.alphabets = {
ripple: 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz',
tipple: 'RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz',
bitcoin: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
};
extend(Base, {
VER_NONE: 1,
VER_NODE_PUBLIC: 28,
VER_NODE_PRIVATE: 32,
VER_ACCOUNT_ID: 0,
VER_ACCOUNT_PUBLIC: 35,
VER_ACCOUNT_PRIVATE: 34,
VER_FAMILY_GENERATOR: 41,
VER_FAMILY_SEED: 33
});
function sha256(bytes) {
return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes)));
}
function encodeString(alphabet, input) {
if (input.length === 0) {
return '';
}
var leadingZeros = _.takeWhile(input, function (d) {
return d === 0;
});
var out = convertBase(input, 256, 58).map(function (digit) {
if (digit < 0 || digit >= alphabet.length) {
throw new Error('Value ' + digit + ' is out of bounds for encoding');
}
return alphabet[digit];
});
var prefix = leadingZeros.map(function () {
return alphabet[0];
});
return prefix.concat(out).join('');
}
function decodeString(indexes, input) {
if (input.length === 0) {
return [];
}
var input58 = input.split('').map(function (c) {
var charCode = c.charCodeAt(0);
if (charCode >= indexes.length || indexes[charCode] === -1) {
throw new Error('Character ' + c + ' is not valid for encoding');
}
return indexes[charCode];
});
var leadingZeros = _.takeWhile(input58, function (d) {
return d === 0;
});
var out = convertBase(input58, 58, 256);
return leadingZeros.concat(out);
}
function Base58(alphabet) {
var indexes = utils.arraySet(128, -1);
for (var i = 0; i < alphabet.length; i++) {
indexes[alphabet.charCodeAt(i)] = i;
}
return {
decode: decodeString.bind(null, indexes),
encode: encodeString.bind(null, alphabet)
};
}
Base.encoders = {};
Object.keys(alphabets).forEach(function (alphabet) {
Base.encoders[alphabet] = new Base58(alphabets[alphabet]);
});
// --> input: big-endian array of bytes.
// <-- string at least as long as input.
Base.encode = function (input, alpha) {
return this.encoders[alpha || 'ripple'].encode(input);
};
// --> input: String
// <-- array of bytes or undefined.
Base.decode = function (input, alpha) {
if (typeof input !== 'string') {
return undefined;
}
try {
return this.encoders[alpha || 'ripple'].decode(input);
} catch (e) {
return undefined;
}
};
Base.verify_checksum = function (bytes) {
var computed = sha256(sha256(bytes.slice(0, -4))).slice(0, 4);
var checksum = bytes.slice(-4);
return _.isEqual(computed, checksum);
};
// --> input: Array
// <-- String
Base.encode_check = function (version, input, alphabet) {
var buffer = [].concat(version, input);
var check = sha256(sha256(buffer)).slice(0, 4);
return Base.encode([].concat(buffer, check), alphabet);
};
// --> input : String
// <-- NaN || sjcl.bn
Base.decode_check = function (version, input, alphabet) {
var buffer = Base.decode(input, alphabet);
if (!buffer || buffer.length < 5) {
return NaN;
}
// Single valid version
if (typeof version === 'number' && buffer[0] !== version) {
return NaN;
}
// Multiple allowed versions
if (Array.isArray(version) && _.every(version, function (v) {
return v !== buffer[0];
})) {
return NaN;
}
if (!Base.verify_checksum(buffer)) {
return NaN;
}
// We'll use the version byte to add a leading zero, this ensures JSBN doesn't
// intrepret the value as a negative number
buffer[0] = 0;
return sjcl.bn.fromBits(sjcl.codec.bytes.toBits(buffer.slice(0, -4)));
};
exports.Base = Base;