Add Promises & Signing to Wallet Classes (#105)

* add validation for raw tx

* add node module, add signing lib

* add tx & message signing, promise everything

* remove unnecessary returns

* move isValidRawTx to for loop

* add & use new type RawTx

* implement cleaner promises, reject instead of throw
This commit is contained in:
skubakdj 2017-08-07 23:25:23 -04:00 committed by Daniel Ternyak
parent a66337ac0a
commit 3ef2b51a68
5 changed files with 135 additions and 8 deletions

40
common/libs/signing.js Normal file
View File

@ -0,0 +1,40 @@
// @flow
import EthTx from 'ethereumjs-tx';
import { sha3, ecsign } from 'ethereumjs-util';
import { isValidRawTx } from 'libs/validators';
import type { RawTx } from 'libs/validators';
export function signRawTxWithPrivKey(privKey: Buffer, rawTx: RawTx): string {
if (!isValidRawTx(rawTx)) {
throw new Error('Invalid raw transaction');
}
let eTx = new EthTx(rawTx);
eTx.sign(privKey);
return '0x' + eTx.serialize().toString('hex');
}
export function signMessageWithPrivKey(
privKey: Buffer,
msg: string,
address: string,
date: string
): string {
let spacer = msg.length > 0 && date.length > 0 ? ' ' : '';
let fullMessage = msg + spacer + date;
let hash = sha3(fullMessage);
let signed = ecsign(hash, privKey);
let combined = Buffer.concat([
Buffer.from(signed.r),
Buffer.from(signed.s),
Buffer.from([signed.v])
]);
let combinedHex = combined.toString('hex');
return JSON.stringify({
address: address,
msg: fullMessage,
sig: '0x' + combinedHex
});
}

View File

@ -84,3 +84,53 @@ export function isPositiveIntegerOrZero(number: number): boolean {
}
return number >= 0 && parseInt(number) === number;
}
export type RawTx = {
nonce: string,
gasPrice: string,
gasLimit: string,
to: string,
value: string,
data: string,
chainId: number
};
export function isValidRawTx(rawTx: RawTx): boolean {
const propReqs = [
{ name: 'nonce', type: 'string', lenReq: true },
{ name: 'gasPrice', type: 'string', lenReq: true },
{ name: 'gasLimit', type: 'string', lenReq: true },
{ name: 'to', type: 'string', lenReq: true },
{ name: 'value', type: 'string', lenReq: true },
{ name: 'data', type: 'string', lenReq: false },
{ name: 'chainId', type: 'number' }
];
//ensure rawTx has above properties
//ensure all specified types match
//ensure length !0 for strings where length is required
//ensure valid hex for strings
//ensure all strings begin with '0x'
//ensure valid address for 'to' prop
//ensure rawTx only has above properties
for (let i = 0; i < propReqs.length; i++) {
const prop = propReqs[i];
const value = rawTx[prop.name];
if (!rawTx.hasOwnProperty(prop.name)) return false;
if (typeof value !== prop.type) return false;
if (prop.type === 'string') {
if (prop.lenReq && value.length === 0) return false;
if (value.length && value.substring(0, 2) !== '0x') {
return false;
}
if (!isValidHex(value)) return false;
}
}
if (!isValidETHAddress(rawTx.to)) return false;
if (Object.keys(rawTx).length !== propReqs.length) return false;
return true;
}

View File

@ -1,11 +1,15 @@
// @flow
export default class BaseWallet {
getAddress(): string {
throw 'Implement me';
getAddress(): Promise<any> {
return Promise.reject('Implement me');
}
getNakedAddress(): string {
return this.getAddress().replace('0x', '').toLowerCase();
getNakedAddress(): Promise<any> {
return new Promise(resolve => {
this.getAddress.then(address => {
resolve(address.replace('0x', '').toLowerCase());
});
});
}
}

View File

@ -7,6 +7,8 @@ import {
} from 'ethereumjs-util';
import { randomBytes } from 'crypto';
import { pkeyToKeystore } from 'libs/keystore';
import { signRawTxWithPrivKey, signMessageWithPrivKey } from 'libs/signing';
import type { RawTx } from 'libs/validators';
export default class PrivKeyWallet extends BaseWallet {
privKey: Buffer;
@ -19,8 +21,10 @@ export default class PrivKeyWallet extends BaseWallet {
this.address = publicToAddress(this.pubKey);
}
getAddress() {
return toChecksumAddress(`0x${this.address.toString('hex')}`);
getAddress(): Promise<any> {
return new Promise(resolve => {
resolve(toChecksumAddress(`0x${this.address.toString('hex')}`));
});
}
getPrivateKey() {
@ -31,7 +35,35 @@ export default class PrivKeyWallet extends BaseWallet {
return new PrivKeyWallet(randomBytes(32));
}
toKeystore(password: string): Object {
return pkeyToKeystore(this.privKey, this.getNakedAddress(), password);
toKeystore(password: string): Promise<any> {
return new Promise(resolve => {
this.getNakedAddress().then(address => {
resolve(pkeyToKeystore(this.privKey, address, password));
});
});
}
unlock(): Promise<any> {
return Promise.resolve();
}
signRawTransaction(rawTx: RawTx): Promise<any> {
return new Promise((resolve, reject) => {
try {
resolve(signRawTxWithPrivKey(this.privKey, rawTx));
} catch (err) {
reject(err);
}
});
}
signMessage(msg: string, address: string, date: string): Promise<any> {
return new Promise((resolve, reject) => {
try {
resolve(signMessageWithPrivKey(this.privKey, msg, address, date));
} catch (err) {
reject(err);
}
});
}
}

View File

@ -10,6 +10,7 @@
"dependencies": {
"big.js": "^3.1.3",
"ethereum-blockies": "git+https://github.com/MyEtherWallet/blockies.git",
"ethereumjs-tx": "^1.3.3",
"ethereumjs-util": "^5.1.2",
"ethereumjs-wallet": "^0.6.0",
"font-awesome": "^4.7.0",