Compare commits

..

19 Commits

Author SHA1 Message Date
ricmoo
dc2583ddd8 Updating dist. 2016-10-05 00:12:15 +08:00
ricmoo
3be962f09d Made secret-storage more closely resemble geth. 2016-10-05 00:06:05 +08:00
ricmoo
736b08e016 Updated parseTransaction to format parameters more meaningfully. 2016-09-16 14:46:19 +08:00
ricmoo
d8013cae37 Added transaction parsing with address recovery. 2016-09-16 14:08:36 +08:00
ricmoo
b26b1b9c53 Moved getContractAddress to utils. 2016-08-23 22:06:26 -04:00
ricmoo
1c9c7b7a7d Refactored some internal libraries. 2016-08-23 20:24:18 -04:00
ricmoo
a9aaeefe24 Moved address functions to utils (things without need ofr signing do not need all the extra requires). 2016-08-23 20:12:12 -04:00
ricmoo
8257e3f885 Moved ether parsing/formatting into its own library. 2016-08-23 19:35:19 -04:00
ricmoo
965c987761 Updated docs for promise-based secret storage. 2016-08-15 23:27:10 -04:00
ricmoo
142e5276fe Moved encrypt, decrypt and brain wallet to use promises (which greatly improves nested progress callback, approx 4x performance). 2016-08-15 23:04:02 -04:00
ricmoo
351d1a2dad Added github repo to package.json. 2016-08-10 14:30:33 -04:00
ricmoo
72424ea2d2 Fixed typo in wallet demo. 2016-08-08 17:12:27 -04:00
ricmoo
1e703bfdb4 Version Bump (patch). 2016-08-08 14:34:55 -04:00
ricmoo
dee0a4bf3e Added raw private key and made homestead default network. 2016-08-08 00:18:01 -04:00
ricmoo
9c1d6051fb Fixed empty transaction field bug. 2016-08-07 15:18:42 -04:00
ricmoo
3c67736cbb Fixed randomish bug. 2016-08-07 15:17:53 -04:00
ricmoo
c697a5bc81 Version bump (patch). 2016-08-05 16:15:50 -04:00
ricmoo
0e9df5cb76 Fixed bug when passing in hex string to formatEther. 2016-08-05 16:14:09 -04:00
ricmoo
7a37dd6949 Added installing section and npm badge to docs. 2016-08-05 03:53:29 -04:00
18 changed files with 1156 additions and 879 deletions

107
README.md
View File

@@ -1,6 +1,8 @@
ethers-wallet
=============
[![npm version](https://badge.fury.io/js/ethers-wallet.svg)](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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -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';

View File

@@ -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);
}
});
});
});

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;

View File

@@ -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
View 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,
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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"
}

View File

@@ -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"}}

View File

@@ -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();
});
}

View File

@@ -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) {

View File

@@ -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();
});
}

View File

@@ -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();
};