Added Secret Storage JSON Wallet support.
This commit is contained in:
parent
8033a8f94c
commit
b5710cd710
179
README.md
179
README.md
@ -5,14 +5,21 @@ Complete Ethereum wallet implementation in JavaScript.
|
||||
|
||||
Features
|
||||
- Keep your private keys in the browser
|
||||
- Import and export JSON wallets (geth and crowdsale)
|
||||
- Generate JavaScript bindings for any contract ABI
|
||||
- Small (~100kb compressed; 290kb uncompressed)
|
||||
- MIT licensed (with one exception, which we are migrating off of; see below)
|
||||
|
||||
*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.*
|
||||
**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.**
|
||||
|
||||
|
||||
Wallet API
|
||||
----------
|
||||
API
|
||||
---
|
||||
|
||||
|
||||
### Wallet API
|
||||
|
||||
An *Ethereum* wallet wraps a cryptographic private key, which is used to sign transactions and control the ether located at the wallet's address. These transactions can then be broadcast to the *Ethereum* network.
|
||||
|
||||
```javascript
|
||||
// A private key can be specified as a 32 byte buffer or hexidecimal string
|
||||
@ -29,18 +36,36 @@ var privateKey = '0x314159265358979323846264338327950288419716939937510582097494
|
||||
// Create a wallet object
|
||||
var wallet = new Wallet(privateKey)
|
||||
|
||||
// Wallet privateKey
|
||||
console.log(wallet.privateKey)
|
||||
/// "0x3141592653589793238462643383279502884197169399375105820974944592"
|
||||
|
||||
// Wallet address
|
||||
console.log(wallet.address)
|
||||
/// "0x7357589f8e367c2C31F51242fB77B350A11830F3"
|
||||
|
||||
// Sign transactions
|
||||
wallet.sign({
|
||||
to: "0x06B5955A67D827CDF91823E3bB8F069e6c89c1D6",
|
||||
gasLimit: 3000000,
|
||||
gasPrice: "0x1000",
|
||||
value: "0x1000"
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### Converting addresses
|
||||
|
||||
Addresses come in various forms, and it is often useful to convert between them. You can pass any valid address into any function, and the library will convert it internally as needed. The address types are:
|
||||
- **hexidecimal** - 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef (all letters must be either lower case or uppercase; no mixed case, as this implies a checksum address)
|
||||
- **ICAP** - XE49Q0EPSW7XTS5PRIE9226HRPOO69XRVU7 (uses the International Bank Account Number format)
|
||||
- **checksummed** - 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF (notice the case is adjusted encoding checkum information)
|
||||
|
||||
```javascript
|
||||
// ICAP Addresses
|
||||
Wallet.getIcapAddress(wallet.address)
|
||||
/// "XE39DH16QOXYG5JY9BYY6JGZW8ORUPBX71V"
|
||||
|
||||
// All address functions accept any format
|
||||
Wallet.getIcapAddress("XE39DH16QOXYG5JY9BYY6JGZW8ORUPBX71V")
|
||||
/// "XE39DH16QOXYG5JY9BYY6JGZW8ORUPBX71V"
|
||||
|
||||
// Get checksummed address (from ICAP)
|
||||
Wallet.getAddress("XE39DH16QOXYG5JY9BYY6JGZW8ORUPBX71V")
|
||||
/// "0x7357589f8e367c2C31F51242fB77B350A11830F3"
|
||||
@ -51,18 +76,109 @@ Wallet.getAddress("0x7357589f8e367c2c31f51242fb77b350a11830f3")
|
||||
|
||||
// Detect address checksum errors (notice the last "f" should be lowercase)
|
||||
Wallet.getAddress('0x7357589f8e367c2c31f51242fb77b350a11830F3')
|
||||
/// Error: invalid checksum address
|
||||
|
||||
// Sign transactions
|
||||
wallet.sign({
|
||||
to: "0x06B5955A67D827CDF91823E3bB8F069e6c89c1D6",
|
||||
gasLimit: 3000000,
|
||||
gasPrice: "0x1000",
|
||||
value: "0x1000"
|
||||
})
|
||||
|
||||
/// throws Error: invalid checksum address
|
||||
```
|
||||
|
||||
|
||||
### Crowdsale JSON Wallets
|
||||
|
||||
During the crowdsale, the Ethereum Project sold ether by generating *crowdsale JSON wallet*. These functions allow you to decrypt those files and retreive the private key.
|
||||
|
||||
```javascript
|
||||
|
||||
// See the test-wallets directory samples (the variable should be a string)
|
||||
var json = "... see the test-wallets directory for samples ...";
|
||||
|
||||
// Detect crowdsale JSON wallets
|
||||
Wallet.isCrowdsaleWallet(json)
|
||||
|
||||
// Get a wallet from a crowdsale JSON wallet
|
||||
var wallet = Wallet.decryptCrowdsaleWallet(json, password);
|
||||
console.log(wallet.address)
|
||||
console.log(wallet.privateKey)
|
||||
```
|
||||
|
||||
|
||||
### Secret Storage JSON Wallet
|
||||
|
||||
This API allows you to decrypt and encrypt the [Secret Storage](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) format used by *Geth* and many other wallet platforms (such as *ethers.io*).
|
||||
|
||||
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')`.
|
||||
|
||||
#### Wallet.decrypt(json, password, callback);
|
||||
|
||||
```javascript
|
||||
|
||||
// See the test-wallets directory samples (the variable should be a string)
|
||||
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 {
|
||||
}
|
||||
|
||||
} else if (wallet) {
|
||||
// The wallet was successfully decrypted
|
||||
|
||||
} else {
|
||||
// The wallet is still being decrypted
|
||||
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.prototype.encrypt(password[, options], callback);
|
||||
|
||||
```javascript
|
||||
|
||||
// Encrypt a wallet into a Secret Storage JSON Wallet (all options are optional)
|
||||
var bytes16 = '0xdeadbeef1deadbeef2deadbeef301234';
|
||||
var options = {
|
||||
salt: bytes16, // hex string or Buffer, any length
|
||||
iv: bytes16, // hex string or Buffer, 16 bytes
|
||||
uuid: bytes16, // hex string or Buffer, 16 bytes
|
||||
scrypt: {
|
||||
N: (1 << 17), // Number, power of 2 greater than 2
|
||||
p: 8, // Number
|
||||
r: 1 // Number
|
||||
}
|
||||
}
|
||||
|
||||
var wallet = new Wallet(privateKey);
|
||||
wallet.encrypt(password, options, function(error, json, progress) {
|
||||
if (error) {
|
||||
if (error.message === 'invalid password') {
|
||||
// Wrong password
|
||||
}
|
||||
|
||||
} else if (json) {
|
||||
// The wallet was successfully 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
|
||||
// called once more with callback(new Error("cancelled"))
|
||||
return shouldCancelDecrypt;
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
Contract API
|
||||
------------
|
||||
|
||||
@ -136,6 +252,35 @@ contract.setValue("Hello World", options).then(function(txid) {
|
||||
```
|
||||
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
A lot of the test cases are performed by comparing against known working implementations of many of the features of this library. To run the test suite, you must use `npm install` (without the `--production` flag, which would skip the development dependencies.)
|
||||
|
||||
To run the test suite,
|
||||
|
||||
```
|
||||
/Users/ethers> npm test
|
||||
|
||||
> ethers-wallet@0.0.1 test /Users/ricmoo/Development/ethers/ethers-wallet
|
||||
> nodeunit test.js
|
||||
|
||||
|
||||
test.js
|
||||
+ testSecretStorage
|
||||
+ testCoderParams
|
||||
+ testContract
|
||||
+ testChecksumAddress
|
||||
+ testIcapAddress
|
||||
+ testAddress
|
||||
+ testTransaction
|
||||
|
||||
OK: 85128 assertions (66646ms)
|
||||
```
|
||||
|
||||
There are also some test JSON wallets available in the [test-wallets](https://github.com/ethers-io/ethers-wallet/tree/master/test-wallets) directory.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
|
29
index.js
29
index.js
@ -1,6 +1,7 @@
|
||||
var rlp = require('rlp');
|
||||
|
||||
var Contract = require('./lib/contract.js');
|
||||
var secretStorage = require('./lib/secret-storage.js');
|
||||
var SigningKey = require('./lib/signing-key.js');
|
||||
var Wallet = require('./lib/wallet.js');
|
||||
|
||||
@ -31,4 +32,32 @@ module.exports = Wallet;
|
||||
utils.defineProperty(Wallet, 'getAddress', SigningKey.getAddress);
|
||||
utils.defineProperty(Wallet, 'getIcapAddress', SigningKey.getIcapAddress);
|
||||
|
||||
utils.defineProperty(Wallet, 'isCrowdsaleWallet', secretStorage.isCrowdsaleWallet);
|
||||
utils.defineProperty(Wallet, 'isValidWallet', secretStorage.isValidWallet);
|
||||
|
||||
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'); }
|
||||
|
||||
secretStorage.decrypt(json, password, function(error, signingKey, progress) {
|
||||
if (signingKey) {
|
||||
return callback(error, new Wallet(signingKey), progress);
|
||||
}
|
||||
return callback(error, signingKey, progress);
|
||||
});
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet.prototype, 'encrypt', function(password, options, callback) {
|
||||
if (typeof(options) === 'function' && !callback) {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
if (typeof(callback) !== 'function') { throw new Error('invalid callback'); }
|
||||
|
||||
secretStorage.encrypt(this.privateKey, password, options, callback);
|
||||
});
|
||||
|
||||
module.exports = Wallet;
|
||||
|
10
lib/randomish.js
Normal file
10
lib/randomish.js
Normal file
@ -0,0 +1,10 @@
|
||||
var utils = require('./utils.js');
|
||||
|
||||
function Randomish() {
|
||||
}
|
||||
|
||||
utils.defineProperty(Randomish, 'randomBytes', function() {
|
||||
|
||||
});
|
||||
|
||||
module.exports = Randomish
|
334
lib/secret-storage.js
Normal file
334
lib/secret-storage.js
Normal file
@ -0,0 +1,334 @@
|
||||
var aes = require('aes-js');
|
||||
var pbkdf2 = require('pbkdf2');
|
||||
var scrypt = require('scrypt-js');
|
||||
var uuid = require('uuid');
|
||||
|
||||
var Randomish = require('./randomish.js');
|
||||
var SigningKey = require('./signing-key.js');
|
||||
var utils = require('./utils.js')
|
||||
|
||||
|
||||
// Search an Object and its children recursively, caselessly.
|
||||
function searchPath(object, path) {
|
||||
var currentChild = object;
|
||||
|
||||
var comps = path.toLowerCase().split('/');
|
||||
for (var i = 0; i < comps.length; i++) {
|
||||
|
||||
// Search for a child object with a case-insensitive matching key
|
||||
var matchingChild = null;
|
||||
for (var key in currentChild) {
|
||||
if (key.toLowerCase() === comps[i]) {
|
||||
matchingChild = currentChild[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't find one. :'(
|
||||
if (matchingChild === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Now check this child...
|
||||
currentChild = matchingChild;
|
||||
}
|
||||
|
||||
return currentChild;
|
||||
}
|
||||
|
||||
/*
|
||||
function SecretStorage(json, signingKey) {
|
||||
if (!(this instanceof SecretStorage)) { throw new Error('missing new'); }
|
||||
|
||||
utils.defineProperty(this, 'json', json);
|
||||
|
||||
Object.defineProperty(this, 'data', {
|
||||
enumerable: true,
|
||||
get: function() { return JSON.parse(json); }
|
||||
});
|
||||
|
||||
utils.defineProperty(this, 'address', signingKey.privateKey);
|
||||
utils.defineProperty(this, 'signingKey', signingKey);
|
||||
}
|
||||
*/
|
||||
var secretStorage = {};
|
||||
|
||||
|
||||
utils.defineProperty(secretStorage, 'isCrowdsaleWallet', function(json) {
|
||||
try {
|
||||
var data = JSON.parse(json);
|
||||
} catch (error) { return false; }
|
||||
|
||||
return (data.encseed && data.ethaddr);
|
||||
});
|
||||
|
||||
utils.defineProperty(secretStorage, 'isValidWallet', function(json) {
|
||||
try {
|
||||
var data = JSON.parse(json);
|
||||
} catch (error) { return false; }
|
||||
|
||||
if (!data.version || parseInt(data.version) !== data.version || parseInt(data.version) !== 3) {
|
||||
return false;
|
||||
}
|
||||
// @TODO: Put more checks to make sure it has kdf, iv and all that good stuff
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
// See: https://github.com/ethereum/pyethsaletool
|
||||
utils.defineProperty(secretStorage, 'decryptCrowdsale', function(json, password) {
|
||||
var data = JSON.parse(json);
|
||||
|
||||
// Ethereum Address
|
||||
var ethaddr = SigningKey.getAddress(searchPath(data, 'ethaddr'));
|
||||
|
||||
// Encrypted Seed
|
||||
var encseed = new Buffer(searchPath(data, 'encseed'), 'hex');
|
||||
if (!encseed || (encseed.length % 16) !== 0) {
|
||||
throw new Error('invalid encseed');
|
||||
}
|
||||
|
||||
var key = pbkdf2.pbkdf2Sync(password, password, 2000, 32, 'sha256').slice(0, 16);
|
||||
|
||||
var iv = encseed.slice(0, 16);
|
||||
var encryptedSeed = encseed.slice(16);
|
||||
|
||||
// Decrypt the seed
|
||||
var seed = new Buffer(0);
|
||||
var aesCbc = new aes.ModeOfOperation.cbc(key, iv);
|
||||
for (var i = 0; i < encryptedSeed.length; i += 16) {
|
||||
seed = Buffer.concat([seed, new Buffer(aesCbc.decrypt(encryptedSeed.slice(i, i + 16)))]);
|
||||
}
|
||||
|
||||
// Check PKCS#7 padding is valid
|
||||
var pad = seed[seed.length - 1];
|
||||
if (pad > 16 || pad > seed.length) {
|
||||
throw new Error('invalid password');
|
||||
}
|
||||
for (var i = seed.length - pad; i < seed.length; i++) {
|
||||
if (seed[i] !== pad) {
|
||||
throw new Error('invalid password');
|
||||
}
|
||||
}
|
||||
|
||||
// Strip the padding
|
||||
seed = seed.slice(0, seed.length - pad);
|
||||
|
||||
// This wallet format is weird... Convert the binary encoded hex to a string.
|
||||
var seedHex = '';
|
||||
for (var i = 0; i < seed.length; i++) {
|
||||
seedHex += String.fromCharCode(seed[i]);
|
||||
}
|
||||
|
||||
var signingKey = new SigningKey(utils.sha3(new Buffer(seedHex)));
|
||||
|
||||
if (signingKey.address !== ethaddr) {
|
||||
throw new Error('corrupt crowdsale wallet');
|
||||
}
|
||||
|
||||
return signingKey;
|
||||
});
|
||||
|
||||
|
||||
utils.defineProperty(secretStorage, 'decrypt', function(json, password, callback) {
|
||||
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') {
|
||||
var iv = new Buffer(searchPath(data, 'crypto/cipherparams/iv'), 'hex')
|
||||
var counter = new aes.Counter(iv);
|
||||
|
||||
var aesCtr = new aes.ModeOfOperation.ctr(key, counter);
|
||||
|
||||
return new Buffer(aesCtr.decrypt(ciphertext));
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
var computeMAC = function(derivedHalf, ciphertext) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
// @TOOD: Support pbkdf2 kdf
|
||||
return fail('unsupported key-derivation function');
|
||||
}
|
||||
});
|
||||
|
||||
utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, options, callback) {
|
||||
|
||||
// the options are optional, so adjust the call as needed
|
||||
if (typeof(options) === 'function' && !callback) {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
if (!options) { options = {}; }
|
||||
|
||||
// Check the private key
|
||||
if (privateKey instanceof SigningKey) {
|
||||
privateKey = privateKey.privateKey;
|
||||
}
|
||||
privateKey = utils.hexOrBuffer(privateKey, 'private key');
|
||||
if (privateKey.length !== 32) { throw new Erro('invalid private key'); }
|
||||
|
||||
// Check the password
|
||||
if (!Buffer.isBuffer(password)) { throw new Error('password must be a buffer'); }
|
||||
|
||||
// Check/generate the DAO
|
||||
var salt = options.salt;
|
||||
if (salt) {
|
||||
salt = utils.hexOrBuffer(salt, 'salt');
|
||||
} else {
|
||||
salt = (new Randomish()).randomBytes(32);;
|
||||
}
|
||||
|
||||
// Override initialization vector
|
||||
var iv = null;
|
||||
if (options.iv) {
|
||||
iv = utils.hexOrBuffer(options.iv, 'iv');
|
||||
if (iv.length !== 16) { throw new Error('invalid iv'); }
|
||||
}
|
||||
|
||||
// Override the uuid
|
||||
var uuidRandom = options.uuid;
|
||||
if (uuidRandom) {
|
||||
uuidRandom = utils.hexOrBuffer(uuidRandom, 'uuid');
|
||||
if (uuidRandom.length !== 16) { throw new Error('invalid uuid'); }
|
||||
}
|
||||
|
||||
// Override the scrypt password-based key derivation function parameters
|
||||
var N = (1 << 17), p = 1, r = 8;
|
||||
if (options.scrypt) {
|
||||
if (options.scrypt.N) { N = options.scrypt.N; }
|
||||
if (options.scrypt.p) { p = options.scrypt.p; }
|
||||
if (options.scrypt.r) { r = options.scrypt.r; }
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return callback(error, null, progress);
|
||||
|
||||
} 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);
|
||||
|
||||
// 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 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));
|
||||
|
||||
// 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')
|
||||
}
|
||||
};
|
||||
|
||||
return callback(null, JSON.stringify(data), 1);
|
||||
|
||||
} else {
|
||||
return callback(null, null, progress);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
module.exports = secretStorage;
|
Loading…
Reference in New Issue
Block a user