首次放到 git

This commit is contained in:
Luk.Lu
2018-10-09 23:03:05 +08:00
commit 16cca302b7
37 changed files with 4203 additions and 0 deletions

1018
utils/abi-coder.js Normal file

File diff suppressed because it is too large Load Diff

124
utils/address.js Normal file
View File

@@ -0,0 +1,124 @@
var BN = require('bn.js');
var convert = require('./convert');
var throwError = require('./throw-error');
var keccak256 = require('./keccak256');
function getChecksumAddress(address) {
if (typeof(address) !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) {
throwError('invalid address', {input: address});
}
address = address.toLowerCase();
var hashed = address.substring(2).split('');
for (var i = 0; i < hashed.length; i++) {
hashed[i] = hashed[i].charCodeAt(0);
}
hashed = convert.arrayify(keccak256(hashed));
address = address.substring(2).split('');
for (var i = 0; i < 40; i += 2) {
if ((hashed[i >> 1] >> 4) >= 8) {
address[i] = address[i].toUpperCase();
}
if ((hashed[i >> 1] & 0x0f) >= 8) {
address[i + 1] = address[i + 1].toUpperCase();
}
}
return '0x' + address.join('');
}
// Shims for environments that are missing some required constants and functions
var MAX_SAFE_INTEGER = 0x1fffffffffffff;
function log10(x) {
if (Math.log10) { return Math.log10(x); }
return Math.log(x) / Math.LN10;
}
// See: https://en.wikipedia.org/wiki/International_Bank_Account_Number
var ibanChecksum = (function() {
// Create lookup table
var ibanLookup = {};
for (var i = 0; i < 10; i++) { ibanLookup[String(i)] = String(i); }
for (var i = 0; i < 26; i++) { ibanLookup[String.fromCharCode(65 + i)] = String(10 + i); }
// How many decimal digits can we process? (for 64-bit float, this is 15)
var safeDigits = Math.floor(log10(MAX_SAFE_INTEGER));
return function(address) {
address = address.toUpperCase();
address = address.substring(4) + address.substring(0, 2) + '00';
var expanded = address.split('');
for (var i = 0; i < expanded.length; i++) {
expanded[i] = ibanLookup[expanded[i]];
}
expanded = expanded.join('');
// Javascript can handle integers safely up to 15 (decimal) digits
while (expanded.length >= safeDigits){
var block = expanded.substring(0, safeDigits);
expanded = parseInt(block, 10) % 97 + expanded.substring(block.length);
}
var checksum = String(98 - (parseInt(expanded, 10) % 97));
while (checksum.length < 2) { checksum = '0' + checksum; }
return checksum;
};
})();
function getAddress(address, icapFormat) {
var result = null;
if (typeof(address) !== 'string') {
throwError('invalid address', {input: address});
}
if (address.match(/^(0x)?[0-9a-fA-F]{40}$/)) {
// Missing the 0x prefix
if (address.substring(0, 2) !== '0x') { address = '0x' + address; }
result = getChecksumAddress(address);
// It is a checksummed address with a bad checksum
if (address.match(/([A-F].*[a-f])|([a-f].*[A-F])/) && result !== address) {
throwError('invalid address checksum', { input: address, expected: result });
}
// Maybe ICAP? (we only support direct mode)
} else if (address.match(/^XE[0-9]{2}[0-9A-Za-z]{30,31}$/)) {
// It is an ICAP address with a bad checksum
if (address.substring(2, 4) !== ibanChecksum(address)) {
throwError('invalid address icap checksum', { input: address });
}
result = (new BN(address.substring(4), 36)).toString(16);
while (result.length < 40) { result = '0' + result; }
result = getChecksumAddress('0x' + result);
} else {
throwError('invalid address', { input: address });
}
if (icapFormat) {
var base36 = (new BN(result.substring(2), 16)).toString(36).toUpperCase();
while (base36.length < 30) { base36 = '0' + base36; }
return 'XE' + ibanChecksum('XE00' + base36) + base36;
}
return result;
}
module.exports = {
getAddress: getAddress,
}

13
utils/base64.js Normal file
View File

@@ -0,0 +1,13 @@
'use strict';
var convert = require('./convert');
module.exports = {
decode: function(textData) {
return convert.arrayify(new Buffer(textData, 'base64'));
},
encode: function(data) {
return (new Buffer(convert.arrayify(data))).toString('base64');
}
};

149
utils/bignumber.js Normal file
View File

@@ -0,0 +1,149 @@
/**
* BigNumber
*
* A wrapper around the BN.js object. In the future we can swap out
* the underlying BN.js library for something smaller.
*/
var BN = require('bn.js');
var defineProperty = require('./properties').defineProperty;
var convert = require('./convert');
var throwError = require('./throw-error');
function BigNumber(value) {
if (!(this instanceof BigNumber)) { throw new Error('missing new'); }
if (convert.isHexString(value)) {
if (value == '0x') { value = '0x0'; }
value = new BN(value.substring(2), 16);
} else if (typeof(value) === 'string' && value[0] === '-' && convert.isHexString(value.substring(1))) {
value = (new BN(value.substring(3), 16)).mul(BigNumber.constantNegativeOne._bn);
} else if (typeof(value) === 'string' && value.match(/^-?[0-9]*$/)) {
if (value == '') { value = '0'; }
value = new BN(value);
} else if (typeof(value) === 'number' && parseInt(value) == value) {
value = new BN(value);
} else if (BN.isBN(value)) {
//value = value
} else if (isBigNumber(value)) {
value = value._bn;
} else if (convert.isArrayish(value)) {
value = new BN(convert.hexlify(value).substring(2), 16);
} else {
throwError('invalid BigNumber value', { input: value });
}
defineProperty(this, '_bn', value);
}
defineProperty(BigNumber, 'constantNegativeOne', bigNumberify(-1));
defineProperty(BigNumber, 'constantZero', bigNumberify(0));
defineProperty(BigNumber, 'constantOne', bigNumberify(1));
defineProperty(BigNumber, 'constantTwo', bigNumberify(2));
defineProperty(BigNumber, 'constantWeiPerEther', bigNumberify(new BN('1000000000000000000')));
defineProperty(BigNumber.prototype, 'fromTwos', function(value) {
return new BigNumber(this._bn.fromTwos(value));
});
defineProperty(BigNumber.prototype, 'toTwos', function(value) {
return new BigNumber(this._bn.toTwos(value));
});
defineProperty(BigNumber.prototype, 'add', function(other) {
return new BigNumber(this._bn.add(bigNumberify(other)._bn));
});
defineProperty(BigNumber.prototype, 'sub', function(other) {
return new BigNumber(this._bn.sub(bigNumberify(other)._bn));
});
defineProperty(BigNumber.prototype, 'div', function(other) {
return new BigNumber(this._bn.div(bigNumberify(other)._bn));
});
defineProperty(BigNumber.prototype, 'mul', function(other) {
return new BigNumber(this._bn.mul(bigNumberify(other)._bn));
});
defineProperty(BigNumber.prototype, 'mod', function(other) {
return new BigNumber(this._bn.mod(bigNumberify(other)._bn));
});
defineProperty(BigNumber.prototype, 'pow', function(other) {
return new BigNumber(this._bn.pow(bigNumberify(other)._bn));
});
defineProperty(BigNumber.prototype, 'maskn', function(value) {
return new BigNumber(this._bn.maskn(value));
});
defineProperty(BigNumber.prototype, 'eq', function(other) {
return this._bn.eq(bigNumberify(other)._bn);
});
defineProperty(BigNumber.prototype, 'lt', function(other) {
return this._bn.lt(bigNumberify(other)._bn);
});
defineProperty(BigNumber.prototype, 'lte', function(other) {
return this._bn.lte(bigNumberify(other)._bn);
});
defineProperty(BigNumber.prototype, 'gt', function(other) {
return this._bn.gt(bigNumberify(other)._bn);
});
defineProperty(BigNumber.prototype, 'gte', function(other) {
return this._bn.gte(bigNumberify(other)._bn);
});
defineProperty(BigNumber.prototype, 'isZero', function() {
return this._bn.isZero();
});
defineProperty(BigNumber.prototype, 'toNumber', function(base) {
return this._bn.toNumber();
});
defineProperty(BigNumber.prototype, 'toString', function() {
//return this._bn.toString(base || 10);
return this._bn.toString(10);
});
defineProperty(BigNumber.prototype, 'toHexString', function() {
var hex = this._bn.toString(16);
if (hex.length % 2) { hex = '0' + hex; }
return '0x' + hex;
});
function isBigNumber(value) {
return (value._bn && value._bn.mod);
}
function bigNumberify(value) {
if (isBigNumber(value)) { return value; }
return new BigNumber(value);
}
module.exports = {
isBigNumber: isBigNumber,
bigNumberify: bigNumberify,
BigNumber: BigNumber
};

