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:
parent
a66337ac0a
commit
3ef2b51a68
|
@ -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
|
||||||
|
});
|
||||||
|
}
|
|
@ -84,3 +84,53 @@ export function isPositiveIntegerOrZero(number: number): boolean {
|
||||||
}
|
}
|
||||||
return number >= 0 && parseInt(number) === number;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
export default class BaseWallet {
|
export default class BaseWallet {
|
||||||
getAddress(): string {
|
getAddress(): Promise<any> {
|
||||||
throw 'Implement me';
|
return Promise.reject('Implement me');
|
||||||
}
|
}
|
||||||
|
|
||||||
getNakedAddress(): string {
|
getNakedAddress(): Promise<any> {
|
||||||
return this.getAddress().replace('0x', '').toLowerCase();
|
return new Promise(resolve => {
|
||||||
|
this.getAddress.then(address => {
|
||||||
|
resolve(address.replace('0x', '').toLowerCase());
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import {
|
||||||
} from 'ethereumjs-util';
|
} from 'ethereumjs-util';
|
||||||
import { randomBytes } from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
import { pkeyToKeystore } from 'libs/keystore';
|
import { pkeyToKeystore } from 'libs/keystore';
|
||||||
|
import { signRawTxWithPrivKey, signMessageWithPrivKey } from 'libs/signing';
|
||||||
|
import type { RawTx } from 'libs/validators';
|
||||||
|
|
||||||
export default class PrivKeyWallet extends BaseWallet {
|
export default class PrivKeyWallet extends BaseWallet {
|
||||||
privKey: Buffer;
|
privKey: Buffer;
|
||||||
|
@ -19,8 +21,10 @@ export default class PrivKeyWallet extends BaseWallet {
|
||||||
this.address = publicToAddress(this.pubKey);
|
this.address = publicToAddress(this.pubKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAddress() {
|
getAddress(): Promise<any> {
|
||||||
return toChecksumAddress(`0x${this.address.toString('hex')}`);
|
return new Promise(resolve => {
|
||||||
|
resolve(toChecksumAddress(`0x${this.address.toString('hex')}`));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getPrivateKey() {
|
getPrivateKey() {
|
||||||
|
@ -31,7 +35,35 @@ export default class PrivKeyWallet extends BaseWallet {
|
||||||
return new PrivKeyWallet(randomBytes(32));
|
return new PrivKeyWallet(randomBytes(32));
|
||||||
}
|
}
|
||||||
|
|
||||||
toKeystore(password: string): Object {
|
toKeystore(password: string): Promise<any> {
|
||||||
return pkeyToKeystore(this.privKey, this.getNakedAddress(), password);
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"big.js": "^3.1.3",
|
"big.js": "^3.1.3",
|
||||||
"ethereum-blockies": "git+https://github.com/MyEtherWallet/blockies.git",
|
"ethereum-blockies": "git+https://github.com/MyEtherWallet/blockies.git",
|
||||||
|
"ethereumjs-tx": "^1.3.3",
|
||||||
"ethereumjs-util": "^5.1.2",
|
"ethereumjs-util": "^5.1.2",
|
||||||
"ethereumjs-wallet": "^0.6.0",
|
"ethereumjs-wallet": "^0.6.0",
|
||||||
"font-awesome": "^4.7.0",
|
"font-awesome": "^4.7.0",
|
||||||
|
|
Loading…
Reference in New Issue