首次放到 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