24
utils/browser-base64.js Normal file
View File

@@ -0,0 +1,24 @@
'use strict';
var convert = require('./convert');
module.exports = {
decode: function(textData) {
textData = atob(textData);
var data = [];
for (var i = 0; i < textData.length; i++) {
data.push(textData.charCodeAt(i));
}
return convert.arrayify(data);
},
encode: function(data) {
data = convert.arrayify(data);
var textData = '';
for (var i = 0; i < data.length; i++) {
textData += String.fromCharCode(data[i]);
}
return btoa(textData);
}
};

View File

@@ -0,0 +1,43 @@
'use strict';
var convert = require('./convert');
var defineProperty = require('./properties').defineProperty;
var crypto = global.crypto || global.msCrypto;
if (!crypto || !crypto.getRandomValues) {
console.log('WARNING: Missing strong random number source; using weak randomBytes');
crypto = {
getRandomValues: function(buffer) {
for (var round = 0; round < 20; round++) {
for (var i = 0; i < buffer.length; i++) {
if (round) {
buffer[i] ^= parseInt(256 * Math.random());
} else {
buffer[i] = parseInt(256 * Math.random());
}
}
}
return buffer;
},
_weakCrypto: true
};
}
function randomBytes(length) {
if (length <= 0 || length > 1024 || parseInt(length) != length) {
throw new Error('invalid length');
}
var result = new Uint8Array(length);
crypto.getRandomValues(result);
return convert.arrayify(result);
};
if (crypto._weakCrypto === true) {
defineProperty(randomBytes, '_weakCrypto', true);
}
module.exports = randomBytes;

20
utils/contract-address.js Normal file
View File

@@ -0,0 +1,20 @@
var getAddress = require('./address').getAddress;
var convert = require('./convert');
var keccak256 = require('./keccak256');
var RLP = require('./rlp');
// http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed
function getContractAddress(transaction) {
if (!transaction.from) { throw new Error('missing from address'); }
var nonce = transaction.nonce;
return getAddress('0x' + keccak256(RLP.encode([
getAddress(transaction.from),
convert.stripZeros(convert.hexlify(nonce, 'nonce'))
])).substring(26));
}
module.exports = {
getContractAddress: getContractAddress,
}

224
utils/convert.js Normal file
View File

@@ -0,0 +1,224 @@
/**
* Conversion Utilities
*
*/
var defineProperty = require('./properties.js').defineProperty;
var errors = require('./errors');
function addSlice(array) {
if (array.slice) { return array; }
array.slice = function() {
var args = Array.prototype.slice.call(arguments);
return new Uint8Array(Array.prototype.slice.apply(array, args));
}
return array;
}
function isArrayish(value) {
if (!value || parseInt(value.length) != value.length || typeof(value) === 'string') {
return false;
}
for (var i = 0; i < value.length; i++) {
var v = value[i];
if (v < 0 || v >= 256 || parseInt(v) != v) {
return false;
}
}
return true;
}
function arrayify(value) {
if (value == null) {
errors.throwError('cannot convert null value to array', errors.INVALID_ARGUMENT, { arg: 'value', value: value });
}
if (value && value.toHexString) {
value = value.toHexString();
}
if (isHexString(value)) {
value = value.substring(2);
if (value.length % 2) { value = '0' + value; }
var result = [];
for (var i = 0; i < value.length; i += 2) {
result.push(parseInt(value.substr(i, 2), 16));
}
return addSlice(new Uint8Array(result));
} else if (typeof(value) === 'string') {
if (value.match(/^[0-9a-fA-F]*$/)) {
errors.throwError('hex string must have 0x prefix', errors.INVALID_ARGUMENT, { arg: 'value', value: value });
}
errors.throwError('invalid hexidecimal string', errors.INVALID_ARGUMENT, { arg: 'value', value: value });
}
if (isArrayish(value)) {
return addSlice(new Uint8Array(value));
}
errors.throwError('invalid arrayify value', { arg: 'value', value: value, type: typeof(value) });
}
function concat(objects) {
var arrays = [];
var length = 0;
for (var i = 0; i < objects.length; i++) {
var object = arrayify(objects[i])
arrays.push(object);
length += object.length;
}
var result = new Uint8Array(length);
var offset = 0;
for (var i = 0; i < arrays.length; i++) {
result.set(arrays[i], offset);
offset += arrays[i].length;
}
return addSlice(result);
}
function stripZeros(value) {
value = arrayify(value);
if (value.length === 0) { return value; }
// Find the first non-zero entry
var start = 0;
while (value[start] === 0) { start++ }
// If we started with zeros, strip them
if (start) {
value = value.slice(start);
}
return value;
}
function padZeros(value, length) {
value = arrayify(value);
if (length < value.length) { throw new Error('cannot pad'); }
var result = new Uint8Array(length);
result.set(value, length - value.length);
return addSlice(result);
}
function isHexString(value, length) {
if (typeof(value) !== 'string' || !value.match(/^0x[0-9A-Fa-f]*$/)) {
return false
}
if (length && value.length !== 2 + 2 * length) { return false; }
return true;
}
var HexCharacters = '0123456789abcdef';
function hexlify(value) {
if (value && value.toHexString) {
return value.toHexString();
}
if (typeof(value) === 'number') {
if (value < 0) {
errors.throwError('cannot hexlify negative value', errors.INVALID_ARG, { arg: 'value', value: value });
}
var hex = '';
while (value) {
hex = HexCharacters[value & 0x0f] + hex;
value = parseInt(value / 16);
}
if (hex.length) {
if (hex.length % 2) { hex = '0' + hex; }
return '0x' + hex;
}
return '0x00';
}
if (isHexString(value)) {
if (value.length % 2) {
value = '0x0' + value.substring(2);
}
return value;
}
if (isArrayish(value)) {
var result = [];
for (var i = 0; i < value.length; i++) {
var v = value[i];
result.push(HexCharacters[(v & 0xf0) >> 4] + HexCharacters[v & 0x0f]);
}
return '0x' + result.join('');
}
errors.throwError('invalid hexlify value', { arg: 'value', value: value });
}
function hexStripZeros(value) {
while (value.length > 3 && value.substring(0, 3) === '0x0') {
value = '0x' + value.substring(3);
}
return value;
}
function hexZeroPad(value, length) {
while (value.length < 2 * length + 2) {
value = '0x0' + value.substring(2);
}
return value;
}
/* @TODO: Add something like this to make slicing code easier to understand
function hexSlice(hex, start, end) {
hex = hexlify(hex);
return '0x' + hex.substring(2 + start * 2, 2 + end * 2);
}
*/
function splitSignature(signature) {
signature = arrayify(signature);
if (signature.length !== 65) {
throw new Error('invalid signature');
}
var v = signature[64];
if (v !== 27 && v !== 28) {
v = 27 + (v % 2);
}
return {
r: hexlify(signature.slice(0, 32)),
s: hexlify(signature.slice(32, 64)),
v: v
}
}
module.exports = {
arrayify: arrayify,
isArrayish: isArrayish,
concat: concat,
padZeros: padZeros,
stripZeros: stripZeros,
splitSignature: splitSignature,
hexlify: hexlify,
isHexString: isHexString,
hexStripZeros: hexStripZeros,
hexZeroPad: hexZeroPad,
};

