Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc2583ddd8 | ||
|
|
3be962f09d | ||
|
|
736b08e016 | ||
|
|
d8013cae37 | ||
|
|
b26b1b9c53 | ||
|
|
1c9c7b7a7d | ||
|
|
a9aaeefe24 | ||
|
|
8257e3f885 | ||
|
|
965c987761 | ||
|
|
142e5276fe | ||
|
|
351d1a2dad | ||
|
|
72424ea2d2 | ||
|
|
1e703bfdb4 | ||
|
|
dee0a4bf3e | ||
|
|
9c1d6051fb | ||
|
|
3c67736cbb | ||
|
|
c697a5bc81 | ||
|
|
0e9df5cb76 | ||
|
|
7a37dd6949 |
107
README.md
107
README.md
@@ -1,6 +1,8 @@
|
||||
ethers-wallet
|
||||
=============
|
||||
|
||||
[](https://badge.fury.io/js/ethers-wallet)
|
||||
|
||||
Complete Ethereum wallet implementation and utilities in JavaScript.
|
||||
|
||||
**Features:**
|
||||
@@ -15,6 +17,22 @@ Complete Ethereum wallet implementation and utilities in JavaScript.
|
||||
**NOTE: This is still very beta; please only use it on the testnet for now, or with VERY small amounts of ether on the livenet that you are willing to lose due to bugs.**
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
To use in a browser:
|
||||
|
||||
```html
|
||||
<script type="text/javascript" src="https://rawgit.com/ethers-io/ethers-wallet/master/dist/ethers-wallet.min.js"></script>
|
||||
```
|
||||
|
||||
To use in [node.js](https://nodejs.org/):
|
||||
|
||||
```
|
||||
npm install ethers-wallet
|
||||
```
|
||||
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
@@ -107,9 +125,9 @@ This API allows you to decrypt and encrypt the [Secret Storage](https://github.c
|
||||
|
||||
The Secret Storage JSON Wallet format uses an algorithm called *scrypt*, which is intentionally CPU-intensive, which ensures that an attacker would need to tie up considerable resources to attempt to brute-force guess your password. It aslo means it may take some time (10-30 seconds) to decrypt or encrypt a wallet. So, these API calls use a callback to provide progress feedback as well as the opportunity to cancel the process.
|
||||
|
||||
The callback should look like `function(error, result, progress)` where progress is a Number between 0 and 1 (inclusive) and if the function returns `true`, then the process will be cancelled, calling the callback once more with `callback(new Error('cancelled'))`.
|
||||
The callback should look like `function(progress)` where progress is a Number between 0 and 1 (inclusive) and if the function returns `true`, then the process will be cancelled, calling the callback once more with `callback(new Error('cancelled'))`.
|
||||
|
||||
#### Wallet.decrypt(json, password, callback);
|
||||
#### Wallet.decrypt(json, password[, callback]);
|
||||
|
||||
```javascript
|
||||
|
||||
@@ -118,31 +136,29 @@ var json = "... see the test-wallets directory for samples ...";
|
||||
|
||||
// Decrypt a Secret Storage JSON wallet
|
||||
var shouldCancelDecrypt = false;
|
||||
Wallet.decrypt(json, password, function(error, wallet, progress) {
|
||||
if (error) {
|
||||
if (error.message === 'invalid password') {
|
||||
// Wrong password
|
||||
|
||||
} else if (error.message === 'cancelled') {
|
||||
// The decryption was cancelled
|
||||
|
||||
}
|
||||
|
||||
} else if (wallet) {
|
||||
// The wallet was successfully decrypted
|
||||
|
||||
} else {
|
||||
// The wallet is still being decrypted
|
||||
console.log('The wallet is ' + parseInt(100 * progress) + '% decrypted');
|
||||
}
|
||||
function updateInterface(progress) {
|
||||
console.log('The wallet is ' + parseInt(100 * progress) + '% decrypted');
|
||||
|
||||
// Optionally return true to stop this decryption; this callback will get
|
||||
// called once more with callback(new Error("cancelled"))
|
||||
return shouldCancelDecrypt;
|
||||
}
|
||||
|
||||
Wallet.decrypt(json, password, updateInterface).then(function(wallet) {
|
||||
// The wallet was successfully decrypted
|
||||
|
||||
}, function(error) {
|
||||
if (error.message === 'invalid password') {
|
||||
// Wrong password
|
||||
|
||||
} else if (error.message === 'cancelled') {
|
||||
// The decryption was cancelled
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Wallet.prototype.encrypt(password[, options], callback);
|
||||
#### Wallet.prototype.encrypt(password[, options][, callback]);
|
||||
|
||||
```javascript
|
||||
|
||||
@@ -162,24 +178,22 @@ var options = {
|
||||
var wallet = new Wallet(privateKey);
|
||||
|
||||
var shouldCancelEncrypt = false;
|
||||
wallet.encrypt(password, options, function(error, json, progress) {
|
||||
if (error) {
|
||||
if (error.message === 'cancelled') {
|
||||
// Cancelled
|
||||
}
|
||||
|
||||
} else if (json) {
|
||||
// The wallet was successfully encrypted as a json string
|
||||
function updateInterface(progress) {
|
||||
console.log('The wallet is ' + parseInt(100 * progress) + '% encrypted');
|
||||
|
||||
} else {
|
||||
// The wallet is still being encrypted
|
||||
console.log('The wallet is ' + parseInt(100 * progress) + '% encrypted');
|
||||
|
||||
}
|
||||
|
||||
// Optionally return true to stop this decryption; this callback will get
|
||||
// Optionally return true to stop this encryption; this callback will get
|
||||
// called once more with callback(new Error("cancelled"))
|
||||
return shouldCancelEncrypt;
|
||||
}
|
||||
|
||||
wallet.encrypt(password, options, updateInterface).then(function(json) {
|
||||
// The wallet was successfully encrypted as a json string
|
||||
|
||||
}, function(error) {
|
||||
if (error.message === 'cancelled') {
|
||||
// Cancelled
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@@ -196,23 +210,22 @@ var email = new Wallet.utils.Buffer('github@ricmoo.com', 'utf8');
|
||||
var password = new Wallet.utils.Buffer('password', 'utf8');
|
||||
|
||||
var shouldCancelSummon = false;
|
||||
Wallet.summonBrainWallet(email, password, function(error, wallet, progress) {
|
||||
if (error) {
|
||||
if (error.message === 'cancelled') {
|
||||
// Cancelled
|
||||
}
|
||||
|
||||
} else if (wallet) {
|
||||
// The wallet was successfully generated
|
||||
|
||||
} else {
|
||||
// The wallet is still being generated
|
||||
console.log('The wallet is ' + parseInt(100 * progress) + '% encrypted');
|
||||
}
|
||||
function updateInterface(progress) {
|
||||
console.log('The wallet is ' + parseInt(100 * progress) + '% generated');
|
||||
|
||||
// Optionally return true to stop this generation; this callback will get
|
||||
// called once more with callback(new Error("cancelled"))
|
||||
return shouldCancelSummon;
|
||||
}
|
||||
|
||||
Wallet.summonBrainWallet(email, password, updateInterface).then(function(wallet) {
|
||||
// The wallet was successfully generated
|
||||
|
||||
}, function(error) {
|
||||
if (error.message === 'cancelled') {
|
||||
// Cancelled
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@@ -250,7 +263,7 @@ wallet.getTransactionCount().then(function(transactionCount) {
|
||||
})
|
||||
|
||||
// Send ether to another account or contract
|
||||
wallet.send(targetAddress, Wallet.parseEther(1.0)).then(function(txid) {
|
||||
wallet.send(targetAddress, Wallet.parseEther('1.0')).then(function(txid) {
|
||||
console.log(txid);
|
||||
})
|
||||
```
|
||||
@@ -342,7 +355,7 @@ To run the test suite,
|
||||
```
|
||||
/Users/ethers> npm test
|
||||
|
||||
> ethers-wallet@0.0.3 test /Users/ethers/ethers-wallet
|
||||
> ethers-wallet@0.0.9 test /Users/ethers/ethers-wallet
|
||||
> nodeunit test.js
|
||||
|
||||
Running test cases... (this can take a long time, please be patient)
|
||||
|
||||
953
dist/ethers-wallet.js
vendored
953
dist/ethers-wallet.js
vendored
File diff suppressed because it is too large
Load Diff
20
dist/ethers-wallet.min.js
vendored
20
dist/ethers-wallet.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -60,6 +60,23 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr />
|
||||
<h2>Raw Private Key</h2>
|
||||
<p>
|
||||
</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Private Key:</th>
|
||||
<td><input type="text" placeholder="(private key)" id="select-privatekey" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<div id="select-submit-privatekey" class="submit disable">Unlock JSON Wallet</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr />
|
||||
<h3>Disclaimer:</h3>
|
||||
<p>
|
||||
@@ -109,8 +126,8 @@
|
||||
<tr>
|
||||
<th>Network:</th>
|
||||
<td>
|
||||
<div class="option left selected" id="option-morden">Morden (testnet)</div>
|
||||
<div class="option right" id="option-homestead">Homestead (mainnet)</div>
|
||||
<div class="option left" id="option-morden">Morden (testnet)</div>
|
||||
<div class="option right selected" id="option-homestead">Homestead (mainnet)</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -298,6 +315,30 @@
|
||||
|
||||
})();
|
||||
|
||||
(function() {
|
||||
var inputPrivatekey = document.getElementById('select-privatekey');
|
||||
var submit = document.getElementById('select-submit-privatekey');
|
||||
|
||||
function check() {
|
||||
if (inputPrivatekey.value.match(/^(0x)?[0-9A-fa-f]{64}$/)) {
|
||||
submit.classList.remove('disable');
|
||||
} else {
|
||||
submit.classList.add('disable');
|
||||
}
|
||||
}
|
||||
inputPrivatekey.oninput = check;
|
||||
|
||||
setEnter(inputPrivatekey, submit);
|
||||
|
||||
submit.onclick = function() {
|
||||
if (submit.classList.contains('disable')) { return; }
|
||||
var privateKey = inputPrivatekey.value;
|
||||
if (privateKey.substring(0, 2) !== '0x') { privateKey = '0x' + privateKey; }
|
||||
showWallet(new Wallet(privateKey));
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
var activeWallet = null;
|
||||
|
||||
function showError(error) {
|
||||
@@ -433,8 +474,9 @@
|
||||
}
|
||||
|
||||
function showWallet(wallet) {
|
||||
var testnet = document.getElementById('option-morden').classList.contains('selected');
|
||||
activeWallet = wallet;
|
||||
activeWallet.provider = new Wallet.providers.EtherscanProvider({testnet: true});
|
||||
activeWallet.provider = new Wallet.providers.EtherscanProvider({testnet: testnet});
|
||||
|
||||
document.getElementById('screen-select').style.display = 'none';
|
||||
document.getElementById('screen-loading').style.display = 'none';
|
||||
|
||||
67
index.js
67
index.js
@@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
var rlp = require('rlp');
|
||||
var scrypt = require('scrypt-js');
|
||||
|
||||
var Contract = require('./lib/contract.js');
|
||||
@@ -9,6 +8,7 @@ var secretStorage = require('./lib/secret-storage.js');
|
||||
var Randomish = require('./lib/randomish.js');
|
||||
var SigningKey = require('./lib/signing-key.js');
|
||||
var Wallet = require('./lib/wallet.js');
|
||||
var units = require('./lib/units.js');
|
||||
|
||||
var utils = require('./lib/utils.js');
|
||||
var BN = utils.BN;
|
||||
@@ -23,22 +23,18 @@ utils.defineProperty(exportUtils, 'Buffer', Buffer);
|
||||
utils.defineProperty(exportUtils, 'sha3', utils.sha3);
|
||||
utils.defineProperty(exportUtils, 'sha256', utils.sha256);
|
||||
|
||||
// http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed
|
||||
utils.defineProperty(exportUtils, 'getContractAddress', function(transaction) {
|
||||
return SigningKey.getAddress('0x' + utils.sha3(rlp.encode([
|
||||
utils.hexOrBuffer(SigningKey.getAddress(transaction.from)),
|
||||
utils.hexOrBuffer(utils.hexlify(transaction.nonce, 'nonce'))
|
||||
])).slice(12).toString('hex'));
|
||||
});
|
||||
utils.defineProperty(exportUtils, 'getContractAddress', utils.getContractAddress);
|
||||
|
||||
module.exports = Wallet;
|
||||
|
||||
|
||||
utils.defineProperty(Wallet, 'etherSymbol', '\uD835\uDF63');
|
||||
|
||||
utils.defineProperty(Wallet, 'formatEther', units.formatEther);
|
||||
utils.defineProperty(Wallet, 'parseEther', units.parseEther);
|
||||
|
||||
//utils.defineProperty(Wallet, 'getAddress', SigningKey.getAddress);
|
||||
//utils.defineProperty(Wallet, 'getIcapAddress', SigningKey.getIcapAddress);
|
||||
utils.defineProperty(Wallet, 'getAddress', utils.getAddress);
|
||||
utils.defineProperty(Wallet, 'getIcapAddress', utils.getIcapAddress);
|
||||
|
||||
utils.defineProperty(Wallet, 'isCrowdsaleWallet', secretStorage.isCrowdsaleWallet);
|
||||
utils.defineProperty(Wallet, 'isValidWallet', secretStorage.isValidWallet);
|
||||
@@ -47,36 +43,47 @@ utils.defineProperty(Wallet, 'decryptCrowdsale', function(json, password) {
|
||||
return new Wallet(secretStorage.decryptCrowdsale(json, password));
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet, 'decrypt', function(json, password, callback) {
|
||||
if (typeof(callback) !== 'function') { throw new Error('invalid callback'); }
|
||||
utils.defineProperty(Wallet, 'decrypt', function(json, password, progressCallback) {
|
||||
if (progressCallback && typeof(progressCallback) !== 'function') {
|
||||
throw new Error('invalid callback');
|
||||
}
|
||||
|
||||
secretStorage.decrypt(json, password, function(error, signingKey, progress) {
|
||||
if (signingKey) {
|
||||
return callback(error, new Wallet(signingKey), progress);
|
||||
}
|
||||
return callback(error, signingKey, progress);
|
||||
return new Promise(function(resolve, reject) {
|
||||
secretStorage.decrypt(json, password, progressCallback).then(function(signingKey) {
|
||||
resolve(new Wallet(signingKey));
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet.prototype, 'encrypt', function(password, options, callback) {
|
||||
if (typeof(options) === 'function' && !callback) {
|
||||
callback = options;
|
||||
utils.defineProperty(Wallet.prototype, 'encrypt', function(password, options, progressCallback) {
|
||||
if (typeof(options) === 'function' && !progressCallback) {
|
||||
progressCallback = options;
|
||||
options = {};
|
||||
}
|
||||
if (typeof(callback) !== 'function') { throw new Error('invalid callback'); }
|
||||
if (progressCallback && typeof(progressCallback) !== 'function') {
|
||||
throw new Error('invalid callback');
|
||||
}
|
||||
|
||||
secretStorage.encrypt(this.privateKey, password, options, callback);
|
||||
return secretStorage.encrypt(this.privateKey, password, options, progressCallback);
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet, 'summonBrainWallet', function(username, password, callback) {
|
||||
if (typeof(callback) !== 'function') { throw new Error('invalid callback'); }
|
||||
utils.defineProperty(Wallet, 'summonBrainWallet', function(username, password, progressCallback) {
|
||||
if (progressCallback && typeof(progressCallback) !== 'function') {
|
||||
throw new Error('invalid callback');
|
||||
}
|
||||
|
||||
scrypt(password, username, (1 << 18), 8, 1, 32, function(error, progress, key) {
|
||||
if (key) {
|
||||
return callback(error, new Wallet(new Buffer(key)), 1);
|
||||
} else {
|
||||
return callback(error, null, progress);
|
||||
}
|
||||
return new Promise(function(resolve, reject) {
|
||||
scrypt(password, username, (1 << 18), 8, 1, 32, function(error, progress, key) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else if (key) {
|
||||
resolve(new Wallet(new Buffer(key)));
|
||||
} else if (progressCallback) {
|
||||
progressCallback(progress);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
var inherits = require('inherits');
|
||||
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
|
||||
|
||||
var SigningKey = require('./signing-key.js');
|
||||
var utils = require('./utils.js');
|
||||
|
||||
// The required methods a provider must support
|
||||
@@ -120,13 +119,17 @@ function postProcess(client, method, params, makeBN) {
|
||||
}
|
||||
|
||||
utils.defineProperty(SendAsyncProvider.prototype, 'getBalance', function(address, blockNumber) {
|
||||
address = SigningKey.getAddress(address);
|
||||
return postProcess(this.client, 'eth_getBalance', [address, validBlock(blockNumber)], true);
|
||||
return postProcess(this.client, 'eth_getBalance', [
|
||||
utils.getAddress(address),
|
||||
validBlock(blockNumber)
|
||||
], true);
|
||||
});
|
||||
|
||||
utils.defineProperty(SendAsyncProvider.prototype, 'getTransactionCount', function(address, blockNumber) {
|
||||
address = SigningKey.getAddress(address);
|
||||
return postProcess(this.client, 'eth_getTransactionCount', [address, validBlock(blockNumber)], false);
|
||||
return postProcess(this.client, 'eth_getTransactionCount', [
|
||||
utils.getAddress(address),
|
||||
validBlock(blockNumber)
|
||||
], false);
|
||||
});
|
||||
|
||||
utils.defineProperty(SendAsyncProvider.prototype, 'getGasPrice', function() {
|
||||
@@ -260,14 +263,14 @@ function EtherscanProvider(options) {
|
||||
utils.defineProperty(providers, 'EtherscanProvider', EtherscanProvider);
|
||||
|
||||
utils.defineProperty(EtherscanProvider.prototype, 'getBalance', function(address, blockNumber) {
|
||||
address = SigningKey.getAddress(address);
|
||||
address = utils.getAddress(address);
|
||||
blockNumber = validBlock(blockNumber);
|
||||
var query = ('module=account&action=balance&address=' + address + '&tag=' + blockNumber);
|
||||
return this._send(query, base10ToBN);
|
||||
});
|
||||
|
||||
utils.defineProperty(EtherscanProvider.prototype, 'getTransactionCount', function(address, blockNumber) {
|
||||
address = SigningKey.getAddress(address);
|
||||
address = utils.getAddress(address);
|
||||
blockNumber = validBlock(blockNumber);
|
||||
var query = ('module=proxy&action=eth_getTransactionCount&address=' + address + '&tag=' + blockNumber);
|
||||
return this._send(query, hexToNumber);
|
||||
@@ -285,7 +288,7 @@ utils.defineProperty(EtherscanProvider.prototype, 'sendTransaction', function(si
|
||||
});
|
||||
|
||||
utils.defineProperty(EtherscanProvider.prototype, 'call', function(transaction) {
|
||||
var address = SigningKey.getAddress(transaction.to);
|
||||
var address = utils.getAddress(transaction.to);
|
||||
var data = transaction.data;
|
||||
if (!utils.isHexString(data)) { throw new Error('invalid data'); }
|
||||
var query = ('module=proxy&action=eth_call&to=' + address + '&data=' + data);
|
||||
@@ -293,7 +296,7 @@ utils.defineProperty(EtherscanProvider.prototype, 'call', function(transaction)
|
||||
});
|
||||
|
||||
utils.defineProperty(EtherscanProvider.prototype, 'estimateGas', function(transaction) {
|
||||
var address = SigningKey.getAddress(transaction.to);
|
||||
var address = utils.getAddress(transaction.to);
|
||||
|
||||
var query = 'module=proxy&action=eth_estimateGas&to=' + address;
|
||||
if (transaction.gasPrice) {
|
||||
@@ -303,7 +306,7 @@ utils.defineProperty(EtherscanProvider.prototype, 'estimateGas', function(transa
|
||||
query += '&gas=' + utils.hexlify(transaction.gasLimit);
|
||||
}
|
||||
if (transaction.from) {
|
||||
query += '&from=' + SigningKey.getAddress(transaction.from);
|
||||
query += '&from=' + utils.getAddress(transaction.from);
|
||||
}
|
||||
if (transaction.data) {
|
||||
query += '&data=' + ensureHex(transaction.data);
|
||||
|
||||
@@ -58,10 +58,10 @@ function Randomish() {
|
||||
var aesCbc = new aes.ModeOfOperation.cbc(key, this.feedEntropy());
|
||||
var result = new Buffer(0);
|
||||
while (result.length < length) {
|
||||
result = result.concat([result, this.feedEntropy()]);
|
||||
result = Buffer.concat([result, this.feedEntropy()]);
|
||||
}
|
||||
|
||||
return result;
|
||||
return result.slice(0, length);
|
||||
});
|
||||
|
||||
this.feedEntropy();
|
||||
|
||||
@@ -82,7 +82,7 @@ utils.defineProperty(secretStorage, 'decryptCrowdsale', function(json, password)
|
||||
var data = JSON.parse(json);
|
||||
|
||||
// Ethereum Address
|
||||
var ethaddr = SigningKey.getAddress(searchPath(data, 'ethaddr'));
|
||||
var ethaddr = utils.getAddress(searchPath(data, 'ethaddr'));
|
||||
|
||||
// Encrypted Seed
|
||||
var encseed = new Buffer(searchPath(data, 'encseed'), 'hex');
|
||||
@@ -132,16 +132,11 @@ utils.defineProperty(secretStorage, 'decryptCrowdsale', function(json, password)
|
||||
});
|
||||
|
||||
|
||||
utils.defineProperty(secretStorage, 'decrypt', function(json, password, callback) {
|
||||
utils.defineProperty(secretStorage, 'decrypt', function(json, password, progressCallback) {
|
||||
if (!Buffer.isBuffer(password)) { throw new Error('password must be a buffer'); }
|
||||
|
||||
var data = JSON.parse(json);
|
||||
|
||||
var fail = function(reason) {
|
||||
console.log('fail', reason);
|
||||
setTimeout(function() { callback(new Error(reason)); }, 0);
|
||||
}
|
||||
|
||||
var decrypt = function(key, ciphertext) {
|
||||
var cipher = searchPath(data, 'crypto/cipher');
|
||||
if (cipher === 'aes-128-ctr') {
|
||||
@@ -160,69 +155,80 @@ utils.defineProperty(secretStorage, 'decrypt', function(json, password, callback
|
||||
return utils.sha3(Buffer.concat([derivedHalf, ciphertext]));
|
||||
}
|
||||
|
||||
var kdf = searchPath(data, 'crypto/kdf');
|
||||
if (kdf && kdf.toLowerCase() === 'scrypt') {
|
||||
var salt = new Buffer(searchPath(data, 'crypto/kdfparams/salt'), 'hex');
|
||||
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) {
|
||||
return fail('unsupported key-derivation function parameters');
|
||||
}
|
||||
|
||||
// Make sure N is a power of 2
|
||||
if ((N & (N - 1)) !== 0) {
|
||||
return fail('unsupported key-derivation function parameter value for N')
|
||||
}
|
||||
|
||||
var dkLen = searchPath(data, 'crypto/kdfparams/dklen');
|
||||
if (dkLen !== 32) {
|
||||
return fail('unsupported key-derivation derived-key length');
|
||||
}
|
||||
|
||||
scrypt(password, salt, N, r, p, dkLen, function(error, progress, key) {
|
||||
if (error) {
|
||||
callback(error, null, progress);
|
||||
|
||||
} else if (key) {
|
||||
key = new Buffer(key);
|
||||
|
||||
var ciphertext = new Buffer(searchPath(data, 'crypto/ciphertext'), 'hex');
|
||||
|
||||
var computedMAC = computeMAC(key.slice(16, 32), ciphertext).toString('hex').toLowerCase();
|
||||
if (computedMAC !== searchPath(data, 'crypto/mac').toLowerCase()) {
|
||||
return fail('invalid password');
|
||||
}
|
||||
|
||||
var privateKey = decrypt(key.slice(0, 16), ciphertext);
|
||||
|
||||
if (!privateKey) {
|
||||
return fail('unsupported cipher');
|
||||
}
|
||||
|
||||
var signingKey = new SigningKey(privateKey);
|
||||
if (signingKey.address !== SigningKey.getAddress(data.address)) {
|
||||
return fail('address mismatch');
|
||||
}
|
||||
|
||||
callback(null, signingKey, 1.0);
|
||||
|
||||
} else {
|
||||
return callback(null, null, progress);
|
||||
return new Promise(function(resolve, reject) {
|
||||
var kdf = searchPath(data, 'crypto/kdf');
|
||||
if (kdf && kdf.toLowerCase() === 'scrypt') {
|
||||
var salt = new Buffer(searchPath(data, 'crypto/kdfparams/salt'), 'hex');
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
// @TOOD: Support pbkdf2 kdf
|
||||
return fail('unsupported key-derivation function');
|
||||
}
|
||||
// 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 = searchPath(data, 'crypto/kdfparams/dklen');
|
||||
if (dkLen !== 32) {
|
||||
reject( new Error('unsupported key-derivation derived-key length'));
|
||||
return;
|
||||
}
|
||||
|
||||
scrypt(password, salt, N, r, p, dkLen, function(error, progress, key) {
|
||||
if (error) {
|
||||
error.progress = progress;
|
||||
reject(error);
|
||||
|
||||
} else if (key) {
|
||||
key = new Buffer(key);
|
||||
|
||||
var ciphertext = new Buffer(searchPath(data, 'crypto/ciphertext'), 'hex');
|
||||
|
||||
var computedMAC = computeMAC(key.slice(16, 32), ciphertext).toString('hex').toLowerCase();
|
||||
if (computedMAC !== searchPath(data, 'crypto/mac').toLowerCase()) {
|
||||
reject(new Error('invalid password'));
|
||||
return;
|
||||
}
|
||||
|
||||
var privateKey = decrypt(key.slice(0, 16), ciphertext);
|
||||
|
||||
if (!privateKey) {
|
||||
reject(new Error('unsupported cipher'));
|
||||
return;
|
||||
}
|
||||
|
||||
var signingKey = new SigningKey(privateKey);
|
||||
if (signingKey.address !== utils.getAddress(data.address)) {
|
||||
reject(new Error('address mismatch'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (progressCallback) { progressCallback(1); }
|
||||
resolve(signingKey);
|
||||
|
||||
} else if (progressCallback) {
|
||||
return progressCallback(progress);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
// @TOOD: Support pbkdf2 kdf
|
||||
reject(new Error('unsupported key-derivation function'));
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, options, callback) {
|
||||
utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, options, progressCallback) {
|
||||
|
||||
// the options are optional, so adjust the call as needed
|
||||
if (typeof(options) === 'function' && !callback) {
|
||||
callback = options;
|
||||
if (typeof(options) === 'function' && !progressCallback) {
|
||||
progressCallback = options;
|
||||
options = {};
|
||||
}
|
||||
if (!options) { options = {}; }
|
||||
@@ -267,70 +273,73 @@ utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, op
|
||||
if (options.scrypt.p) { p = options.scrypt.p; }
|
||||
}
|
||||
|
||||
// We take 64 bytes:
|
||||
// - 32 bytes As normal for the Web3 secret storage (derivedKey, macPrefix)
|
||||
// - 16 bytes The initialization vector
|
||||
// - 16 bytes The UUID random bytes
|
||||
scrypt(password, salt, N, r, p, 64, function(error, progress, key) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
|
||||
if (error) {
|
||||
return callback(error, null, progress);
|
||||
// We take 64 bytes:
|
||||
// - 32 bytes As normal for the Web3 secret storage (derivedKey, macPrefix)
|
||||
// - 16 bytes The initialization vector
|
||||
// - 16 bytes The UUID random bytes
|
||||
scrypt(password, salt, N, r, p, 64, function(error, progress, key) {
|
||||
if (error) {
|
||||
error.progress = progress;
|
||||
reject(error);
|
||||
|
||||
} else if (key) {
|
||||
// Convert the array-like to a Buffer
|
||||
key = new Buffer(key);
|
||||
} else if (key) {
|
||||
// Convert the array-like to a Buffer
|
||||
key = new Buffer(key);
|
||||
|
||||
// These will be used to encrypt the wallet (as per Web3 secret storage)
|
||||
var derivedKey = key.slice(0, 16);
|
||||
var macPrefix = key.slice(16, 32);
|
||||
// These will be used to encrypt the wallet (as per Web3 secret storage)
|
||||
var derivedKey = key.slice(0, 16);
|
||||
var macPrefix = key.slice(16, 32);
|
||||
|
||||
// Get the initialization vector
|
||||
if (!iv) { iv = key.slice(32, 48); }
|
||||
// Get the initialization vector
|
||||
if (!iv) { iv = key.slice(32, 48); }
|
||||
|
||||
// Get the UUID random data
|
||||
if (!uuidRandom) { uuidRandom = key.slice(48, 64); }
|
||||
// Get the UUID random data
|
||||
if (!uuidRandom) { uuidRandom = key.slice(48, 64); }
|
||||
|
||||
// Get the address for this private key
|
||||
var address = (new SigningKey(privateKey)).address;
|
||||
// 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 = new Buffer(aesCtr.encrypt(privateKey));
|
||||
// Encrypt the private key
|
||||
var counter = new aes.Counter(iv);
|
||||
var aesCtr = new aes.ModeOfOperation.ctr(derivedKey, counter);
|
||||
var ciphertext = new Buffer(aesCtr.encrypt(privateKey));
|
||||
|
||||
// Compute the message authentication code, used to check the password
|
||||
var mac = utils.sha3(Buffer.concat([macPrefix, ciphertext]))
|
||||
// Compute the message authentication code, used to check the password
|
||||
var mac = utils.sha3(Buffer.concat([macPrefix, ciphertext]))
|
||||
|
||||
// See: https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
|
||||
var data = {
|
||||
address: address,
|
||||
id: uuid.v4({random: uuidRandom}),
|
||||
version: 3,
|
||||
Crypto: {
|
||||
cipher: 'aes-128-ctr',
|
||||
cipherparams: {
|
||||
iv: iv.toString('hex')
|
||||
},
|
||||
ciphertext: ciphertext.toString('hex'),
|
||||
kdf: 'scrypt',
|
||||
kdfparams: {
|
||||
salt: salt.toString('hex'),
|
||||
n: N,
|
||||
dklen: 32,
|
||||
p: p,
|
||||
r: r
|
||||
},
|
||||
mac: mac.toString('hex')
|
||||
}
|
||||
};
|
||||
// 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: iv.toString('hex')
|
||||
},
|
||||
ciphertext: ciphertext.toString('hex'),
|
||||
kdf: 'scrypt',
|
||||
kdfparams: {
|
||||
salt: salt.toString('hex'),
|
||||
n: N,
|
||||
dklen: 32,
|
||||
p: p,
|
||||
r: r
|
||||
},
|
||||
mac: mac.toString('hex')
|
||||
}
|
||||
};
|
||||
|
||||
return callback(null, JSON.stringify(data), 1);
|
||||
if (progressCallback) { progressCallback(1); }
|
||||
resolve(JSON.stringify(data));
|
||||
|
||||
} else {
|
||||
return callback(null, null, progress);
|
||||
}
|
||||
} else if (progressCallback) {
|
||||
return progressCallback(progress);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
module.exports = secretStorage;
|
||||
|
||||
@@ -7,97 +7,6 @@ var utils = require('./utils.js');
|
||||
var secp256k1 = new (elliptic.ec)('secp256k1');
|
||||
|
||||
|
||||
function getChecksumAddress(address) {
|
||||
if (typeof(address) !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) {
|
||||
throw new Error('invalid address');
|
||||
}
|
||||
|
||||
address = address.substring(2).toLowerCase();
|
||||
var hashed = utils.sha3(address);
|
||||
|
||||
address = address.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('');
|
||||
}
|
||||
|
||||
function getAddress(address) {
|
||||
var result = null;
|
||||
|
||||
if (typeof(address) !== 'string') { throw new Error('invalid 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) {
|
||||
throw new Error('invalid address checksum');
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
throw new Error('invalid address icap checksum');
|
||||
}
|
||||
|
||||
result = (new utils.BN(address.substring(4), 36)).toString(16);
|
||||
while (result.length < 40) { result = '0' + result; }
|
||||
result = getChecksumAddress('0x' + result);
|
||||
|
||||
} else {
|
||||
throw new Error('invalid address');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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(Math.log10(Number.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 SigningKey(privateKey) {
|
||||
if (!(this instanceof SigningKey)) { throw new Error('missing new'); }
|
||||
@@ -112,7 +21,7 @@ function SigningKey(privateKey) {
|
||||
var keyPair = secp256k1.keyFromPrivate(privateKey);
|
||||
var publicKey = (new Buffer(keyPair.getPublic(false, 'hex'), 'hex')).slice(1);
|
||||
|
||||
var address = getAddress(utils.sha3(publicKey).slice(12).toString('hex'));
|
||||
var address = utils.getAddress(utils.sha3(publicKey).slice(12).toString('hex'));
|
||||
utils.defineProperty(this, 'address', address)
|
||||
|
||||
utils.defineProperty(this, 'signDigest', function(digest) {
|
||||
@@ -120,14 +29,10 @@ function SigningKey(privateKey) {
|
||||
});
|
||||
}
|
||||
|
||||
utils.defineProperty(SigningKey, 'getAddress', getAddress);
|
||||
|
||||
utils.defineProperty(SigningKey, 'getIcapAddress', function(address) {
|
||||
address = getAddress(address).substring(2);
|
||||
var base36 = (new utils.BN(address, 16)).toString(36).toUpperCase();
|
||||
while (base36.length < 30) { base36 = '0' + base36; }
|
||||
return 'XE' + ibanChecksum('XE00' + base36) + base36;
|
||||
utils.defineProperty(SigningKey, 'recover', function(digest, r, s, recoveryParam) {
|
||||
var publicKey = secp256k1.recoverPubKey(digest, {r: r, s: s}, recoveryParam);
|
||||
publicKey = (new Buffer(publicKey.encode('hex', false), 'hex')).slice(1);
|
||||
return utils.getAddress(utils.sha3(publicKey).slice(12).toString('hex'));
|
||||
});
|
||||
|
||||
|
||||
module.exports = SigningKey;
|
||||
|
||||
78
lib/units.js
Normal file
78
lib/units.js
Normal file
@@ -0,0 +1,78 @@
|
||||
var utils = require('./utils.js');
|
||||
|
||||
var zero = new utils.BN(0);
|
||||
var negative1 = new utils.BN(-1);
|
||||
var tenPower18 = new utils.BN('1000000000000000000');
|
||||
|
||||
function formatEther(wei, options) {
|
||||
|
||||
if (typeof(wei) === 'number') {
|
||||
// @TODO: Warn if truncation will occur?
|
||||
wei = new utils.BN(wei);
|
||||
} else if (utils.isHexString(wei)) {
|
||||
wei = new utils.BN(wei.substring(2), 16);
|
||||
}
|
||||
|
||||
if (!options) { options = {}; }
|
||||
|
||||
if (!(wei instanceof utils.BN)) { throw new Error('invalid wei'); }
|
||||
|
||||
var negative = wei.lt(zero);
|
||||
if (negative) { wei = wei.mul(negative1); }
|
||||
|
||||
var fraction = wei.mod(tenPower18).toString(10);
|
||||
while (fraction.length < 18) { fraction = '0' + fraction; }
|
||||
|
||||
if (!options.pad) {
|
||||
fraction = fraction.match(/^([0-9]*[1-9]|0)(0*)/)[1];
|
||||
}
|
||||
|
||||
var whole = wei.div(tenPower18).toString(10);
|
||||
|
||||
if (options.commify) {
|
||||
whole = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",")
|
||||
}
|
||||
|
||||
var value = whole + '.' + fraction;
|
||||
|
||||
if (negative) { value = '-' + value; }
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseEther(ether) {
|
||||
if (typeof(ether) !== 'string' || !ether.match(/^-?[0-9.]+$/)) {
|
||||
throw new Error('invalid value');
|
||||
}
|
||||
|
||||
// Is it negative?
|
||||
var negative = (ether.substring(0, 1) === '-');
|
||||
if (negative) { ether = ether.substring(1); }
|
||||
|
||||
if (ether === '.') { throw new Error('invalid value'); }
|
||||
|
||||
// Split it into a whole and fractional part
|
||||
var comps = ether.split('.');
|
||||
if (comps.length > 2) { throw new Error('too many decimal points'); }
|
||||
|
||||
var whole = comps[0], fraction = comps[1];
|
||||
if (!whole) { whole = '0'; }
|
||||
if (!fraction) { fraction = '0'; }
|
||||
if (fraction.length > 18) { throw new Error('too many decimal places'); }
|
||||
|
||||
while (fraction.length < 18) { fraction += '0'; }
|
||||
|
||||
whole = new utils.BN(whole);
|
||||
fraction = new utils.BN(fraction);
|
||||
|
||||
var wei = (whole.mul(tenPower18)).add(fraction);
|
||||
|
||||
if (negative) { wei = wei.mul(negative1); }
|
||||
|
||||
return wei;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatEther: formatEther,
|
||||
parseEther: parseEther,
|
||||
}
|
||||
114
lib/utils.js
114
lib/utils.js
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var rlp = require('rlp');
|
||||
|
||||
var BN = require('../node_modules/elliptic/node_modules/bn.js/lib/bn.js');
|
||||
var hash = require('../node_modules/elliptic/node_modules/hash.js/lib/hash.js');
|
||||
|
||||
@@ -213,6 +215,113 @@ function defineProperty(object, name, value) {
|
||||
});
|
||||
}
|
||||
|
||||
function getChecksumAddress(address) {
|
||||
if (typeof(address) !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) {
|
||||
throw new Error('invalid address');
|
||||
}
|
||||
|
||||
address = address.substring(2).toLowerCase();
|
||||
var hashed = sha3(address);
|
||||
|
||||
address = address.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('');
|
||||
}
|
||||
|
||||
function getAddress(address) {
|
||||
var result = null;
|
||||
|
||||
if (typeof(address) !== 'string') { throw new Error('invalid 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) {
|
||||
throw new Error('invalid address checksum');
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
throw new Error('invalid address icap checksum');
|
||||
}
|
||||
|
||||
result = (new BN(address.substring(4), 36)).toString(16);
|
||||
while (result.length < 40) { result = '0' + result; }
|
||||
result = getChecksumAddress('0x' + result);
|
||||
|
||||
} else {
|
||||
throw new Error('invalid address');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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(Math.log10(Number.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 getIcapAddress(address) {
|
||||
address = getAddress(address).substring(2);
|
||||
var base36 = (new BN(address, 16)).toString(36).toUpperCase();
|
||||
while (base36.length < 30) { base36 = '0' + base36; }
|
||||
return 'XE' + ibanChecksum('XE00' + base36) + base36;
|
||||
}
|
||||
|
||||
// http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed
|
||||
function getContractAddress(transaction) {
|
||||
return getAddress('0x' + sha3(rlp.encode([
|
||||
hexOrBuffer(getAddress(transaction.from)),
|
||||
hexOrBuffer(hexlify(transaction.nonce, 'nonce'))
|
||||
])).slice(12).toString('hex'));
|
||||
}
|
||||
|
||||
function cloneObject(object) {
|
||||
var clone = {};
|
||||
for (var key in object) { clone[key] = object[key]; }
|
||||
@@ -275,6 +384,11 @@ module.exports = {
|
||||
|
||||
defineProperty: defineProperty,
|
||||
|
||||
getAddress: getAddress,
|
||||
getIcapAddress: getIcapAddress,
|
||||
|
||||
getContractAddress: getContractAddress,
|
||||
|
||||
cloneObject: cloneObject,
|
||||
|
||||
bnToBuffer: bnToBuffer,
|
||||
|
||||
129
lib/wallet.js
129
lib/wallet.js
@@ -76,7 +76,7 @@ function Wallet(privateKey, provider) {
|
||||
value = utils.hexOrBuffer(utils.hexlify(value), fieldInfo.name);
|
||||
|
||||
// Fixed-width field
|
||||
if (fieldInfo.length && value.length !== fieldInfo.length) {
|
||||
if (fieldInfo.length && value.length !== fieldInfo.length && value.length > 0) {
|
||||
var error = new Error('invalid ' + fieldInfo.name);
|
||||
error.reason = 'wrong length';
|
||||
error.value = value;
|
||||
@@ -111,6 +111,58 @@ function Wallet(privateKey, provider) {
|
||||
});
|
||||
}
|
||||
|
||||
utils.defineProperty(Wallet, 'parseTransaction', function(rawTransaction) {
|
||||
rawTransaction = utils.hexOrBuffer(rawTransaction, 'rawTransaction');
|
||||
var signedTransaction = rlp.decode(rawTransaction);
|
||||
|
||||
var raw = [];
|
||||
|
||||
var transaction = {};
|
||||
transactionFields.forEach(function(fieldInfo, index) {
|
||||
transaction[fieldInfo.name] = signedTransaction[index];
|
||||
raw.push(signedTransaction[index]);
|
||||
});
|
||||
|
||||
if (transaction.to) {
|
||||
if (transaction.to.length === 0) {
|
||||
delete transaction.to;
|
||||
} else {
|
||||
transaction.to = utils.getAddress('0x' + transaction.to.toString('hex'));
|
||||
}
|
||||
}
|
||||
|
||||
['gasPrice', 'gasLimit', 'nonce', 'value'].forEach(function(name) {
|
||||
if (!transaction[name]) { return; }
|
||||
if (transaction[name].length === 0) {
|
||||
transaction[name] = new utils.BN(0);
|
||||
} else {
|
||||
transaction[name] = new utils.BN(transaction[name].toString('hex'), 16);
|
||||
}
|
||||
});
|
||||
|
||||
/* @TODO: Maybe? In the future, all nonces stored as numbers? (obviously, major version change)
|
||||
if (transaction.nonce) {
|
||||
transaction.nonce = transaction.nonce.toNumber()
|
||||
}
|
||||
*/
|
||||
|
||||
if (signedTransaction.length > 6 && signedTransaction[6].length === 1 &&
|
||||
signedTransaction[7].length >= 1 && signedTransaction[7].length <= 32 &&
|
||||
signedTransaction[8].length >= 1 && signedTransaction[7].length <= 32) {
|
||||
|
||||
transaction.v = signedTransaction[6][0];
|
||||
transaction.r = signedTransaction[7];
|
||||
transaction.s = signedTransaction[8];
|
||||
|
||||
var digest = utils.sha3(rlp.encode(raw));
|
||||
try {
|
||||
transaction.from = SigningKey.recover(digest, transaction.r, transaction.s, transaction.v - 27);
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
return transaction;
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet.prototype, 'getBalance', function(blockNumber) {
|
||||
var provider = this._provider;
|
||||
|
||||
@@ -205,7 +257,7 @@ utils.defineProperty(Wallet.prototype, 'sendTransaction', function(transaction)
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet.prototype, 'send', function(address, amountWei, options) {
|
||||
address = SigningKey.getAddress(address);
|
||||
address = utils.getAddress(address);
|
||||
if (utils.BN.isBN(amountWei)) {
|
||||
amountWei = '0x' + utils.bnToBuffer(amountWei).toString('hex');
|
||||
}
|
||||
@@ -226,79 +278,6 @@ utils.defineProperty(Wallet.prototype, 'getContract', function(address, abi) {
|
||||
return new Contract(this, address, new Contract.Interface(abi));
|
||||
});
|
||||
|
||||
|
||||
utils.defineProperty(Wallet, 'getAddress', SigningKey.getAddress);
|
||||
utils.defineProperty(Wallet, 'getIcapAddress', SigningKey.getIcapAddress);
|
||||
|
||||
utils.defineProperty(Wallet, '_Contract', Contract);
|
||||
|
||||
var zero = new utils.BN(0);
|
||||
var negative1 = new utils.BN(-1);
|
||||
var tenPower18 = new utils.BN('1000000000000000000');
|
||||
utils.defineProperty(Wallet, 'formatEther', function(wei, options) {
|
||||
if (typeof(wei) === 'number') {
|
||||
// @TODO: Warn if truncation will occur?
|
||||
wei = new utils.BN(wei);
|
||||
} else if (utils.isHexString(wei)) {
|
||||
wei = new utils.BN(wei.substring(2));
|
||||
}
|
||||
if (!options) { options = {}; }
|
||||
|
||||
if (!(wei instanceof utils.BN)) { throw new Error('invalid wei'); }
|
||||
|
||||
var negative = wei.lt(zero);
|
||||
if (negative) { wei = wei.mul(negative1); }
|
||||
|
||||
var fraction = wei.mod(tenPower18).toString(10);
|
||||
while (fraction.length < 18) { fraction = '0' + fraction; }
|
||||
|
||||
if (!options.pad) {
|
||||
fraction = fraction.match(/^([0-9]*[1-9]|0)(0*)/)[1];
|
||||
}
|
||||
|
||||
var whole = wei.div(tenPower18).toString(10);
|
||||
|
||||
if (options.commify) {
|
||||
whole = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",")
|
||||
}
|
||||
|
||||
var value = whole + '.' + fraction;
|
||||
|
||||
if (negative) { value = '-' + value; }
|
||||
|
||||
return value;
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet, 'parseEther', function(ether) {
|
||||
if (typeof(ether) !== 'string' || !ether.match(/^-?[0-9.]+$/)) {
|
||||
throw new Error('invalid value');
|
||||
}
|
||||
|
||||
// Is it negative?
|
||||
var negative = (ether.substring(0, 1) === '-');
|
||||
if (negative) { ether = ether.substring(1); }
|
||||
|
||||
if (ether === '.') { throw new Error('invalid value'); }
|
||||
|
||||
// Split it into a whole and fractional part
|
||||
var comps = ether.split('.');
|
||||
if (comps.length > 2) { throw new Error('too many decimal points'); }
|
||||
|
||||
var whole = comps[0], fraction = comps[1];
|
||||
if (!whole) { whole = '0'; }
|
||||
if (!fraction) { fraction = '0'; }
|
||||
if (fraction.length > 18) { throw new Error('too many decimal places'); }
|
||||
|
||||
while (fraction.length < 18) { fraction += '0'; }
|
||||
|
||||
whole = new utils.BN(whole);
|
||||
fraction = new utils.BN(fraction);
|
||||
|
||||
var wei = (whole.mul(tenPower18)).add(fraction);
|
||||
|
||||
if (negative) { wei = wei.mul(negative1); }
|
||||
|
||||
return wei;
|
||||
});
|
||||
|
||||
module.exports = Wallet;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ethers-wallet",
|
||||
"version": "0.0.9",
|
||||
"version": "1.0.5",
|
||||
"description": "Ethereum wallet library.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -14,7 +14,7 @@
|
||||
"pbkdf2": "3.0.4",
|
||||
"rlp": "2.0.0",
|
||||
"setimmediate": "1.0.4",
|
||||
"scrypt-js": "2.0.2",
|
||||
"scrypt-js": "2.0.3",
|
||||
"uuid": "2.0.1",
|
||||
"xmlhttprequest": "1.8.0"
|
||||
},
|
||||
@@ -40,5 +40,9 @@
|
||||
"wallet"
|
||||
],
|
||||
"author": "Richard Moore <me@ricmoo.com>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/ethers-io/ethers-wallet.git"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"address":"0x17c5185167401eD00cF5F5b2fc97D9BBfDb7D025","id":"01234567-8901-4345-a789-012345678901","version":3,"Crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"deadbeef1deadbeef2deadbeef301234"},"ciphertext":"cea502df4b9405fa2b6d8d19ec4e2953c5ce08e0e01d4c0292512ce62baef8f3","kdf":"scrypt","kdfparams":{"salt":"abcd1abcd2abcd3abcd4abcd5abcd6ef","n":1024,"dklen":32,"p":2,"r":4},"mac":"6e0a8e2409261d464e35a251f4b1fddd6ad0f7045a18d3957c127387f1c0de72"}}
|
||||
{"address":"17c5185167401ed00cf5f5b2fc97d9bbfdb7d025","id":"01234567-8901-4345-a789-012345678901","version":3,"Crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"deadbeef1deadbeef2deadbeef301234"},"ciphertext":"cea502df4b9405fa2b6d8d19ec4e2953c5ce08e0e01d4c0292512ce62baef8f3","kdf":"scrypt","kdfparams":{"salt":"abcd1abcd2abcd3abcd4abcd5abcd6ef","n":1024,"dklen":32,"p":2,"r":4},"mac":"6e0a8e2409261d464e35a251f4b1fddd6ad0f7045a18d3957c127387f1c0de72"}}
|
||||
|
||||
@@ -4,13 +4,12 @@ var Wallet = require('../index.js');
|
||||
module.exports = function(test) {
|
||||
var username = new Wallet.utils.Buffer('ricmoo', 'utf8');
|
||||
var password = new Wallet.utils.Buffer('password', 'utf8');
|
||||
Wallet.summonBrainWallet(username, password, function(error, wallet, progress) {
|
||||
if (error) {
|
||||
test.ok(false, 'Failed to generarte brain wallet');
|
||||
} else if (wallet) {
|
||||
test.equal(wallet.address, '0xbed9d2E41BdD066f702C4bDB86eB3A3740101acC', 'wrong wallet generated');
|
||||
test.done();
|
||||
}
|
||||
Wallet.summonBrainWallet(username, password).then(function(wallet) {
|
||||
test.equal(wallet.address, '0xbed9d2E41BdD066f702C4bDB86eB3A3740101acC', 'wrong wallet generated');
|
||||
test.done();
|
||||
}, function(error) {
|
||||
test.ok(false, 'Failed to generarte brain wallet');
|
||||
test.done();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
'use strict';
|
||||
var Wallet = require('../index.js');
|
||||
|
||||
// @TODO: Add testcases where format receives hexidecimal string
|
||||
|
||||
var BN = Wallet.utils.BN;
|
||||
|
||||
module.exports = function(test) {
|
||||
|
||||
@@ -3,8 +3,29 @@ var Wallet = require('../index.js');
|
||||
|
||||
var fs = require('fs');
|
||||
|
||||
|
||||
module.exports = function(test) {
|
||||
|
||||
function equals(a, b) {
|
||||
if (typeof(a) !== typeof(b)) { return false; }
|
||||
if (Array.isArray(a)) {
|
||||
if (!Array.isArray(b)) { return false; }
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) { return false; }
|
||||
}
|
||||
} else if (typeof(a) === 'object') {
|
||||
if (!equals(Object.keys(a), Object.keys(b))) {
|
||||
return false;
|
||||
}
|
||||
for (var key in a) {
|
||||
if (!equals(a[key], b[key])) { return false; }
|
||||
}
|
||||
} else {
|
||||
return (a === b);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var crowdsale = [
|
||||
{
|
||||
address: '0x2e326fA404Fc3661de4F4361776ed9bBABDC26E3',
|
||||
@@ -39,9 +60,6 @@ module.exports = function(test) {
|
||||
},
|
||||
];
|
||||
|
||||
test.expect((crowdsale.length * 4) + (geth.length * 4) + 2);
|
||||
|
||||
|
||||
// Test crowdsale private key decryption
|
||||
crowdsale.forEach(function(testcase) {
|
||||
|
||||
@@ -55,63 +73,61 @@ module.exports = function(test) {
|
||||
test.equal(wallet.address, testcase.address, 'wrong address');
|
||||
});
|
||||
|
||||
// Private keys are asynchronous, so we do this to trigger the done
|
||||
// only after all testcases have returned
|
||||
var expecting = geth.length + 1;
|
||||
function checkAsync() {
|
||||
expecting--;
|
||||
if (expecting === 0) { test.done(); }
|
||||
}
|
||||
var async = [];
|
||||
|
||||
geth.forEach(function(testcase) {
|
||||
// Check wallet type detection
|
||||
test.ok(Wallet.isValidWallet(testcase.data), 'wrong wallet type detected');
|
||||
test.ok(!Wallet.isCrowdsaleWallet(testcase.data), 'wrong wallet type detected');
|
||||
|
||||
// Test private key decryption
|
||||
var password = new Buffer(testcase.password, 'utf8');
|
||||
Wallet.decrypt(testcase.data, password, function(error, wallet, progress) {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
test.ok(false, 'callback returned error - ' + error.message);
|
||||
checkAsync();
|
||||
async.push(new Promise(function(resolve, reject) {
|
||||
|
||||
} else if (wallet) {
|
||||
// Test private key decryption
|
||||
var password = new Buffer(testcase.password, 'utf8');
|
||||
Wallet.decrypt(testcase.data, password).then(function(wallet) {
|
||||
test.equals(wallet.privateKey, testcase.privateKey, 'wrong private key')
|
||||
test.equals(wallet.address, testcase.address, 'wrong address');
|
||||
checkAsync();
|
||||
}
|
||||
});
|
||||
resolve();
|
||||
}, function(error) {
|
||||
console.log(error);
|
||||
test.ok(false, 'callback returned error - ' + error.message);
|
||||
reject(error);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
var privateKey = new Buffer(32);
|
||||
privateKey.fill(0x42);
|
||||
|
||||
var password = new Buffer("foo", 'utf8');
|
||||
|
||||
(new Wallet(privateKey)).encrypt(password, {
|
||||
scrypt: { N: (1 << 10), r: 4, p: 2 },
|
||||
iv: '0xdeadbeef1deadbeef2deadbeef301234',
|
||||
salt: '0xabcd1abcd2abcd3abcd4abcd5abcd6ef',
|
||||
uuid: '0x01234567890123456789012345678901',
|
||||
}, function(error, json, progress) {
|
||||
if (error) {
|
||||
test.ok(false, 'callback returned error - ' + error.message);
|
||||
checkAsync();
|
||||
|
||||
} else if (json) {
|
||||
async.push(new Promise(function(resolve, reject) {
|
||||
(new Wallet(privateKey)).encrypt(password, {
|
||||
scrypt: { N: (1 << 10), r: 4, p: 2 },
|
||||
iv: '0xdeadbeef1deadbeef2deadbeef301234',
|
||||
salt: '0xabcd1abcd2abcd3abcd4abcd5abcd6ef',
|
||||
uuid: '0x01234567890123456789012345678901',
|
||||
}).then(function(json) {
|
||||
var jsonWallet = fs.readFileSync('./test-wallets/wallet-test-life.json').toString();
|
||||
test.equal(json, jsonWallet, 'failed to encrypt wallet');
|
||||
Wallet.decrypt(json, password, function(error, wallet, progress) {
|
||||
if (error) {
|
||||
test.ok(false, 'callback returned error - ' + error.message);
|
||||
checkAsync();
|
||||
} else if (wallet) {
|
||||
test.equal(wallet.privateKey, '0x' + privateKey.toString('hex'), 'decryption failed');
|
||||
checkAsync();
|
||||
}
|
||||
test.ok(equals(JSON.parse(json), JSON.parse(jsonWallet)), 'failed to encrypt wallet')
|
||||
//test.equal(json, jsonWallet, 'failed to encrypt wallet');
|
||||
Wallet.decrypt(json, password).then(function(wallet) {
|
||||
test.equal(wallet.privateKey, '0x' + privateKey.toString('hex'), 'decryption failed');
|
||||
resolve();
|
||||
}, function(error) {
|
||||
test.ok(false, 'callback returned error - ' + error.message);
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
}, function(error) {
|
||||
test.ok(false, 'callback returned error - ' + error.message);
|
||||
reject(error);
|
||||
});
|
||||
}));
|
||||
|
||||
Promise.all(async).then(function(results) {
|
||||
test.done();
|
||||
}, function(error) {
|
||||
console.log(error);
|
||||
test.done();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,13 @@ module.exports = function(test) {
|
||||
rawTransaction.sign(privateKey);
|
||||
var ethereumLib = '0x' + rawTransaction.serialize().toString('hex');
|
||||
|
||||
var ethers = (new Wallet(privateKey)).sign(transaction);
|
||||
var wallet = new Wallet(privateKey);
|
||||
var ethers = wallet.sign(transaction);
|
||||
|
||||
test.equal(ethers, ethereumLib, 'invalid transaction');
|
||||
|
||||
// @TODO: More testing on parsed transaction.
|
||||
test.equal(wallet.address, Wallet.parseTransaction(ethers).from, 'invalid parseTransaction');
|
||||
}
|
||||
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
@@ -75,6 +79,19 @@ module.exports = function(test) {
|
||||
s: "0x29ae9893dac4f9afb1af743e25fbb6a63f7879a61437203cb48c997b0fcefc3a"
|
||||
});
|
||||
|
||||
// Test all possible blank fields
|
||||
var privateKey = new Buffer('0123456789012345678901234567890123456789012345678901234567890123', 'hex');
|
||||
for (var i = 0; i < 64; i++) {
|
||||
var transaction = {};
|
||||
if (i & (1 << 0)) { transaction.nonce = '0x02'; }
|
||||
if (i & (1 << 1)) { transaction.gasPrice = '0x03'; }
|
||||
if (i & (1 << 2)) { transaction.gasLimit = '0x04'; }
|
||||
if (i & (1 << 3)) { transaction.to = '0x0123456789012345678901234567890123456789'; }
|
||||
if (i & (1 << 4)) { transaction.value = '0x05'; }
|
||||
if (i & (1 << 5)) { transaction.data = '0x06'; }
|
||||
testTransaction(privateKey, transaction);
|
||||
}
|
||||
|
||||
test.done();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user