首次放到 git

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

60
Account.js Normal file
View 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
View 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
View 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
View 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 ethershould 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

124
utils/address.js Normal file
View File

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

13
utils/base64.js Normal file
View File

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

149
utils/bignumber.js Normal file
View File

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

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

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

View File

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

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

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

224
utils/convert.js Normal file
View File

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

1
utils/empty.js Normal file
View File

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

91
utils/errors.js Normal file
View File

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

259
utils/hdnode.js Normal file
View File

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

24
utils/hmac.js Normal file
View File

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

10
utils/id.js Normal file
View File

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

75
utils/index.js Normal file
View File

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

12
utils/keccak256.js Normal file
View File

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

38
utils/namehash.js Normal file
View File

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

51
utils/pbkdf2.js Normal file
View File

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

22
utils/properties.js Normal file
View File

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

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

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

142
utils/rlp.js Normal file
View File

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

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

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

23
utils/sha2.js Normal file
View File

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

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

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

97
utils/solidity.js Normal file
View File

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

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

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

148
utils/units.js Normal file
View File

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

113
utils/utf8.js Normal file
View File

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

1
utils/words.json Normal file

File diff suppressed because one or more lines are too long