1
utils/empty.js Normal file
View File

@@ -0,0 +1 @@
module.exports = undefined;

91
utils/errors.js Normal file
View File

@@ -0,0 +1,91 @@
'use strict';
var defineProperty = require('./properties').defineProperty;
var codes = { };
[
// Unknown Error
'UNKNOWN_ERROR',
// Not implemented
'NOT_IMPLEMENTED',
// Missing new operator to an object
// - name: The name of the class
'MISSING_NEW',
// Call exception
'CALL_EXCEPTION',
// Response from a server was invalid
// - response: The body of the response
//'BAD_RESPONSE',
// Invalid argument (e.g. type) to a function:
// - arg: The argument name that was invalid
// - value: The value of the argument
// - type: The type of the argument
// - expected: What was expected
'INVALID_ARGUMENT',
// Missing argument to a function:
// - arg: The argument name that is required
// - count: The number of arguments received
// - expectedCount: The number of arguments expected
'MISSING_ARGUMENT',
// Too many arguments
// - count: The number of arguments received
// - expectedCount: The number of arguments expected
'UNEXPECTED_ARGUMENT',
// Unsupported operation
// - operation
'UNSUPPORTED_OPERATION',
].forEach(function(code) {
defineProperty(codes, code, code);
});
defineProperty(codes, 'throwError', function(message, code, params) {
if (!code) { code = codes.UNKNOWN_ERROR; }
if (!params) { params = {}; }
var messageDetails = [];
Object.keys(params).forEach(function(key) {
try {
messageDetails.push(key + '=' + JSON.stringify(params[key]));
} catch (error) {
messageDetails.push(key + '=' + JSON.stringify(params[key].toString()));
}
});
var reason = message;
if (messageDetails.length) {
message += ' (' + messageDetails.join(', ') + ')';
}
var error = new Error(message);
error.reason = reason;
error.code = code
Object.keys(params).forEach(function(key) {
error[key] = params[key];
});
throw error;
});
defineProperty(codes, 'checkNew', function(self, kind) {
if (!(self instanceof kind)) {
codes.throwError('missing new', codes.MISSING_NEW, { name: kind.name });
}
});
module.exports = codes;

259
utils/hdnode.js Normal file
View File

