首次放到 git
This commit is contained in:
commit
16cca302b7
60
Account.js
Normal file
60
Account.js
Normal file
@ -0,0 +1,60 @@
|
||||
'use strict'
|
||||
const Coins = {}
|
||||
Coins.TIC = require('./tic.js').TIC;
|
||||
Coins.ETH = require('./eth.js').ETH;
|
||||
Coins.ERC20 = require('./eth.js').ERC20;
|
||||
Coins.BTC = require('./btc.js').BTC;
|
||||
|
||||
class Account {
|
||||
constructor(coinType,privateKey,contractAddress){
|
||||
if(coinType === 'tic' || coinType === 'btc' || coinType === 'eth')
|
||||
return new Coins[coinType.toUpperCase()](privateKey)
|
||||
else
|
||||
return new Coins.ERC20(privateKey,contractAddress)
|
||||
}
|
||||
static generateNewAccount(coinType){
|
||||
if(coinType === 'tic' || coinType === 'btc')
|
||||
return Coins[coinType.toUpperCase()].generateNewAccount()
|
||||
return Coins.ETH.generateNewAccount()
|
||||
}
|
||||
static fromMnemonic(coinType,mnemonic){
|
||||
if(coinType === 'tic' || coinType === 'btc' || coinType === 'eth')
|
||||
return Coins[coinType.toUpperCase()].fromMnemonic(mnemonic)
|
||||
return Coins.ETH.fromMnemonic(mnemonic)
|
||||
}
|
||||
static fromPrivateKey(coinType,privateKey,contractAddress){
|
||||
if(coinType === 'tic' || coinType === 'btc' || coinType === 'eth')
|
||||
return new Coins[coinType.toUpperCase()](privateKey)
|
||||
return new Coins.ERC20(privateKey,contractAddress)
|
||||
}
|
||||
static async fromOfficalWallet(encryptedWallet,key){
|
||||
return await Coins.ETH.fromEncryptedWallet(encryptedWallet,key)
|
||||
}
|
||||
static async getBalance(coinType,address,contractAddress){
|
||||
if(coinType === 'tic' || coinType === 'btc' || coinType === 'eth')
|
||||
return await Coins[coinType.toUpperCase()].getBalance(address)
|
||||
return await Coins.ERC20.getBalance(address,contractAddress)
|
||||
}
|
||||
static async getActions(coinType,address,contractAddress){
|
||||
if(coinType === 'tic' || coinType === 'btc' || coinType === 'eth')
|
||||
return await Coins[coinType.toUpperCase()].getActions(address)
|
||||
return await Coins.ERC20.getActions(address,contractAddress)
|
||||
}
|
||||
static decrypt(coinType,encryptedWallet,key){
|
||||
if(coinType === 'tic' || coinType === 'btc' || coinType === 'eth')
|
||||
return Coins[coinType.toUpperCase()].decrypt(encryptedWallet,key)
|
||||
return Coins.ETH.decrypt(encryptedWallet,key)
|
||||
}
|
||||
static isValidAddress(coinType, address){
|
||||
if(!coinType || !address) return null
|
||||
switch(coinType){
|
||||
case "tic": return Coins.TIC.isValidAddress(address)
|
||||
case "btc": return Coins.BTC.isValidAddress(address)
|
||||
default: return Coins.ETH.isValidAddress(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Account
|
||||
}
|
12
README.md
Normal file
12
README.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: Toolset for Cryptocurrency
|
||||
tags:
|
||||
- bitcoin
|
||||
- ethereum
|
||||
- tic
|
||||
---
|
||||
to start
|
||||
> git clone https://github.com/chaosBreaking/BitHoole.git
|
||||
|
||||
for use
|
||||
> account = require('BitHoole')
|
151
btc.js
Normal file
151
btc.js
Normal file
@ -0,0 +1,151 @@
|
||||
'use strict'
|
||||
|
||||
const axios = require('axios');
|
||||
const HDNode = require('./utils/hdnode');
|
||||
const bitcoinjs = require('bitcoinjs-lib');
|
||||
// const bitcore = require('tic.common').Bitcore;
|
||||
const ticCommon = require('tic.common').Crypto;
|
||||
const BTC_NODE = require('./netConfig').BTC_NODE;
|
||||
const BTC_NODE2 = require('./netConfig').BTC_NODE2;
|
||||
const BTC_TXFEE = 30;
|
||||
|
||||
class BTC {
|
||||
constructor(privateKey){
|
||||
if(!ticCommon.isSeckey(privateKey)) throw new Error('Invalid PrivateKey')
|
||||
var publicKey = ticCommon.seckey2pubkey(privateKey)
|
||||
Object.defineProperties(this,{
|
||||
"privateKey" : {
|
||||
enumerable : true,
|
||||
writable : false,
|
||||
value : privateKey
|
||||
},
|
||||
"publicKey": {
|
||||
enumerable : true,
|
||||
writable : false,
|
||||
value : ticCommon.seckey2pubkey(privateKey,{coin:"BTC"})
|
||||
},
|
||||
"address" : {
|
||||
enumerable : true,
|
||||
writable : false,
|
||||
value : ticCommon.pubkey2address(publicKey,{coin:"BTC"})
|
||||
},
|
||||
"url" : {
|
||||
enumerable : true,
|
||||
get: function() { return this._url; },
|
||||
set: function(url) {
|
||||
if (typeof(url) !== 'string') { throw new Error('invalid url'); }
|
||||
this._url = url;
|
||||
}
|
||||
},
|
||||
"defaultGas":{
|
||||
enumerable: true,
|
||||
get: function() { return this._defaultGasFee; },
|
||||
set: function(value) {
|
||||
if (typeof(value) !== 'number') { throw new Error('invalid defaultGasFee'); }
|
||||
this._defaultGasFee = value;
|
||||
}
|
||||
}
|
||||
})
|
||||
this._url = BTC_NODE;
|
||||
this._defaultGasFee = BTC_TXFEE;
|
||||
|
||||
}
|
||||
static generateNewAccount(){
|
||||
var mnemonic = ticCommon.randomSecword()
|
||||
return Object.assign(new BTC(ticCommon.secword2keypair(mnemonic, {coin:"BTC"}).seckey),{mnemonic : mnemonic})
|
||||
}
|
||||
static fromMnemonic(mnemonic){
|
||||
HDNode.isValidMnemonic(mnemonic)
|
||||
return Object.assign(new BTC(ticCommon.secword2keypair(mnemonic, {coin:"BTC"}).seckey),{mnemonic:mnemonic})
|
||||
}
|
||||
static async getBalance(address){
|
||||
return (await axios.get(`${BTC_NODE}/addrs/${address}/balance`)).data.balance
|
||||
}
|
||||
static async getActions(address){
|
||||
return (await axios.get(`${BTC_NODE}/addrs/${address}`)).data.txrefs
|
||||
}
|
||||
static async getUTXO(address){
|
||||
// console.log(`${BTC_NODE2}/unspent?active=${address}`,`${BTC_NODE2}/unspent?active=${address}`)
|
||||
try {
|
||||
return (await axios.get(`${BTC_NODE2}/unspent?active=${address}`)).data.unspent_outputs
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
static encrypt(data, key){
|
||||
if(!data || !key) throw new Error('Required Params Missing')
|
||||
return ticCommon.encrypt(data,key)
|
||||
}
|
||||
static decrypt(data, key){
|
||||
return ticCommon.decrypt(data, key, {format:"json"}) //return null for wrong key
|
||||
}
|
||||
static isValidAddress(address){
|
||||
return address.length == 34 && address[0] == '1'
|
||||
}
|
||||
async sendTransaction(toAddress, amount, option = {gasFee : BTC_TXFEE}){
|
||||
let set = bitcoinjs.ECPair.fromPrivateKey(Buffer.from(this.privateKey,'hex'));//导入私钥用于签名
|
||||
let txb = new bitcoinjs.TransactionBuilder();//初始化交易对象
|
||||
let tx = await BTC.getUTXO('1DEP8i3QJCsomS4BSMY2RpU1upv62aGvhD')
|
||||
if(!tx) return null
|
||||
var tot = 0;//用于记录UTXO总量
|
||||
amount+=1e4;//消费金额是转出金额加上10000的矿工费
|
||||
txb.setVersion(1);//设置交易版本号
|
||||
for(var i=0;i<tx.length;i++){ //将UTXO的相关信息依次填入交易体中
|
||||
txb.addInput(tx[i].tx_hash_big_endian, tx[i].tx_output_n);
|
||||
tot+=tx[i].value;
|
||||
}
|
||||
|
||||
txb.addOutput(toAddress, amount-1e4);//填入转出目标地址和对应的金额
|
||||
txb.addOutput(this.address, tot-amount); //填入找零地址,也就是原地址,并填入把找零金额
|
||||
for(var i=0;i<tx.length;i++){//对交易体中的UTXO依次签名
|
||||
txb.sign(i, set);
|
||||
}
|
||||
// let txBody = txb.buildIncomplete().toHex()
|
||||
let data = {tx : txb.buildIncomplete().toHex()}
|
||||
try {
|
||||
let res = await axios.post(`${BTC_NODE}/txs/push`,data)
|
||||
return res
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
// async sendTransaction(toAddress, amount, option = {gasFee : BTC_TXFEE}){
|
||||
// var privateKey = bitcore.PrivateKey(this.privateKey)
|
||||
// var ecdsa = new bitcore.crypto.ECDSA();
|
||||
|
||||
// var newtx = {
|
||||
// inputs: [{addresses: [this.address]}],
|
||||
// outputs: [{addresses: [toAddress], value: amount}]
|
||||
// };
|
||||
// try {
|
||||
// var tmptx = (await axios.post('https://api.blockcypher.com/v1/btc/test3/txs/new',newtx)).data;
|
||||
// tmptx.pubkeys = [];
|
||||
// tmptx.pubkeys.push(privateKey.toPublicKey().toString("hex"))
|
||||
// ecdsa.hashbuf = bitcore.crypto.Hash.sha256(new Buffer(tmptx.tosign));
|
||||
// ecdsa.privkey = privateKey;
|
||||
// ecdsa.pubkey = privateKey.toPublicKey();
|
||||
// ecdsa.deterministicK();
|
||||
// let signatureExpected = ecdsa.sign();
|
||||
// tmptx.signatures = [Buffer.from(signatureExpected.sig.toDER()).toString('hex')]
|
||||
// let res = (await axios.post('https://api.blockcypher.com/v1/btc/test3/txs/send',tmptx)).data
|
||||
// return res
|
||||
// } catch (error) {
|
||||
// return error.response.data
|
||||
// }
|
||||
// }
|
||||
|
||||
async getBalance(){
|
||||
return await BTC.getBalance(this.address)
|
||||
}
|
||||
async getActions(){
|
||||
return await BTC.getActions(this.address)
|
||||
}
|
||||
encrypt(key){
|
||||
return BTC.encrypt(this,key)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BTC
|
||||
}
|
461
eth.js
Normal file
461
eth.js
Normal file
@ -0,0 +1,461 @@
|
||||
'use strict'
|
||||
const eth = require('etherscan-api').init('E3ZFFAEMNN33KX4HHVUZ4KF8XY1FXMR4BI');
|
||||
const secretStorage = require('./utils/secret-storage');
|
||||
const SigningKey = require('./utils/signing-key.js');
|
||||
const ticCommon = require('tic.common').Crypto;
|
||||
const HDNode = require('./utils/hdnode');
|
||||
const utils = require('./util.js');
|
||||
const axios = require('axios');
|
||||
|
||||
require('setimmediate');
|
||||
|
||||
const GAS_UNIT_WEI = 1e18; //1wei
|
||||
const GAS_UNIT_GWEI = 1e9; //1gwei = 1e9 wei
|
||||
const GAS_Fee = 0.000021;
|
||||
const GAS_Fee_ERC20 = 0.000060;
|
||||
const GAS_LIMIT = 21000;
|
||||
const GAS_LIMIT_ERC20 = 60000;
|
||||
const defaultPath = "m/44'/60'/0'/0/0";
|
||||
const ETH_NODE = require('./netConfig').ETH_NODE;
|
||||
|
||||
const transactionFields = [
|
||||
{name: 'nonce', maxLength: 32, },
|
||||
{name: 'gasPrice', maxLength: 32, },
|
||||
{name: 'gasLimit', maxLength: 32, },
|
||||
{name: 'to', length: 20, },
|
||||
{name: 'value', maxLength: 32, },
|
||||
{name: 'data'},
|
||||
];
|
||||
|
||||
class ETH {
|
||||
constructor(privateKey){
|
||||
if(privateKey.length == 64 && !(privateKey.split('x')[1] && privateKey.split('x')[0] === '0'))
|
||||
privateKey = '0x'+privateKey;
|
||||
var signingKey = privateKey;
|
||||
if (!(privateKey instanceof SigningKey)) {
|
||||
signingKey = new SigningKey(privateKey);
|
||||
}
|
||||
Object.defineProperties(this, {
|
||||
'privateKey' : {
|
||||
enumerable : true,
|
||||
writable : false,
|
||||
value : signingKey.privateKey
|
||||
},
|
||||
'address' : {
|
||||
enumerable : true,
|
||||
writable : false,
|
||||
value : signingKey.address,
|
||||
},
|
||||
'url' : {
|
||||
enumerable: true,
|
||||
get: function() { return this._url; },
|
||||
set: function(url) {
|
||||
if (typeof(url) !== 'string') { throw new Error('invalid url'); }
|
||||
this._url = url;
|
||||
},
|
||||
},
|
||||
'defaultGasFee' : {
|
||||
enumerable: true,
|
||||
get: function() { return this._defaultGasFee; },
|
||||
set: function(value) {
|
||||
if (typeof(value) !== 'number') { throw new Error('invalid defaultGasFee'); }
|
||||
this._defaultGasFee = value;
|
||||
}
|
||||
}
|
||||
})
|
||||
this._defaultGasFee = GAS_Fee;
|
||||
this._url = ETH_NODE;
|
||||
}
|
||||
static generateNewAccount(option = {path:defaultPath}){
|
||||
//major path as default path >/0'/0/0
|
||||
var mnemonic = ticCommon.randomSecword();
|
||||
return Object.assign(ETH.fromMnemonic(mnemonic, option),{mnemonic,mnemonic})
|
||||
}
|
||||
static fromMnemonic(mnemonic, option = {path:defaultPath}){
|
||||
HDNode.isValidMnemonic(mnemonic) //check valid mnemonic,will throw Error if not valid
|
||||
let seed = HDNode.mnemonicToSeed(mnemonic)
|
||||
return new ETH(HDNode.fromSeed(seed).derivePath(option.path).privateKey)
|
||||
}
|
||||
static async getBalance(address){
|
||||
if(!address){ throw new Error('Address is required'); }
|
||||
let res = (await axios.post(ETH_NODE,{
|
||||
"jsonrpc":"2.0","method":"eth_getBalance","params":[address, "latest"],"id":1
|
||||
})).data
|
||||
if(res)
|
||||
return parseInt(res.result)/1e18 //1000000000000000000
|
||||
else return null
|
||||
}
|
||||
static async getActions(address){
|
||||
let tx = await eth.account.txlist(address, 0 ,'latast')
|
||||
if(tx && tx.message === "OK")
|
||||
return tx.result
|
||||
else return []
|
||||
}
|
||||
static fromEncryptedWallet(json, password, progressCallback) {
|
||||
if (progressCallback && typeof(progressCallback) !== 'function') {
|
||||
throw new Error('invalid callback');
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
|
||||
if (secretStorage.isCrowdsaleWallet(json)) {
|
||||
try {
|
||||
var privateKey = secretStorage.decryptCrowdsale(json, password);
|
||||
resolve(new ETH(privateKey));
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
|
||||
} else if (secretStorage.isValidWallet(json)) {
|
||||
|
||||
secretStorage.decrypt(json, password, progressCallback).then(function(signingKey) {
|
||||
var wallet = new ETH(signingKey);
|
||||
if (signingKey.mnemonic && signingKey.path) {
|
||||
utils.defineProperty(wallet, 'mnemonic', signingKey.mnemonic);
|
||||
utils.defineProperty(wallet, 'path', signingKey.path);
|
||||
}
|
||||
resolve(wallet);
|
||||
return null;
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
}).catch(function(error) { reject(error); });
|
||||
|
||||
} else {
|
||||
reject('invalid wallet JSON');
|
||||
}
|
||||
});
|
||||
}
|
||||
static parseTransaction(rawTransaction){
|
||||
rawTransaction = utils.hexlify(rawTransaction, 'rawTransaction');
|
||||
var signedTransaction = utils.RLP.decode(rawTransaction);
|
||||
if (signedTransaction.length !== 9) { throw new Error('invalid transaction'); }
|
||||
|
||||
var raw = [];
|
||||
|
||||
var transaction = {};
|
||||
transactionFields.forEach(function(fieldInfo, index) {
|
||||
transaction[fieldInfo.name] = signedTransaction[index];
|
||||
raw.push(signedTransaction[index]);
|
||||
});
|
||||
|
||||
if (transaction.to) {
|
||||
if (transaction.to == '0x') {
|
||||
delete transaction.to;
|
||||
} else {
|
||||
transaction.to = utils.getAddress(transaction.to);
|
||||
}
|
||||
}
|
||||
|
||||
['gasPrice', 'gasLimit', 'nonce', 'value'].forEach(function(name) {
|
||||
if (!transaction[name]) { return; }
|
||||
if (transaction[name].length === 0) {
|
||||
transaction[name] = utils.bigNumberify(0);
|
||||
} else {
|
||||
transaction[name] = utils.bigNumberify(transaction[name]);
|
||||
}
|
||||
});
|
||||
|
||||
if (transaction.nonce) {
|
||||
transaction.nonce = transaction.nonce.toNumber();
|
||||
} else {
|
||||
transaction.nonce = 0;
|
||||
}
|
||||
|
||||
var v = utils.arrayify(signedTransaction[6]);
|
||||
var r = utils.arrayify(signedTransaction[7]);
|
||||
var s = utils.arrayify(signedTransaction[8]);
|
||||
|
||||
if (v.length >= 1 && r.length >= 1 && r.length <= 32 && s.length >= 1 && s.length <= 32) {
|
||||
transaction.v = utils.bigNumberify(v).toNumber();
|
||||
transaction.r = signedTransaction[7];
|
||||
transaction.s = signedTransaction[8];
|
||||
|
||||
var chainId = (transaction.v - 35) / 2;
|
||||
if (chainId < 0) { chainId = 0; }
|
||||
chainId = parseInt(chainId);
|
||||
|
||||
transaction.chainId = chainId;
|
||||
|
||||
var recoveryParam = transaction.v - 27;
|
||||
|
||||
if (chainId) {
|
||||
raw.push(utils.hexlify(chainId));
|
||||
raw.push('0x');
|
||||
raw.push('0x');
|
||||
recoveryParam -= chainId * 2 + 8;
|
||||
}
|
||||
|
||||
var digest = utils.keccak256(utils.RLP.encode(raw));
|
||||
try {
|
||||
transaction.from = SigningKey.recover(digest, r, s, recoveryParam);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return transaction;
|
||||
}
|
||||
static encrypt(data, key){
|
||||
if(!data || !key) throw new Error('Required Params Missing')
|
||||
return ticCommon.encrypt(data,key)
|
||||
}
|
||||
static decrypt(data, key){
|
||||
return ticCommon.decrypt(data, key, {format:"json"}) //return null for wrong key
|
||||
}
|
||||
static async estimateGasPrice(){
|
||||
try{
|
||||
return parseInt((await axios.post(ETH_NODE, {
|
||||
"method": "eth_gasPrice",
|
||||
"id": "6842",
|
||||
"jsonrpc": "2.0"
|
||||
})).data.result)/1e9
|
||||
}
|
||||
catch(err){
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
static isValidAddress(address){
|
||||
let res = address.match(/^(0x)?[0-9a-fA-F]{40}$/)
|
||||
return res && res[0].slice(0,2) === '0x'
|
||||
}
|
||||
|
||||
async getBalance(){
|
||||
return ETH.getBalance(this.address)
|
||||
}
|
||||
async getActions(){
|
||||
return ETH.getActions(this.address)
|
||||
}
|
||||
async getTransactionCount(){
|
||||
if(!this._url){ throw new Error('Base url required'); }
|
||||
var self = this;
|
||||
return (await axios.post(this._url,{
|
||||
"jsonrpc":"2.0","method":"eth_getTransactionCount","params":[self.address, "latest"],"id":1
|
||||
})).data.result||null
|
||||
}
|
||||
signTransaction(transaction){
|
||||
var chainId = transaction.chainId;
|
||||
if (chainId == null && this.provider) { chainId = this.provider.chainId; }
|
||||
if (!chainId) { chainId = 0; }
|
||||
|
||||
var raw = [];
|
||||
transactionFields.forEach(function(fieldInfo) {
|
||||
var value = transaction[fieldInfo.name] || ([]);
|
||||
value = utils.arrayify(utils.hexlify(value), fieldInfo.name);
|
||||
|
||||
// Fixed-width field
|
||||
if (fieldInfo.length && value.length !== fieldInfo.length && value.length > 0) {
|
||||
var error = new Error('invalid ' + fieldInfo.name);
|
||||
error.reason = 'wrong length';
|
||||
error.value = value;
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Variable-width (with a maximum)
|
||||
if (fieldInfo.maxLength) {
|
||||
value = utils.stripZeros(value);
|
||||
if (value.length > fieldInfo.maxLength) {
|
||||
var error = new Error('invalid ' + fieldInfo.name);
|
||||
error.reason = 'too long';
|
||||
error.value = value;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
raw.push(utils.hexlify(value));
|
||||
});
|
||||
|
||||
if (chainId) {
|
||||
raw.push(utils.hexlify(chainId));
|
||||
raw.push('0x');
|
||||
raw.push('0x');
|
||||
}
|
||||
|
||||
var digest = utils.keccak256(utils.RLP.encode(raw));
|
||||
var signingKey = new SigningKey(this.privateKey);
|
||||
var signature = signingKey.signDigest(digest);
|
||||
|
||||
var v = 27 + signature.recoveryParam
|
||||
if (chainId) {
|
||||
raw.pop();
|
||||
raw.pop();
|
||||
raw.pop();
|
||||
v += chainId * 2 + 8;
|
||||
}
|
||||
|
||||
raw.push(utils.hexlify(v));
|
||||
raw.push(utils.stripZeros(utils.arrayify(signature.r)));
|
||||
raw.push(utils.stripZeros(utils.arrayify(signature.s)));
|
||||
|
||||
return utils.RLP.encode(raw);
|
||||
}
|
||||
async sendTransaction(toAddress, amount, option = {gasFee : GAS_Fee}){
|
||||
/****************************************************************
|
||||
1 Ether = 1e18 wei
|
||||
1Gwei = 1e9 wei
|
||||
*GWei as the unit of gasPrice, minimum gasPrice is 1Gwei
|
||||
*unit of amount is ether,should be translate to wei
|
||||
****************************************************************/
|
||||
let nonce = await this.getTransactionCount();
|
||||
if(!nonce) nonce = '0x0'
|
||||
var gasPrice, gasLimit;
|
||||
if(!option.gasPrice || !option.gasLimit){
|
||||
//Normal Mode:use customized gasFee( ether ) to caculate gasPrice( wei ), gasLimit use default value
|
||||
gasLimit = GAS_LIMIT;
|
||||
gasPrice = String(option.gasFee * GAS_UNIT_WEI / gasLimit);
|
||||
}
|
||||
else{
|
||||
//Advance Mode:specified the gasLimit and gasPrice( gwei )
|
||||
gasLimit = option.gasLimit;
|
||||
gasPrice = String(GAS_UNIT_GWEI * option.gasPrice)
|
||||
}
|
||||
let transaction = {
|
||||
nonce: nonce,
|
||||
gasLimit: gasLimit,
|
||||
gasPrice: utils.bigNumberify(gasPrice),
|
||||
to: toAddress,
|
||||
|
||||
value: utils.parseEther(String(amount)),
|
||||
};
|
||||
try{
|
||||
let signedTransaction = this.signTransaction(transaction);
|
||||
let ethTxRes = (await axios.post(ETH_NODE,{
|
||||
"jsonrpc":"2.0",
|
||||
"method":"eth_sendRawTransaction",
|
||||
"params":[signedTransaction.toString('hex')],
|
||||
"id":6842
|
||||
})).data
|
||||
if(ethTxRes && ethTxRes.result)
|
||||
return ethTxRes
|
||||
return null
|
||||
}
|
||||
catch(err){
|
||||
return null
|
||||
}
|
||||
}
|
||||
encrypt(key){
|
||||
return ETH.encrypt(this, key)
|
||||
}
|
||||
}
|
||||
|
||||
class ERC20 extends ETH{
|
||||
constructor(privateKey, contractAddress){
|
||||
if(!contractAddress) throw new Error('Missing contractAddress')
|
||||
super(privateKey);
|
||||
Object.defineProperty(this, 'contractAddress',{
|
||||
enumerable:true,
|
||||
writable:false,
|
||||
value:contractAddress
|
||||
})
|
||||
}
|
||||
static async getDecimals(contractAddress){
|
||||
if(!contractAddress) throw new Error('Missing params')
|
||||
let queryAddress = '0x313ce567' + (contractAddress.split('x')[1]).padStart(64,'0')
|
||||
let params = [{"to":contractAddress, "data":queryAddress},"latest"]
|
||||
let queryData = {
|
||||
"jsonrpc":"2.0",
|
||||
"method":"eth_call",
|
||||
"params":params,
|
||||
"id":6842
|
||||
}
|
||||
return parseInt((await axios.post(ETH_NODE, queryData)).data.result)
|
||||
}
|
||||
static async getBalance(address, contractAddress){
|
||||
if(!address || !contractAddress) throw new Error('Missing params')
|
||||
let queryAddress = '0x70a08231' + (address.split('x')[1]).padStart(64,'0')
|
||||
let params = [{"to":contractAddress, "data":queryAddress},"latest"]
|
||||
let queryData = {
|
||||
"jsonrpc":"2.0",
|
||||
"method":"eth_call",
|
||||
"params":params,
|
||||
"id":6842
|
||||
}
|
||||
// return parseInt(erc20res.result)/Number('10'.padEnd(ERC20Table[obj.name].decimals+1,'0'))
|
||||
let res = (await axios.post(ETH_NODE, queryData)).data.result
|
||||
if(res == '0x') return 0
|
||||
return parseInt(res)
|
||||
}
|
||||
static async getActions(address, contractAddress){
|
||||
try{
|
||||
let res = (await eth.account.tokentx(address,contractAddress))
|
||||
if(res && res.result)
|
||||
return res.result
|
||||
}
|
||||
catch(err){
|
||||
return []
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
async getBalance(){
|
||||
return ERC20.getBalance(this.address, this.contractAddress)
|
||||
}
|
||||
async getActions(){
|
||||
return ERC20.getActions(this.address, this.contractAddress)
|
||||
}
|
||||
async getDecimals(){
|
||||
let decimals = await ERC20.getDecimals(this.contractAddress)
|
||||
if(decimals)
|
||||
Object.defineProperty(this, 'decimals', {
|
||||
enumerable:true,
|
||||
value:decimals,
|
||||
writable:false
|
||||
})
|
||||
else
|
||||
return 0 // any good idea?
|
||||
}
|
||||
async sendTransaction(toAddress, amount, option = {gasFee : GAS_Fee_ERC20}){
|
||||
/****************************************************************
|
||||
1 Ether = 1e18 wei
|
||||
1 Gwei = 1e9 wei
|
||||
*GWei as the unit of gasPrice, minimum gasPrice is 1Gwei
|
||||
minimum gaslimit for erc20transaction is 60000
|
||||
****************************************************************/
|
||||
var nonce = await this.getTransactionCount();
|
||||
var gasPrice, gasLimit, decimals, contractAddress = this.contractAddress;
|
||||
if(!nonce) nonce = '0x0'
|
||||
if(!option.gasPrice || !option.gasLimit){
|
||||
//Normal Mode:use customized gasFee( ether ) to caculate gasPrice( wei ), gasLimit use default value
|
||||
gasLimit = GAS_LIMIT_ERC20;
|
||||
gasPrice = String(option.gasFee * GAS_UNIT_WEI / gasLimit);
|
||||
}
|
||||
else{
|
||||
//Advance Mode:specified the gasLimit and gasPrice( gwei )
|
||||
gasLimit = option.gasLimit;
|
||||
gasPrice = String(GAS_UNIT_GWEI * option.gasPrice)
|
||||
}
|
||||
if(!option.decimals) decimals = await ERC20.getDecimals(contractAddress)
|
||||
let txBody = '0x' + 'a9059cbb' + toAddress.split('x')[1].padStart(64,'0')+ Number(amount*Math.pow(10,decimals)).toString(16).padStart(64,'0')
|
||||
let transaction = {
|
||||
nonce: nonce,
|
||||
gasLimit: gasLimit,
|
||||
gasPrice : utils.bigNumberify(gasPrice),
|
||||
to: contractAddress,
|
||||
value : 0,
|
||||
data : txBody
|
||||
};
|
||||
let signedTransaction = this.signTransaction(transaction);
|
||||
try{
|
||||
let erc20TxRes = (await axios.post(ETH_NODE, {
|
||||
"jsonrpc":"2.0",
|
||||
"method":"eth_sendRawTransaction",
|
||||
"params":[signedTransaction.toString('hex')],
|
||||
"id":6842
|
||||
})).data
|
||||
if(erc20TxRes && erc20TxRes.result)
|
||||
return erc20TxRes.result
|
||||
console.log(erc20TxRes)
|
||||
return null
|
||||
}
|
||||
catch(err){
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ETH, ERC20
|
||||
}
|
||||
|
16
index.js
Normal file
16
index.js
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict'
|
||||
|
||||
const TIC = require('./tic.js').TIC;
|
||||
const ETH = require('./eth.js').ETH;
|
||||
const ERC20 = require('./eth.js').ERC20;
|
||||
const BTC = require('./btc.js').BTC;
|
||||
const Account = require('./Account').Account;
|
||||
const Crypto = require('tic.common').Crypto;
|
||||
module.exports = {
|
||||
TIC,
|
||||
ETH,
|
||||
BTC,
|
||||
ERC20,
|
||||
Account,
|
||||
Crypto,
|
||||
}
|
14
netConfig.js
Normal file
14
netConfig.js
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
const TIC_NODE = 'https://bank.bittic.net:7285/api';
|
||||
const BTC_NODE = 'https://api.blockcypher.com/v1/btc/main';
|
||||
const BTC_NODE2 = 'https://blockchain.info'//https://blockchain.info/unspent?active=12HnmPpLomtPL53Q4s6xEqRB4wkMHi5GEZ
|
||||
const ETH_NODE = 'https://mainnet.infura.io/8284219b092f4cc69f3de29e532b1eb2';
|
||||
const ETH_NODE2 = 'https://api.myetherapi.com/eth';
|
||||
const ETH_TEST_NODE = 'https://ropsten.infura.io/8284219b092f4cc69f3de29e532b1eb2';
|
||||
|
||||
module.exports = {
|
||||
TIC_NODE,
|
||||
ETH_NODE,
|
||||
BTC_NODE,
|
||||
BTC_NODE2,
|
||||
}
|
24
package.json
Normal file
24
package.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "tic.common",
|
||||
"version": "1.2.1",
|
||||
"dependencies": {
|
||||
"aes-js": "^3.1.1",
|
||||
"axios": "^0.18.0",
|
||||
"bitcoinjs-lib": "^4.0.2",
|
||||
"elliptic": "^6.4.1",
|
||||
"etherscan-api": "^8.1.3",
|
||||
"js-sha3": "^0.8.0",
|
||||
"scrypt-js": "^2.0.3",
|
||||
"setimmediate": "^1.0.5",
|
||||
"tic.common": "git+https://git.faronear.org/tic/tic.common.git",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "blockchain tool for ticwallet",
|
||||
"keywords": [
|
||||
"tool",
|
||||
"blockchain",
|
||||
"tic"
|
||||
],
|
||||
"main": "index.js"
|
||||
}
|
136
tic.js
Normal file
136
tic.js
Normal file
@ -0,0 +1,136 @@
|
||||
'use strict'
|
||||
const axios = require('axios')
|
||||
const ticCommon = require('tic.common').Crypto
|
||||
const ticActTransfer = require('tic.common').ActTransfer
|
||||
|
||||
const TIC_TXFEE = 10;
|
||||
const TIC_NODE = require('./netConfig').TIC_NODE
|
||||
|
||||
class TIC {
|
||||
constructor(seckey,option={}){
|
||||
if(!seckey||!ticCommon.isSeckey(seckey)) throw "ERROR:Invalid Seckey"
|
||||
Object.defineProperties(this, {
|
||||
'seckey' : {
|
||||
value : seckey,
|
||||
enumerable : true,
|
||||
writable : false,
|
||||
},
|
||||
'pubkey' : {
|
||||
value : ticCommon.seckey2pubkey(seckey),
|
||||
enumerable : true,
|
||||
writable : false,
|
||||
},
|
||||
'address' : {
|
||||
value : ticCommon.pubkey2address(ticCommon.seckey2pubkey(seckey)),
|
||||
enumerable : true,
|
||||
writable : false
|
||||
}
|
||||
})
|
||||
Object.assign(this,{
|
||||
_url : option._url||TIC_NODE,
|
||||
_defaultFee : option.fee||TIC_TXFEE //fee cannot be zero
|
||||
})
|
||||
}
|
||||
get url(){return this._url}
|
||||
set url(newURL){this._url = newURL}
|
||||
get txfee(){return this._defaultFee}
|
||||
set txfee(fee){this._defaultFee = fee}
|
||||
|
||||
static generateNewAccount(){
|
||||
var secword = ticCommon.randomSecword()
|
||||
return Object.assign(new TIC(ticCommon.secword2keypair(secword).seckey),{secword:secword})
|
||||
}
|
||||
static fromMnemonic(secword){
|
||||
if(!secword||!ticCommon.isSecword(secword)) throw "ERROR:Invalid Secword"
|
||||
return new TIC(ticCommon.secword2keypair(secword).seckey)
|
||||
}
|
||||
static async getBalance(address){
|
||||
if(!address){ throw new Error('Address is required'); }
|
||||
return (await axios.post(TIC_NODE+'/Account/getBalance',{
|
||||
"Account" : {
|
||||
"address":address
|
||||
}
|
||||
})).data
|
||||
}
|
||||
static async getActions(address){
|
||||
if(!address){ throw new Error('Address is required'); }
|
||||
return (await axios.post(TIC_NODE+'/Action/getActionList',{
|
||||
"Action" : {
|
||||
"actorAddress" : address,
|
||||
"toAddress" : address
|
||||
},
|
||||
"config":{
|
||||
"logic":"OR"
|
||||
}
|
||||
})).data
|
||||
}
|
||||
static encrypt(data, key){
|
||||
if(!data || !key) throw new Error('Required Params Missing')
|
||||
return ticCommon.encrypt(data,key)
|
||||
}
|
||||
static decrypt(data, key){
|
||||
return ticCommon.decrypt(data, key, {format:"json"}) //return null for wrong key
|
||||
}
|
||||
|
||||
static isValidAddress(address){
|
||||
return ticCommon.isAddress(address)
|
||||
}
|
||||
|
||||
async sendTransaction(toAddress, amount, option = {gasFee : TIC_TXFEE}){
|
||||
if(!toAddress||!amount){throw new Error("ERROR:RequiredParamsMissing")} //amount cannot be zero
|
||||
let action = new ticActTransfer({
|
||||
amount: parseInt(amount),
|
||||
toAddress: toAddress,
|
||||
fee: option.gasFee
|
||||
})
|
||||
//对交易数据签名,packMe 内的参数是交易发起人的keypair
|
||||
action.packMe({
|
||||
seckey: this.seckey,
|
||||
pubkey: this.pubkey,
|
||||
address: this.address
|
||||
})
|
||||
let data = {
|
||||
Action:action
|
||||
}
|
||||
try{
|
||||
|
||||
let res = (await axios.post(this._url + '/Action/prepare',data)).data
|
||||
return res
|
||||
}catch(err){
|
||||
return null
|
||||
}
|
||||
}
|
||||
async getBalance(){
|
||||
return TIC.getBalance(this.address)
|
||||
}
|
||||
async getActions(){
|
||||
return TIC.getActions(this.address)
|
||||
}
|
||||
getSerializedTx(option){
|
||||
if(!option.toAddress||!option.amount){throw new Error("ERROR:RequiredParamsMissing")}
|
||||
let action=new ticActTransfer({
|
||||
amount: parseInt(option.amount),
|
||||
toAddress: option.toAddress,
|
||||
fee:option.fee||this._defaultFee
|
||||
})
|
||||
//sign for txBody use function packMe, which needs actor's keypair as parameter
|
||||
action.packMe({
|
||||
seckey: this.seckey,
|
||||
pubkey: this.pubkey,
|
||||
address: this.address
|
||||
})
|
||||
return action
|
||||
}
|
||||
//default key for sign&encrypt is account's seckey,other keys are optional.
|
||||
sign(message,key = this.seckey){
|
||||
return ticCommon.sign(message,key)
|
||||
}
|
||||
verify(message,signature){
|
||||
return ticCommon.sign(message,signature,this.seckey)
|
||||
}
|
||||
encrypt(key){
|
||||
return TIC.encrypt(this, key)
|
||||
}
|
||||
|
||||
}
|
||||
module.exports = {TIC}
|
35
util.js
Normal file
35
util.js
Normal file
@ -0,0 +1,35 @@
|
||||
'use strict'
|
||||
module.exports = (function() {
|
||||
var convert = require('./utils/convert');
|
||||
var hmac = require('./utils/hmac');
|
||||
var base64 = require('./utils/base64');
|
||||
return {
|
||||
defineProperty: require('./utils/properties').defineProperty,
|
||||
|
||||
arrayify: convert.arrayify,
|
||||
hexlify: convert.hexlify,
|
||||
stripZeros: convert.stripZeros,
|
||||
concat: convert.concat,
|
||||
padZeros: convert.padZeros,
|
||||
stripZeros: convert.stripZeros,
|
||||
base64: base64,
|
||||
|
||||
bigNumberify: require('./utils/bignumber').bigNumberify,
|
||||
|
||||
toUtf8Bytes: require('./utils/utf8').toUtf8Bytes,
|
||||
|
||||
getAddress: require('./utils/address').getAddress,
|
||||
|
||||
keccak256: require('./utils/keccak256'),
|
||||
|
||||
RLP: require('./utils/rlp'),
|
||||
|
||||
pbkdf2: require('./utils/pbkdf2.js'),
|
||||
|
||||
createSha512Hmac: hmac.createSha512Hmac,
|
||||
|
||||
// isMnemonic: isMnemonic,
|
||||
|
||||
parseEther:require('./utils/units').parseEther
|
||||
};
|
||||
})();
|
1018
utils/abi-coder.js
Normal file
1018
utils/abi-coder.js
Normal file
File diff suppressed because it is too large
Load Diff
124
utils/address.js
Normal file
124
utils/address.js
Normal 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
13
utils/base64.js
Normal 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
149
utils/bignumber.js
Normal 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
24
utils/browser-base64.js
Normal 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);
|
||||
}
|
||||
};
|
||||
|
||||
|
43
utils/browser-random-bytes.js
Normal file
43
utils/browser-random-bytes.js
Normal 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
20
utils/contract-address.js
Normal 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
224
utils/convert.js
Normal 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
1
utils/empty.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = undefined;
|
91
utils/errors.js
Normal file
91
utils/errors.js
Normal 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
259
utils/hdnode.js
Normal 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
24
utils/hmac.js
Normal 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
10
utils/id.js
Normal 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
75
utils/index.js
Normal 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
12
utils/keccak256.js
Normal 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
38
utils/namehash.js
Normal 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
51
utils/pbkdf2.js
Normal 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
22
utils/properties.js
Normal 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
8
utils/random-bytes.js
Normal 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
142
utils/rlp.js
Normal 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
449
utils/secret-storage.js
Normal 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
23
utils/sha2.js
Normal 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
104
utils/signing-key.js
Normal 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
97
utils/solidity.js
Normal 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
11
utils/throw-error.js
Normal 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
148
utils/units.js
Normal 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
113
utils/utf8.js
Normal 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
1
utils/words.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user