diff --git a/common/libs/signing.js b/common/libs/signing.js new file mode 100644 index 00000000..10e8dc64 --- /dev/null +++ b/common/libs/signing.js @@ -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 + }); +} diff --git a/common/libs/validators.js b/common/libs/validators.js index 08a77c68..30ae9def 100644 --- a/common/libs/validators.js +++ b/common/libs/validators.js @@ -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; +} diff --git a/common/libs/wallet/base.js b/common/libs/wallet/base.js index b3e20fa5..738b0750 100644 --- a/common/libs/wallet/base.js +++ b/common/libs/wallet/base.js @@ -1,11 +1,15 @@ // @flow export default class BaseWallet { - getAddress(): string { - throw 'Implement me'; + getAddress(): Promise { + return Promise.reject('Implement me'); } - getNakedAddress(): string { - return this.getAddress().replace('0x', '').toLowerCase(); + getNakedAddress(): Promise { + return new Promise(resolve => { + this.getAddress.then(address => { + resolve(address.replace('0x', '').toLowerCase()); + }); + }); } } diff --git a/common/libs/wallet/privkey.js b/common/libs/wallet/privkey.js index da78e6fb..d86dc25c 100644 --- a/common/libs/wallet/privkey.js +++ b/common/libs/wallet/privkey.js @@ -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 { + 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 { + return new Promise(resolve => { + this.getNakedAddress().then(address => { + resolve(pkeyToKeystore(this.privKey, address, password)); + }); + }); + } + + unlock(): Promise { + return Promise.resolve(); + } + + signRawTransaction(rawTx: RawTx): Promise { + return new Promise((resolve, reject) => { + try { + resolve(signRawTxWithPrivKey(this.privKey, rawTx)); + } catch (err) { + reject(err); + } + }); + } + + signMessage(msg: string, address: string, date: string): Promise { + return new Promise((resolve, reject) => { + try { + resolve(signMessageWithPrivKey(this.privKey, msg, address, date)); + } catch (err) { + reject(err); + } + }); } } diff --git a/package.json b/package.json index 446c89af..bebd61eb 100644 --- a/package.json +++ b/package.json @@ -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",