@@ -0,0 +1,259 @@
// See: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
// See: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
var secp256k1 = new (require('elliptic')).ec('secp256k1');
var wordlist = (function() {
var words = require('./words.json');
return words.replace(/([A-Z])/g, ' $1').toLowerCase().substring(1).split(' ');
})();
var utils = (function() {
var convert = require('./convert.js');
var sha2 = require('./sha2');
var hmac = require('./hmac');
return {
defineProperty: require('./properties.js').defineProperty,
arrayify: convert.arrayify,
bigNumberify: require('./bignumber.js').bigNumberify,
hexlify: convert.hexlify,
toUtf8Bytes: require('./utf8.js').toUtf8Bytes,
sha256: sha2.sha256,
createSha512Hmac: hmac.createSha512Hmac,
pbkdf2: require('./pbkdf2.js'),
}
})();
// "Bitcoin seed"
var MasterSecret = utils.toUtf8Bytes('Bitcoin seed');
var HardenedBit = 0x80000000;
// Returns a byte with the MSB bits set
function getUpperMask(bits) {
return ((1 << bits) - 1) << (8 - bits);
}
// Returns a byte with the LSB bits set
function getLowerMask(bits) {
return (1 << bits) - 1;
}
function HDNode(keyPair, chainCode, index, depth) {
if (!(this instanceof HDNode)) { throw new Error('missing new'); }
utils.defineProperty(this, '_keyPair', keyPair);
utils.defineProperty(this, 'privateKey', utils.hexlify(keyPair.priv.toArray('be', 32)));
utils.defineProperty(this, 'publicKey', '0x' + keyPair.getPublic(true, 'hex'));
utils.defineProperty(this, 'chainCode', utils.hexlify(chainCode));
utils.defineProperty(this, 'index', index);
utils.defineProperty(this, 'depth', depth);
}
utils.defineProperty(HDNode.prototype, '_derive', function(index) {
// Public parent key -> public child key
if (!this.privateKey) {
if (index >= HardenedBit) { throw new Error('cannot derive child of neutered node'); }
throw new Error('not implemented');
}
var data = new Uint8Array(37);
if (index & HardenedBit) {
// Data = 0x00 || ser_256(k_par)
data.set(utils.arrayify(this.privateKey), 1);
} else {
// Data = ser_p(point(k_par))
data.set(this._keyPair.getPublic().encode(null, true));
}
// Data += ser_32(i)
for (var i = 24; i >= 0; i -= 8) { data[33 + (i >> 3)] = ((index >> (24 - i)) & 0xff); }
var I = utils.arrayify(utils.createSha512Hmac(this.chainCode).update(data).digest());
var IL = utils.bigNumberify(I.slice(0, 32));
var IR = I.slice(32);
var ki = IL.add('0x' + this._keyPair.getPrivate('hex')).mod('0x' + secp256k1.curve.n.toString(16));
return new HDNode(secp256k1.keyFromPrivate(utils.arrayify(ki)), I.slice(32), index, this.depth + 1);
});
utils.defineProperty(HDNode.prototype, 'derivePath', function(path) {
var components = path.split('/');
if (components.length === 0 || (components[0] === 'm' && this.depth !== 0)) {
throw new Error('invalid path');
}
if (components[0] === 'm') { components.shift(); }
var result = this;
for (var i = 0; i < components.length; i++) {
var component = components[i];
if (component.match(/^[0-9]+'$/)) {
var index = parseInt(component.substring(0, component.length - 1));
if (index >= HardenedBit) { throw new Error('invalid path index - ' + component); }
result = result._derive(HardenedBit + index);
} else if (component.match(/^[0-9]+$/)) {
var index = parseInt(component);
if (index >= HardenedBit) { throw new Error('invalid path index - ' + component); }
result = result._derive(index);
} else {
throw new Error('invlaid path component - ' + component);
}
}
return result;
});
utils.defineProperty(HDNode, 'fromMnemonic', function(mnemonic) {
// Check that the checksum s valid (will throw an error)
mnemonicToEntropy(mnemonic);
return HDNode.fromSeed(mnemonicToSeed(mnemonic));
});
utils.defineProperty(HDNode, 'fromSeed', function(seed) {
seed = utils.arrayify(seed);
if (seed.length < 16 || seed.length > 64) { throw new Error('invalid seed'); }
var I = utils.arrayify(utils.createSha512Hmac(MasterSecret).update(seed).digest());
return new HDNode(secp256k1.keyFromPrivate(I.slice(0, 32)), I.slice(32), 0, 0, 0);
});
function mnemonicToSeed(mnemonic, password) {
if (!password) {
password = '';
} else if (password.normalize) {
password = password.normalize('NFKD');
} else {
for (var i = 0; i < password.length; i++) {
var c = password.charCodeAt(i);
if (c < 32 || c > 127) { throw new Error('passwords with non-ASCII characters not supported in this environment'); }
}
}
mnemonic = utils.toUtf8Bytes(mnemonic, 'NFKD');
var salt = utils.toUtf8Bytes('mnemonic' + password, 'NFKD');
return utils.hexlify(utils.pbkdf2(mnemonic, salt, 2048, 64, utils.createSha512Hmac));
}
function mnemonicToEntropy(mnemonic) {
var words = mnemonic.toLowerCase().split(' ');
if ((words.length % 3) !== 0) { throw new Error('invalid mnemonic'); }
var entropy = utils.arrayify(new Uint8Array(Math.ceil(11 * words.length / 8)));
var offset = 0;
for (var i = 0; i < words.length; i++) {
var index = wordlist.indexOf(words[i]);
if (index === -1) { throw new Error('invalid mnemonic'); }
for (var bit = 0; bit < 11; bit++) {
if (index & (1 << (10 - bit))) {
entropy[offset >> 3] |= (1 << (7 - (offset % 8)));
}
offset++;
}
}
var entropyBits = 32 * words.length / 3;
var checksumBits = words.length / 3;
var checksumMask = getUpperMask(checksumBits);
var checksum = utils.arrayify(utils.sha256(entropy.slice(0, entropyBits / 8)))[0];
checksum &= checksumMask;
if (checksum !== (entropy[entropy.length - 1] & checksumMask)) {
throw new Error('invalid checksum');
}
return utils.hexlify(entropy.slice(0, entropyBits / 8));
}
function entropyToMnemonic(entropy) {
entropy = utils.arrayify(entropy);
if ((entropy.length % 4) !== 0 || entropy.length < 16 || entropy.length > 32) {
throw new Error('invalid entropy');
}
var words = [0];
var remainingBits = 11;
for (var i = 0; i < entropy.length; i++) {
// Consume the whole byte (with still more to go)
if (remainingBits > 8) {
words[words.length - 1] <<= 8;
words[words.length - 1] |= entropy[i];
remainingBits -= 8;
// This byte will complete an 11-bit index
} else {
words[words.length - 1] <<= remainingBits;
words[words.length - 1] |= entropy[i] >> (8 - remainingBits);
// Start the next word
words.push(entropy[i] & getLowerMask(8 - remainingBits));
remainingBits += 3;
}
}
// Compute the checksum bits
var checksum = utils.arrayify(utils.sha256(entropy))[0];
var checksumBits = entropy.length / 4;
checksum &= getUpperMask(checksumBits);
// Shift the checksum into the word indices
words[words.length - 1] <<= checksumBits;
words[words.length - 1] |= (checksum >> (8 - checksumBits));
// Convert indices into words
for (var i = 0; i < words.length; i++) {
words[i] = wordlist[words[i]];
}
return words.join(' ');
}
function isValidMnemonic(mnemonic) {
try {
mnemonicToEntropy(mnemonic);
return true;
} catch (error) { }
return false;
}
module.exports = {
fromMnemonic: HDNode.fromMnemonic,
fromSeed: HDNode.fromSeed,
mnemonicToSeed: mnemonicToSeed,
mnemonicToEntropy: mnemonicToEntropy,
entropyToMnemonic: entropyToMnemonic,
isValidMnemonic: isValidMnemonic,
};

24
utils/hmac.js Normal file
View File

@@ -0,0 +1,24 @@
'use strict';
var hash = require('hash.js');
var sha2 = require('./sha2.js');
var convert = require('./convert.js');
// @TODO: Make this use create-hmac in node
function createSha256Hmac(key) {
if (!key.buffer) { key = convert.arrayify(key); }
return new hash.hmac(sha2.createSha256, key);
}
function createSha512Hmac(key) {
if (!key.buffer) { key = convert.arrayify(key); }
return new hash.hmac(sha2.createSha512, key);
}
module.exports = {
createSha256Hmac: createSha256Hmac,
createSha512Hmac: createSha512Hmac,
};

10
utils/id.js Normal file
View File

@@ -0,0 +1,10 @@
'use strict';
var keccak256 = require('./keccak256');
var utf8 = require('./utf8');
function id(text) {
return keccak256(utf8.toUtf8Bytes(text));
}
module.exports = id;

75
utils/index.js Normal file
View File

@@ -0,0 +1,75 @@
'use strict';
// This is SUPER useful, but adds 140kb (even zipped, adds 40kb)
//var unorm = require('unorm');
var address = require('./address');
var AbiCoder = require('./abi-coder');
var base64 = require('./base64');
var bigNumber = require('./bignumber');
var contractAddress = require('./contract-address');
var convert = require('./convert');
var id = require('./id');
var keccak256 = require('./keccak256');
var namehash = require('./namehash');
var sha256 = require('./sha2').sha256;
var solidity = require('./solidity');
var randomBytes = require('./random-bytes');
var properties = require('./properties');
var RLP = require('./rlp');
var utf8 = require('./utf8');
var units = require('./units');
module.exports = {
AbiCoder: AbiCoder,
RLP: RLP,
defineProperty: properties.defineProperty,
// NFKD (decomposed)
//etherSymbol: '\uD835\uDF63',
// NFKC (composed)
etherSymbol: '\u039e',
arrayify: convert.arrayify,
concat: convert.concat,
padZeros: convert.padZeros,
stripZeros: convert.stripZeros,
base64: base64,
bigNumberify: bigNumber.bigNumberify,
BigNumber: bigNumber.BigNumber,
hexlify: convert.hexlify,
toUtf8Bytes: utf8.toUtf8Bytes,
toUtf8String: utf8.toUtf8String,
namehash: namehash,
id: id,
getAddress: address.getAddress,
getContractAddress: contractAddress.getContractAddress,
formatEther: units.formatEther,
parseEther: units.parseEther,
formatUnits: units.formatUnits,
parseUnits: units.parseUnits,
keccak256: keccak256,
sha256: sha256,
randomBytes: randomBytes,
solidityPack: solidity.pack,
solidityKeccak256: solidity.keccak256,
soliditySha256: solidity.sha256,
splitSignature: convert.splitSignature,
}

12
utils/keccak256.js Normal file
View File

@@ -0,0 +1,12 @@
'use strict';
var sha3 = require('js-sha3');
var convert = require('./convert.js');
function keccak256(data) {
data = convert.arrayify(data);
return '0x' + sha3.keccak_256(data);
}
module.exports = keccak256;

38
utils/namehash.js Normal file
View File

@@ -0,0 +1,38 @@
'use strict';
var convert = require('./convert');
var utf8 = require('./utf8');
var keccak256 = require('./keccak256');
var Zeros = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var Partition = new RegExp("^((.*)\\.)?([^.]+)$");
var UseSTD3ASCIIRules = new RegExp("^[a-z0-9.-]*$");
function namehash(name, depth) {
name = name.toLowerCase();
// Supporting the full UTF-8 space requires additional (and large)
// libraries, so for now we simply do not support them.
// It should be fairly easy in the future to support systems with
// String.normalize, but that is future work.
if (!name.match(UseSTD3ASCIIRules)) {
throw new Error('contains invalid UseSTD3ASCIIRules characters');
}
var result = Zeros;
var processed = 0;
while (name.length && (!depth || processed < depth)) {
var partition = name.match(Partition);
var label = utf8.toUtf8Bytes(partition[3]);
result = keccak256(convert.concat([result, keccak256(label)]));
name = partition[2] || '';
processed++;
}
return convert.hexlify(result);
}
module.exports = namehash;

51
utils/pbkdf2.js Normal file
View File

@@ -0,0 +1,51 @@
'use strict';
var convert = require('./convert');
function pbkdf2(password, salt, iterations, keylen, createHmac) {
var hLen
var l = 1
var DK = new Uint8Array(keylen)
var block1 = new Uint8Array(salt.length + 4)
block1.set(salt);
//salt.copy(block1, 0, 0, salt.length)
var r
var T
for (var i = 1; i <= l; i++) {
//block1.writeUInt32BE(i, salt.length)
block1[salt.length] = (i >> 24) & 0xff;
block1[salt.length + 1] = (i >> 16) & 0xff;
block1[salt.length + 2] = (i >> 8) & 0xff;
block1[salt.length + 3] = i & 0xff;
var U = createHmac(password).update(block1).digest();
if (!hLen) {
hLen = U.length
T = new Uint8Array(hLen)
l = Math.ceil(keylen / hLen)
r = keylen - (l - 1) * hLen
}
//U.copy(T, 0, 0, hLen)
T.set(U);
for (var j = 1; j < iterations; j++) {
U = createHmac(password).update(U).digest()
for (var k = 0; k < hLen; k++) T[k] ^= U[k]
}
var destPos = (i - 1) * hLen
var len = (i === l ? r : hLen)
//T.copy(DK, destPos, 0, len)
DK.set(convert.arrayify(T).slice(0, len), destPos);
}
return convert.arrayify(DK)
}
module.exports = pbkdf2;

22
utils/properties.js Normal file
View File

@@ -0,0 +1,22 @@
'use strict';
function defineProperty(object, name, value) {
Object.defineProperty(object, name, {
enumerable: true,
value: value,
writable: false,
});
}
function defineFrozen(object, name, value) {
var frozen = JSON.stringify(value);
Object.defineProperty(object, name, {
enumerable: true,
get: function() { return JSON.parse(frozen); }
});
}
module.exports = {
defineFrozen: defineFrozen,
defineProperty: defineProperty,
};

8
utils/random-bytes.js Normal file
View File

@@ -0,0 +1,8 @@
'use strict';
var randomBytes = require('crypto').randomBytes;
module.exports = function(length) {
return new Uint8Array(randomBytes(length));
}

142
utils/rlp.js Normal file
View File

@@ -0,0 +1,142 @@
//See: https://github.com/ethereum/wiki/wiki/RLP
var convert = require('./convert.js');
function arrayifyInteger(value) {
var result = [];
while (value) {
result.unshift(value & 0xff);
value >>= 8;
}
return result;
}
function unarrayifyInteger(data, offset, length) {
var result = 0;
for (var i = 0; i < length; i++) {
result = (result * 256) + data[offset + i];
}
return result;
}
function _encode(object) {
if (Array.isArray(object)) {
var payload = [];
object.forEach(function(child) {
payload = payload.concat(_encode(child));
});
if (payload.length <= 55) {
payload.unshift(0xc0 + payload.length)
return payload;
}
var length = arrayifyInteger(payload.length);
length.unshift(0xf7 + length.length);
return length.concat(payload);
} else {
object = [].slice.call(convert.arrayify(object));
if (object.length === 1 && object[0] <= 0x7f) {
return object;
} else if (object.length <= 55) {
object.unshift(0x80 + object.length);
return object
}
var length = arrayifyInteger(object.length);
length.unshift(0xb7 + length.length);
return length.concat(object);
}
}
function encode(object) {
return convert.hexlify(_encode(object));
}
function _decodeChildren(data, offset, childOffset, length) {
var result = [];
while (childOffset < offset + 1 + length) {
var decoded = _decode(data, childOffset);
result.push(decoded.result);
childOffset += decoded.consumed;
if (childOffset > offset + 1 + length) {
throw new Error('invalid rlp');
}
}
return {consumed: (1 + length), result: result};
}
// returns { consumed: number, result: Object }
function _decode(data, offset) {
if (data.length === 0) { throw new Error('invalid rlp data'); }
// Array with extra length prefix
if (data[offset] >= 0xf8) {
var lengthLength = data[offset] - 0xf7;
if (offset + 1 + lengthLength > data.length) {
throw new Error('too short');
}
var length = unarrayifyInteger(data, offset + 1, lengthLength);
if (offset + 1 + lengthLength + length > data.length) {
throw new Error('to short');
}
return _decodeChildren(data, offset, offset + 1 + lengthLength, lengthLength + length);
} else if (data[offset] >= 0xc0) {
var length = data[offset] - 0xc0;
if (offset + 1 + length > data.length) {
throw new Error('invalid rlp data');
}
return _decodeChildren(data, offset, offset + 1, length);
} else if (data[offset] >= 0xb8) {
var lengthLength = data[offset] - 0xb7;
if (offset + 1 + lengthLength > data.length) {
throw new Error('invalid rlp data');
}
var length = unarrayifyInteger(data, offset + 1, lengthLength);
if (offset + 1 + lengthLength + length > data.length) {
throw new Error('invalid rlp data');
}
var result = convert.hexlify(data.slice(offset + 1 + lengthLength, offset + 1 + lengthLength + length));
return { consumed: (1 + lengthLength + length), result: result }
} else if (data[offset] >= 0x80) {
var length = data[offset] - 0x80;
if (offset + 1 + length > data.offset) {
throw new Error('invlaid rlp data');
}
var result = convert.hexlify(data.slice(offset + 1, offset + 1 + length));
return { consumed: (1 + length), result: result }
}
return { consumed: 1, result: convert.hexlify(data[offset]) };
}
function decode(data) {
data = convert.arrayify(data);
var decoded = _decode(data, 0);
if (decoded.consumed !== data.length) {
throw new Error('invalid rlp data');
}
return decoded.result;
}
module.exports = {
encode: encode,
decode: decode,
}

449
utils/secret-storage.js Normal file
View File

@@ -0,0 +1,449 @@
'use strict';
var aes = require('aes-js');
var scrypt = require('scrypt-js');
var uuid = require('uuid');
var hmac = require('../utils/hmac');
var pbkdf2 = require('../utils/pbkdf2');
var utils = require('../util.js');
var SigningKey = require('./signing-key');
var HDNode = require('./hdnode');
// @TODO: Maybe move this to HDNode?
var defaultPath = "m/44'/60'/0'/0/0";
function arrayify(hexString) {
if (typeof(hexString) === 'string' && hexString.substring(0, 2) !== '0x') {
hexString = '0x' + hexString;
}
return utils.arrayify(hexString);
}
function zpad(value, length) {
value = String(value);
while (value.length < length) { value = '0' + value; }
return value;
}
function getPassword(password) {
if (typeof(password) === 'string') {
return utils.toUtf8Bytes(password, 'NFKC');
}
return utils.arrayify(password, 'password');
}
// Search an Object and its children recursively, caselessly.
function searchPath(object, path) {
var currentChild = object;
var comps = path.toLowerCase().split('/');
for (var i = 0; i < comps.length; i++) {
// Search for a child object with a case-insensitive matching key
var matchingChild = null;
for (var key in currentChild) {
if (key.toLowerCase() === comps[i]) {
matchingChild = currentChild[key];
break;
}
}
// Didn't find one. :'(
if (matchingChild === null) {
return null;
}
// Now check this child...
currentChild = matchingChild;
}
return currentChild;
}
var secretStorage = {};
utils.defineProperty(secretStorage, 'isCrowdsaleWallet', function(json) {
try {
var data = JSON.parse(json);
} catch (error) { return false; }
return (data.encseed && data.ethaddr);
});
utils.defineProperty(secretStorage, 'isValidWallet', function(json) {
try {
var data = JSON.parse(json);
} catch (error) { return false; }
if (!data.version || parseInt(data.version) !== data.version || parseInt(data.version) !== 3) {
return false;
}
// @TODO: Put more checks to make sure it has kdf, iv and all that good stuff
return true;
});
// See: https://github.com/ethereum/pyethsaletool
utils.defineProperty(secretStorage, 'decryptCrowdsale', function(json, password) {
var data = JSON.parse(json);
password = getPassword(password);
// Ethereum Address
var ethaddr = utils.getAddress(searchPath(data, 'ethaddr'));
// Encrypted Seed
var encseed = arrayify(searchPath(data, 'encseed'));
if (!encseed || (encseed.length % 16) !== 0) {
throw new Error('invalid encseed');
}
var key = pbkdf2(password, password, 2000, 32, hmac.createSha256Hmac).slice(0, 16);
var iv = encseed.slice(0, 16);
var encryptedSeed = encseed.slice(16);
// Decrypt the seed
var aesCbc = new aes.ModeOfOperation.cbc(key, iv);
var seed = utils.arrayify(aesCbc.decrypt(encryptedSeed));
seed = aes.padding.pkcs7.strip(seed);
// This wallet format is weird... Convert the binary encoded hex to a string.
var seedHex = '';
for (var i = 0; i < seed.length; i++) {
seedHex += String.fromCharCode(seed[i]);
}
var seedHexBytes = utils.toUtf8Bytes(seedHex);
var signingKey = new SigningKey(utils.keccak256(seedHexBytes));
if (signingKey.address !== ethaddr) {
throw new Error('corrupt crowdsale wallet');
}
return signingKey;
});
utils.defineProperty(secretStorage, 'decrypt', function(json, password, progressCallback) {
var data = JSON.parse(json);
password = getPassword(password);
var decrypt = function(key, ciphertext) {
var cipher = searchPath(data, 'crypto/cipher');
if (cipher === 'aes-128-ctr') {
var iv = arrayify(searchPath(data, 'crypto/cipherparams/iv'), 'crypto/cipherparams/iv')
var counter = new aes.Counter(iv);
var aesCtr = new aes.ModeOfOperation.ctr(key, counter);
return arrayify(aesCtr.decrypt(ciphertext));
}
return null;
};
var computeMAC = function(derivedHalf, ciphertext) {
return utils.keccak256(utils.concat([derivedHalf, ciphertext]));
}
var getSigningKey = function(key, reject) {
var ciphertext = arrayify(searchPath(data, 'crypto/ciphertext'));
var computedMAC = utils.hexlify(computeMAC(key.slice(16, 32), ciphertext)).substring(2);
if (computedMAC !== searchPath(data, 'crypto/mac').toLowerCase()) {
reject(new Error('invalid password'));
return null;
}
var privateKey = decrypt(key.slice(0, 16), ciphertext);
var mnemonicKey = key.slice(32, 64);
if (!privateKey) {
reject(new Error('unsupported cipher'));
return null;
}
var signingKey = new SigningKey(privateKey);
if (signingKey.address !== utils.getAddress(data.address)) {
reject(new Error('address mismatch'));
return null;
}
// Version 0.1 x-ethers metadata must contain an encrypted mnemonic phrase
if (searchPath(data, 'x-ethers/version') === '0.1') {
var mnemonicCiphertext = arrayify(searchPath(data, 'x-ethers/mnemonicCiphertext'), 'x-ethers/mnemonicCiphertext');
var mnemonicIv = arrayify(searchPath(data, 'x-ethers/mnemonicCounter'), 'x-ethers/mnemonicCounter');
var mnemonicCounter = new aes.Counter(mnemonicIv);
var mnemonicAesCtr = new aes.ModeOfOperation.ctr(mnemonicKey, mnemonicCounter);
var path = searchPath(data, 'x-ethers/path') || defaultPath;
var entropy = arrayify(mnemonicAesCtr.decrypt(mnemonicCiphertext));
var mnemonic = HDNode.entropyToMnemonic(entropy);
if (HDNode.fromMnemonic(mnemonic).derivePath(path).privateKey != utils.hexlify(privateKey)) {
reject(new Error('mnemonic mismatch'));
return null;
}
signingKey.mnemonic = mnemonic;
signingKey.path = path;
}
return signingKey;
}
return new Promise(function(resolve, reject) {
var kdf = searchPath(data, 'crypto/kdf');
if (kdf && typeof(kdf) === 'string') {
if (kdf.toLowerCase() === 'scrypt') {
var salt = arrayify(searchPath(data, 'crypto/kdfparams/salt'), 'crypto/kdfparams/salt');
var N = parseInt(searchPath(data, 'crypto/kdfparams/n'));
var r = parseInt(searchPath(data, 'crypto/kdfparams/r'));
var p = parseInt(searchPath(data, 'crypto/kdfparams/p'));
if (!N || !r || !p) {
reject(new Error('unsupported key-derivation function parameters'));
return;
}
// Make sure N is a power of 2
if ((N & (N - 1)) !== 0) {
reject(new Error('unsupported key-derivation function parameter value for N'));
return;
}
var dkLen = parseInt(searchPath(data, 'crypto/kdfparams/dklen'));
if (dkLen !== 32) {
reject( new Error('unsupported key-derivation derived-key length'));
return;
}
scrypt(password, salt, N, r, p, 64, function(error, progress, key) {
if (error) {
error.progress = progress;
reject(error);
} else if (key) {
key = arrayify(key);
var signingKey = getSigningKey(key, reject);
if (!signingKey) { return; }
if (progressCallback) { progressCallback(1); }
resolve(signingKey);
} else if (progressCallback) {
return progressCallback(progress);
}
});
} else if (kdf.toLowerCase() === 'pbkdf2') {
var salt = arrayify(searchPath(data, 'crypto/kdfparams/salt'), 'crypto/kdfparams/salt');
var prfFunc = null;
var prf = searchPath(data, 'crypto/kdfparams/prf');
if (prf === 'hmac-sha256') {
prfFunc = hmac.createSha256Hmac;
} else if (prf === 'hmac-sha512') {
prfFunc = hmac.createSha512Hmac;
} else {
reject(new Error('unsupported prf'));
return;
}
var c = parseInt(searchPath(data, 'crypto/kdfparams/c'));
var dkLen = parseInt(searchPath(data, 'crypto/kdfparams/dklen'));
if (dkLen !== 32) {
reject( new Error('unsupported key-derivation derived-key length'));
return;
}
var key = pbkdf2(password, salt, c, dkLen, prfFunc);
var signingKey = getSigningKey(key, reject);
if (!signingKey) { return; }
resolve(signingKey);
} else {
reject(new Error('unsupported key-derivation function'));
}
} else {
reject(new Error('unsupported key-derivation function'));
}
});
});
utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, options, progressCallback) {
// the options are optional, so adjust the call as needed
if (typeof(options) === 'function' && !progressCallback) {
progressCallback = options;
options = {};
}
if (!options) { options = {}; }
// Check the private key
if (privateKey instanceof SigningKey) {
privateKey = privateKey.privateKey;
}
privateKey = arrayify(privateKey, 'private key');
if (privateKey.length !== 32) { throw new Error('invalid private key'); }
password = getPassword(password);
var entropy = options.entropy;
if (options.mnemonic) {
if (entropy) {
if (HDNode.entropyToMnemonic(entropy) !== options.mnemonic) {
throw new Error('entropy and mnemonic mismatch');
}
} else {
entropy = HDNode.mnemonicToEntropy(options.mnemonic);
}
}
if (entropy) {
entropy = arrayify(entropy, 'entropy');
}
var path = options.path;
if (entropy && !path) {
path = defaultPath;
}
var client = options.client;
if (!client) { client = "ethers.js"; }
// Check/generate the salt
var salt = options.salt;
if (salt) {
salt = arrayify(salt, 'salt');
} else {
salt = utils.randomBytes(32);;
}
// Override initialization vector
var iv = null;
if (options.iv) {
iv = arrayify(options.iv, 'iv');
if (iv.length !== 16) { throw new Error('invalid iv'); }
} else {
iv = utils.randomBytes(16);
}
// Override the uuid
var uuidRandom = options.uuid;
if (uuidRandom) {
uuidRandom = arrayify(uuidRandom, 'uuid');
if (uuidRandom.length !== 16) { throw new Error('invalid uuid'); }
} else {
uuidRandom = utils.randomBytes(16);
}
// Override the scrypt password-based key derivation function parameters
var N = (1 << 17), r = 8, p = 1;
if (options.scrypt) {
if (options.scrypt.N) { N = options.scrypt.N; }
if (options.scrypt.r) { r = options.scrypt.r; }
if (options.scrypt.p) { p = options.scrypt.p; }
}
return new Promise(function(resolve, reject) {
// We take 64 bytes:
// - 32 bytes As normal for the Web3 secret storage (derivedKey, macPrefix)
// - 32 bytes AES key to encrypt mnemonic with (required here to be Ethers Wallet)
scrypt(password, salt, N, r, p, 64, function(error, progress, key) {
if (error) {
error.progress = progress;
reject(error);
} else if (key) {
key = arrayify(key);
// This will be used to encrypt the wallet (as per Web3 secret storage)
var derivedKey = key.slice(0, 16);
var macPrefix = key.slice(16, 32);
// This will be used to encrypt the mnemonic phrase (if any)
var mnemonicKey = key.slice(32, 64);
// Get the address for this private key
var address = (new SigningKey(privateKey)).address;
// Encrypt the private key
var counter = new aes.Counter(iv);
var aesCtr = new aes.ModeOfOperation.ctr(derivedKey, counter);
var ciphertext = utils.arrayify(aesCtr.encrypt(privateKey));
// Compute the message authentication code, used to check the password
var mac = utils.keccak256(utils.concat([macPrefix, ciphertext]))
// See: https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
var data = {
address: address.substring(2).toLowerCase(),
id: uuid.v4({ random: uuidRandom }),
version: 3,
Crypto: {
cipher: 'aes-128-ctr',
cipherparams: {
iv: utils.hexlify(iv).substring(2),
},
ciphertext: utils.hexlify(ciphertext).substring(2),
kdf: 'scrypt',
kdfparams: {
salt: utils.hexlify(salt).substring(2),
n: N,
dklen: 32,
p: p,
r: r
},
mac: mac.substring(2)
}
};
// If we have a mnemonic, encrypt it into the JSON wallet
if (entropy) {
var mnemonicIv = utils.randomBytes(16);
var mnemonicCounter = new aes.Counter(mnemonicIv);
var mnemonicAesCtr = new aes.ModeOfOperation.ctr(mnemonicKey, mnemonicCounter);
var mnemonicCiphertext = utils.arrayify(mnemonicAesCtr.encrypt(entropy));
var now = new Date();
var timestamp = (now.getUTCFullYear() + '-' +
zpad(now.getUTCMonth() + 1, 2) + '-' +
zpad(now.getUTCDate(), 2) + 'T' +
zpad(now.getUTCHours(), 2) + '-' +
zpad(now.getUTCMinutes(), 2) + '-' +
zpad(now.getUTCSeconds(), 2) + '.0Z'
);
data['x-ethers'] = {
client: client,
gethFilename: ('UTC--' + timestamp + '--' + data.address),
mnemonicCounter: utils.hexlify(mnemonicIv).substring(2),
mnemonicCiphertext: utils.hexlify(mnemonicCiphertext).substring(2),
version: "0.1"
};
}
if (progressCallback) { progressCallback(1); }
resolve(JSON.stringify(data));
} else if (progressCallback) {
return progressCallback(progress);
}
});
});
});
module.exports = secretStorage;

