Skip to content

Commit

Permalink
Support external eth_sign (openethereum#5481)
Browse files Browse the repository at this point in the history
* Display a QR for eth_sign requests.

* Support raw confirmation of eth_sign

* Fix ethkey issue on nightly.

* Fixing test.

* Fixing test.
  • Loading branch information
tomusdrw authored and gavofyork committed Apr 27, 2017
1 parent 43175f1 commit 28dcbc6
Show file tree
Hide file tree
Showing 17 changed files with 313 additions and 93 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions ethkey/src/brain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ mod tests {
#[test]
fn test_brain() {
let words = "this is sparta!".to_owned();
let first_keypair = Brain(words.clone()).generate().unwrap();
let second_keypair = Brain(words.clone()).generate().unwrap();
let first_keypair = Brain::new(words.clone()).generate().unwrap();
let second_keypair = Brain::new(words.clone()).generate().unwrap();
assert_eq!(first_keypair.secret(), second_keypair.secret());
}
}
42 changes: 41 additions & 1 deletion ethkey/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use rustc_serialize::hex::{ToHex, FromHex};
use bigint::hash::{H520, H256};
use {Secret, Public, SECP256K1, Error, Message, public_to_address, Address};

/// Signature encoded as RSV components
#[repr(C)]
pub struct Signature([u8; 65]);

Expand All @@ -44,8 +45,32 @@ impl Signature {
self.0[64]
}

/// Encode the signature into VRS array (V altered to be in "Electrum" notation).
pub fn into_vrs(self) -> [u8; 65] {
let mut vrs = [0u8; 65];
vrs[0] = self.v() + 27;
vrs[1..33].copy_from_slice(self.r());
vrs[33..65].copy_from_slice(self.s());
vrs
}

/// Parse bytes as a signature encoded as VRS (V in "Electrum" notation).
/// May return empty (invalid) signature if given data has invalid length.
pub fn from_vrs(data: &[u8]) -> Self {
if data.len() != 65 || data[0] < 27 {
// fallback to empty (invalid) signature
return Signature::default();
}

let mut sig = [0u8; 65];
sig[0..32].copy_from_slice(&data[1..33]);
sig[32..64].copy_from_slice(&data[33..65]);
sig[64] = data[0] - 27;
Signature(sig)
}

/// Create a signature object from the sig.
pub fn from_rsv(r: &H256, s: &H256, v: u8) -> Signature {
pub fn from_rsv(r: &H256, s: &H256, v: u8) -> Self {
let mut sig = [0u8; 65];
sig[0..32].copy_from_slice(&r);
sig[32..64].copy_from_slice(&s);
Expand Down Expand Up @@ -222,6 +247,21 @@ mod tests {
use {Generator, Random, Message};
use super::{sign, verify_public, verify_address, recover, Signature};

#[test]
fn vrs_conversion() {
// given
let keypair = Random.generate().unwrap();
let message = Message::default();
let signature = sign(keypair.secret(), &message).unwrap();

// when
let vrs = signature.clone().into_vrs();
let from_vrs = Signature::from_vrs(&vrs);

// then
assert_eq!(signature, from_vrs);
}

#[test]
fn signature_to_and_from_str() {
let keypair = Random.generate().unwrap();
Expand Down
23 changes: 17 additions & 6 deletions js/src/redux/providers/signerMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,28 @@ export default class SignerMiddleware {
return this._hwstore.signLedger(transaction);
})
.then((rawTx) => {
return this.confirmRawTransaction(store, id, rawTx);
return this.confirmRawRequest(store, id, rawTx);
});
}

confirmRawTransaction (store, id, rawTx) {
confirmRawRequest (store, id, rawData) {
const handlePromise = this._createConfirmPromiseHandler(store, id);

return handlePromise(this._api.signer.confirmRequestRaw(id, rawTx));
return handlePromise(this._api.signer.confirmRequestRaw(id, rawData));
}

confirmSignedData (store, id, dataSigned) {
const { signature } = dataSigned;

return this.confirmRawRequest(store, id, signature);
}

confirmSignedTransaction (store, id, txSigned) {
const { netVersion } = store.getState().nodeStatus;
const { signature, tx } = txSigned;
const { rlp } = createSignedTx(netVersion, signature, tx);

return this.confirmRawTransaction(store, id, rlp);
return this.confirmRawRequest(store, id, rlp);
}

confirmWalletTransaction (store, id, transaction, wallet, password) {
Expand Down Expand Up @@ -138,7 +144,7 @@ export default class SignerMiddleware {
return signer.signTransaction(txData);
})
.then((rawTx) => {
return this.confirmRawTransaction(store, id, rawTx);
return this.confirmRawRequest(store, id, rawTx);
})
.catch((error) => {
console.error(error.message);
Expand All @@ -147,7 +153,7 @@ export default class SignerMiddleware {
}

onConfirmStart = (store, action) => {
const { condition, gas = 0, gasPrice = 0, id, password, payload, txSigned, wallet } = action.payload;
const { condition, gas = 0, gasPrice = 0, id, password, payload, txSigned, dataSigned, wallet } = action.payload;
const handlePromise = this._createConfirmPromiseHandler(store, id);
const transaction = payload.sendTransaction || payload.signTransaction;

Expand All @@ -170,6 +176,11 @@ export default class SignerMiddleware {
}
}

// TODO [ToDr] Support eth_sign for external wallet (wallet && !transction)
if (dataSigned) {
return this.confirmSignedData(store, id, dataSigned);
}

return handlePromise(this._api.signer.confirmRequest(id, { gas, gasPrice, condition }, password));
}

Expand Down
18 changes: 14 additions & 4 deletions js/src/util/qrscan.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import Transaction from 'ethereumjs-tx';
import { inAddress, inHex, inNumber10 } from '~/api/format/input';
import { sha3 } from '~/api/util/sha3';

export function createUnsignedTx (api, netVersion, gasStore, transaction) {
const { data, from, gas, gasPrice, to, value } = gasStore.overrideTransaction(transaction);
export function createUnsignedTx (api, netVersion, transaction) {
const { data, from, gas, gasPrice, to, value } = transaction;

return api.parity
.nextNonce(from)
Expand Down Expand Up @@ -111,8 +111,18 @@ export function generateQr (from, tx, hash, rlp) {
});
}

export function generateTxQr (api, netVersion, gasStore, transaction) {
return createUnsignedTx(api, netVersion, gasStore, transaction)
export function generateDataQr (data) {
return Promise.resolve({
data,
value: JSON.stringify({
action: 'signData',
data
})
});
}

export function generateTxQr (api, netVersion, transaction) {
return createUnsignedTx(api, netVersion, transaction)
.then((qr) => {
qr.value = generateQr(transaction.from, qr.tx, qr.hash, qr.rlp);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export default class RequestOrigin extends Component {
<span>
<FormattedMessage
id='signer.requestOrigin.rpc'
defaultMessage='via RPC {rpc}'
defaultMessage='via RPC {url}'
values={ {
url: (
<span className={ styles.url }>
Expand Down
15 changes: 11 additions & 4 deletions js/src/views/Signer/components/SignRequest/signRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';

import HardwareStore from '~/mobx/hardwareStore';

import Account from '../Account';
import TransactionPendingForm from '../TransactionPendingForm';
import RequestOrigin from '../RequestOrigin';
Expand Down Expand Up @@ -68,6 +70,8 @@ class SignRequest extends Component {
}
};

hardwareStore = HardwareStore.get(this.context.api);

componentWillMount () {
const { address, signerStore } = this.props;

Expand Down Expand Up @@ -155,8 +159,9 @@ class SignRequest extends Component {
}

renderActions () {
const { accounts, address, focus, isFinished, status } = this.props;
const account = accounts[address];
const { accounts, address, focus, isFinished, status, data } = this.props;
const account = accounts[address] || {};
const disabled = account.hardware && !this.hardwareStore.isConnected(address);

if (isFinished) {
if (status === 'confirmed') {
Expand Down Expand Up @@ -188,21 +193,23 @@ class SignRequest extends Component {
<TransactionPendingForm
account={ account }
address={ address }
disabled={ disabled }
focus={ focus }
isSending={ this.props.isSending }
netVersion={ this.props.netVersion }
onConfirm={ this.onConfirm }
onReject={ this.onReject }
className={ styles.actions }
dataToSign={ { data } }
/>
);
}

onConfirm = (data) => {
const { id } = this.props;
const { password } = data;
const { password, dataSigned, wallet } = data;

this.props.onConfirm({ id, password });
this.props.onConfirm({ id, password, dataSigned, wallet });
}

onReject = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ class TransactionPending extends Component {
}

renderTransaction () {
const { accounts, className, focus, id, isSending, netVersion, origin, signerStore, transaction } = this.props;
const transaction = this.gasStore.overrideTransaction(this.props.transaction);

const { accounts, className, focus, id, isSending, netVersion, origin, signerStore } = this.props;
const { totalValue } = this.state;
const { balances, externalLink } = signerStore;
const { from, value } = transaction;
Expand Down Expand Up @@ -127,12 +129,11 @@ class TransactionPending extends Component {
address={ from }
disabled={ disabled }
focus={ focus }
gasStore={ this.gasStore }
isSending={ isSending }
netVersion={ netVersion }
onConfirm={ this.onConfirm }
onReject={ this.onReject }
transaction={ transaction }
dataToSign={ { transaction } }
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { FormattedMessage } from 'react-intl';
import ReactTooltip from 'react-tooltip';

import { Form, Input, IdentityIcon, QrCode, QrScan } from '~/ui';
import { generateTxQr } from '~/util/qrscan';
import { generateTxQr, generateDataQr } from '~/util/qrscan';

import styles from './transactionPendingFormConfirm.css';

Expand All @@ -40,11 +40,10 @@ export default class TransactionPendingFormConfirm extends Component {
address: PropTypes.string.isRequired,
disabled: PropTypes.bool,
focus: PropTypes.bool,
gasStore: PropTypes.object.isRequired,
netVersion: PropTypes.string.isRequired,
isSending: PropTypes.bool.isRequired,
onConfirm: PropTypes.func.isRequired,
transaction: PropTypes.object.isRequired
dataToSign: PropTypes.object.isRequired
};

static defaultProps = {
Expand Down Expand Up @@ -406,20 +405,30 @@ export default class TransactionPendingFormConfirm extends Component {
}

onScanTx = (signature) => {
const { chainId, rlp, tx } = this.state.qr;
const { chainId, rlp, tx, data } = this.state.qr;

if (signature && signature.substr(0, 2) !== '0x') {
signature = `0x${signature}`;
}

this.setState({ qrState: QR_COMPLETED });

if (tx) {
this.props.onConfirm({
txSigned: {
chainId,
rlp,
signature,
tx
}
});
return;
}

this.props.onConfirm({
txSigned: {
chainId,
rlp,
signature,
tx
dataSigned: {
data,
signature
}
});
}
Expand Down Expand Up @@ -487,13 +496,20 @@ export default class TransactionPendingFormConfirm extends Component {
});
}

generateTxQr = () => {
generateQr = () => {
const { api } = this.context;
const { netVersion, gasStore, transaction } = this.props;

generateTxQr(api, netVersion, gasStore, transaction).then((qr) => {
const { netVersion, dataToSign } = this.props;
const { transaction, data } = dataToSign;
const setState = qr => {
this.setState({ qr });
});
};

if (transaction) {
generateTxQr(api, netVersion, transaction).then(setState);
return;
}

generateDataQr(data).then(setState);
}

onKeyDown = (event) => {
Expand Down Expand Up @@ -528,19 +544,25 @@ export default class TransactionPendingFormConfirm extends Component {

readNonce = () => {
const { api } = this.context;
const { account } = this.props;
const { account, dataToSign } = this.props;
const { qr } = this.state;

if (dataToSign.data && qr && !qr.value) {
this.generateQr();
return;
}

if (!account || !account.external || !api.transport.isConnected) {
if (!account || !account.external || !api.transport.isConnected || !dataToSign.transaction) {
return;
}

return api.parity
.nextNonce(account.address)
.then((nonce) => {
const { qr } = this.state;
.then((newNonce) => {
const { nonce } = this.state.qr;

if (!qr.nonce || !nonce.eq(qr.nonce)) {
this.generateTxQr();
if (!nonce || !newNonce.eq(nonce)) {
this.generateQr();
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function render (address) {
address={ address }
onConfirm={ onConfirm }
isSending={ false }
dataToSign={ {} }
/>
);
instance = component.instance();
Expand Down
Loading

0 comments on commit 28dcbc6

Please sign in to comment.