23
utils/sha2.js Normal file
View File

@@ -0,0 +1,23 @@
'use strict';
var hash = require('hash.js');
var convert = require('./convert.js');
function sha256(data) {
data = convert.arrayify(data);
return '0x' + (hash.sha256().update(data).digest('hex'));
}
function sha512(data) {
data = convert.arrayify(data);
return '0x' + (hash.sha512().update(data).digest('hex'));
}
module.exports = {
sha256: sha256,
sha512: sha512,
createSha256: hash.sha256,
createSha512: hash.sha512,
}

104
utils/signing-key.js Normal file
View File

@@ -0,0 +1,104 @@
'use strict';
/**
* SigningKey
*
*
*/
var secp256k1 = new (require('elliptic')).ec('secp256k1');
var utils = (function() {
var convert = require('../utils/convert');
return {
defineProperty: require('../utils/properties').defineProperty,
arrayify: convert.arrayify,
hexlify: convert.hexlify,
padZeros: convert.padZeros,
getAddress: require('../utils/address').getAddress,
keccak256: require('../utils/keccak256')
};
})();
var errors = require('../utils/errors');
function SigningKey(privateKey) {
errors.checkNew(this, SigningKey);
try {
privateKey = utils.arrayify(privateKey);
if (privateKey.length !== 32) {
errors.throwError('exactly 32 bytes required', errors.INVALID_ARGUMENT, { value: privateKey });
}
} catch(error) {
var params = { arg: 'privateKey', reason: error.reason, value: '[REDACTED]' }
if (error.value) {
if(typeof(error.value.length) === 'number') {
params.length = error.value.length;
}
params.type = typeof(error.value);
}
errors.throwError('invalid private key', error.code, params);
}
utils.defineProperty(this, 'privateKey', utils.hexlify(privateKey))
var keyPair = secp256k1.keyFromPrivate(privateKey);
utils.defineProperty(this, 'publicKey', '0x' + keyPair.getPublic(true, 'hex'))
var address = SigningKey.publicKeyToAddress('0x' + keyPair.getPublic(false, 'hex'));
utils.defineProperty(this, 'address', address)
utils.defineProperty(this, 'signDigest', function(digest) {
var signature = keyPair.sign(utils.arrayify(digest), {canonical: true});
var r = '0x' + signature.r.toString(16);
var s = '0x' + signature.s.toString(16);
return {
recoveryParam: signature.recoveryParam,
r: utils.hexlify(utils.padZeros(r, 32)),
s: utils.hexlify(utils.padZeros(s, 32))
}
});
}
utils.defineProperty(SigningKey, 'recover', function(digest, r, s, recoveryParam) {
var signature = {
r: utils.arrayify(r),
s: utils.arrayify(s)
};
var publicKey = secp256k1.recoverPubKey(utils.arrayify(digest), signature, recoveryParam);
return SigningKey.publicKeyToAddress('0x' + publicKey.encode('hex', false));
});
utils.defineProperty(SigningKey, 'getPublicKey', function(value, compressed) {
value = utils.arrayify(value);
compressed = !!compressed;
if (value.length === 32) {
var keyPair = secp256k1.keyFromPrivate(value);
return '0x' + keyPair.getPublic(compressed, 'hex');
} else if (value.length === 33) {
var keyPair = secp256k1.keyFromPublic(value);
return '0x' + keyPair.getPublic(compressed, 'hex');
} else if (value.length === 65) {
var keyPair = secp256k1.keyFromPublic(value);
return '0x' + keyPair.getPublic(compressed, 'hex');
}
throw new Error('invalid value');
});
utils.defineProperty(SigningKey, 'publicKeyToAddress', function(publicKey) {
publicKey = '0x' + SigningKey.getPublicKey(publicKey, false).slice(4);
return utils.getAddress('0x' + utils.keccak256(publicKey).substring(26));
});
module.exports = SigningKey;

97
utils/solidity.js Normal file
View File

@@ -0,0 +1,97 @@
'use strict';
var bigNumberify = require('./bignumber').bigNumberify;
var convert = require('./convert');
var getAddress = require('./address').getAddress;
var utf8 = require('./utf8');
var hashKeccak256 = require('./keccak256');
var hashSha256 = require('./sha2').sha256;
var regexBytes = new RegExp("^bytes([0-9]+)$");
var regexNumber = new RegExp("^(u?int)([0-9]*)$");
var regexArray = new RegExp("^(.*)\\[([0-9]*)\\]$");
var Zeros = '0000000000000000000000000000000000000000000000000000000000000000';
function _pack(type, value, isArray) {
switch(type) {
case 'address':
if (isArray) { return convert.padZeros(value, 32); }
return convert.arrayify(value);
case 'string':
return utf8.toUtf8Bytes(value);
case 'bytes':
return convert.arrayify(value);
case 'bool':
value = (value ? '0x01': '0x00');
if (isArray) { return convert.padZeros(value, 32); }
return convert.arrayify(value);
}
var match = type.match(regexNumber);
if (match) {
var signed = (match[1] === 'int')
var size = parseInt(match[2] || "256")
if ((size % 8 != 0) || size === 0 || size > 256) {
throw new Error('invalid number type - ' + type);
}
if (isArray) { size = 256; }
value = bigNumberify(value).toTwos(size);
return convert.padZeros(value, size / 8);
}
match = type.match(regexBytes);
if (match) {
var size = match[1];
if (size != parseInt(size) || size === 0 || size > 32) {
throw new Error('invalid number type - ' + type);
}
size = parseInt(size);
if (convert.arrayify(value).byteLength !== size) { throw new Error('invalid value for ' + type); }
if (isArray) { return (value + Zeros).substring(0, 66); }
return value;
}
match = type.match(regexArray);
if (match) {
var baseType = match[1];
var count = parseInt(match[2] || value.length);
if (count != value.length) { throw new Error('invalid value for ' + type); }
var result = [];
value.forEach(function(value) {
value = _pack(baseType, value, true);
result.push(value);
});
return convert.concat(result);
}
throw new Error('unknown type - ' + type);
}
function pack(types, values) {
if (types.length != values.length) { throw new Error('type/value count mismatch'); }
var tight = [];
types.forEach(function(type, index) {
tight.push(_pack(type, values[index]));
});
return convert.hexlify(convert.concat(tight));
}
function keccak256(types, values) {
return hashKeccak256(pack(types, values));
}
function sha256(types, values) {
return hashSha256(pack(types, values));
}
module.exports = {
pack: pack,
keccak256: keccak256,
sha256: sha256,
}

11
utils/throw-error.js Normal file
View File

@@ -0,0 +1,11 @@
'use strict';
function throwError(message, params) {
var error = new Error(message);
for (var key in params) {
error[key] = params[key];
}
throw error;
}
module.exports = throwError;

148
utils/units.js Normal file
View File

@@ -0,0 +1,148 @@
var bigNumberify = require('./bignumber.js').bigNumberify;
var throwError = require('./throw-error');
var zero = new bigNumberify(0);
var negative1 = new bigNumberify(-1);
var names = [
'wei',
'kwei',
'Mwei',
'Gwei',
'szabo',
'finny',
'ether',
];
var getUnitInfo = (function() {
var unitInfos = {};
function getUnitInfo(value) {
return {
decimals: value.length - 1,
tenPower: bigNumberify(value)
};
}
// Cache the common units
var value = '1';
names.forEach(function(name) {
var info = getUnitInfo(value);
unitInfos[name.toLowerCase()] = info;
unitInfos[String(info.decimals)] = info;
value += '000';
});
return function(name) {
// Try the cache
var info = unitInfos[String(name).toLowerCase()];
if (!info && typeof(name) === 'number' && parseInt(name) == name && name >= 0 && name <= 256) {
var value = '1';
for (var i = 0; i < name; i++) { value += '0'; }
info = getUnitInfo(value);
}
// Make sure we got something
if (!info) { throwError('invalid unitType', { unitType: name }); }
return info;
}
})();
function formatUnits(value, unitType, options) {
if (typeof(unitType) === 'object' && !options) {
options = unitType;
unitType = undefined;
}
if (unitType == null) { unitType = 18; }
var unitInfo = getUnitInfo(unitType);
// Make sure wei is a big number (convert as necessary)
value = bigNumberify(value);
if (!options) { options = {}; }
var negative = value.lt(zero);
if (negative) { value = value.mul(negative1); }
var fraction = value.mod(unitInfo.tenPower).toString(10);
while (fraction.length < unitInfo.decimals) { fraction = '0' + fraction; }
// Strip off trailing zeros (but keep one if would otherwise be bare decimal point)
if (!options.pad) {
fraction = fraction.match(/^([0-9]*[1-9]|0)(0*)/)[1];
}
var whole = value.div(unitInfo.tenPower).toString(10);
if (options.commify) {
whole = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",")
}
var value = whole + '.' + fraction;
if (negative) { value = '-' + value; }
return value;
}
function parseUnits(value, unitType) {
if (unitType == null) { unitType = 18; }
var unitInfo = getUnitInfo(unitType);
if (typeof(value) !== 'string' || !value.match(/^-?[0-9.,]+$/)) {
throwError('invalid value', { input: value });
}
// Remove commas
var value = value.replace(/,/g,'');
// Is it negative?
var negative = (value.substring(0, 1) === '-');
if (negative) { value = value.substring(1); }
if (value === '.') { throwError('invalid value', { input: value }); }
// Split it into a whole and fractional part
var comps = value.split('.');
if (comps.length > 2) { throwError('too many decimal points', { input: value }); }
var whole = comps[0], fraction = comps[1];
if (!whole) { whole = '0'; }
if (!fraction) { fraction = '0'; }
// Prevent underflow
if (fraction.length > unitInfo.decimals) {
throwError('too many decimal places', { input: value, decimals: fraction.length });
}
// Fully pad the string with zeros to get to wei
while (fraction.length < unitInfo.decimals) { fraction += '0'; }
whole = bigNumberify(whole);
fraction = bigNumberify(fraction);
var wei = (whole.mul(unitInfo.tenPower)).add(fraction);
if (negative) { wei = wei.mul(negative1); }
return wei;
}
function formatEther(wei, options) {
return formatUnits(wei, 18, options);
}
function parseEther(ether) {
return parseUnits(ether, 18);
}
module.exports = {
formatEther: formatEther,
parseEther: parseEther,
formatUnits: formatUnits,
parseUnits: parseUnits,
}

113
utils/utf8.js Normal file
View File

@@ -0,0 +1,113 @@
var convert = require('./convert.js');
// http://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array
function utf8ToBytes(str) {
var result = [];
var offset = 0;
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
if (c < 128) {
result[offset++] = c;
} else if (c < 2048) {
result[offset++] = (c >> 6) | 192;
result[offset++] = (c & 63) | 128;
} else if (((c & 0xFC00) == 0xD800) && (i + 1) < str.length && ((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) {
// Surrogate Pair
c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF);
result[offset++] = (c >> 18) | 240;
result[offset++] = ((c >> 12) & 63) | 128;
result[offset++] = ((c >> 6) & 63) | 128;
result[offset++] = (c & 63) | 128;
} else {
result[offset++] = (c >> 12) | 224;
result[offset++] = ((c >> 6) & 63) | 128;
result[offset++] = (c & 63) | 128;
}
}
return convert.arrayify(result);
};
// http://stackoverflow.com/questions/13356493/decode-utf-8-with-javascript#13691499
function bytesToUtf8(bytes) {
bytes = convert.arrayify(bytes);
var result = '';
var i = 0;
// Invalid bytes are ignored
while(i < bytes.length) {
var c = bytes[i++];
if (c >> 7 == 0) {
// 0xxx xxxx
result += String.fromCharCode(c);
continue;
}
// Invalid starting byte
if (c >> 6 == 0x02) { continue; }
// Multibyte; how many bytes left for thus character?
var extraLength = null;
if (c >> 5 == 0x06) {
extraLength = 1;
} else if (c >> 4 == 0x0e) {
extraLength = 2;
} else if (c >> 3 == 0x1e) {
extraLength = 3;
} else if (c >> 2 == 0x3e) {
extraLength = 4;
} else if (c >> 1 == 0x7e) {
extraLength = 5;
} else {
continue;
}
// Do we have enough bytes in our data?
if (i + extraLength > bytes.length) {
// If there is an invalid unprocessed byte, try to continue
for (; i < bytes.length; i++) {
if (bytes[i] >> 6 != 0x02) { break; }
}
if (i != bytes.length) continue;
// All leftover bytes are valid.
return result;
}
// Remove the UTF-8 prefix from the char (res)
var res = c & ((1 << (8 - extraLength - 1)) - 1);
var count;
for (count = 0; count < extraLength; count++) {
var nextChar = bytes[i++];
// Is the char valid multibyte part?
if (nextChar >> 6 != 0x02) {break;};
res = (res << 6) | (nextChar & 0x3f);
}
if (count != extraLength) {
i--;
continue;
}
if (res <= 0xffff) {
result += String.fromCharCode(res);
continue;
}
res -= 0x10000;
result += String.fromCharCode(((res >> 10) & 0x3ff) + 0xd800, (res & 0x3ff) + 0xdc00);
}
return result;
}
module.exports = {
toUtf8Bytes: utf8ToBytes,
toUtf8String: bytesToUtf8,
};

1
utils/words.json Normal file

File diff suppressed because one or more lines are too long