Implement signRaw (#196)

This commit is contained in:
Caio
2019-12-06 15:49:37 +01:00
committed by Jaco Greeff
parent 1ac4bc2442
commit 411e1f1ed1
10 changed files with 213 additions and 71 deletions
@@ -0,0 +1,59 @@
// Copyright 2019 @polkadot/extension-ui authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import styled from 'styled-components';
import React from 'react';
interface Props {
className?: string;
bytes: string;
url: string;
}
function Bytes ({ bytes, className, url }: Props): React.ReactElement<Props> {
return (
<table className={className}>
<tbody>
<tr>
<td className='label'>from</td>
<td className='data'>{url}</td>
</tr>
<tr>
<td className='label'>bytes</td>
<td className='data'>{bytes}</td>
</tr>
</tbody>
</table>
);
}
export default styled(Bytes)`
border: 0;
display: block;
font-size: 0.75rem;
margin-top: 0.75rem;
td.data {
max-width: 0;
overflow: hidden;
text-align: left;
text-overflow: ellipsis;
vertical-align: middle;
width: 100%;
pre {
font-family: inherit;
font-size: 0.75rem;
margin: 0;
}
}
td.label {
opacity: 0.5;
padding: 0 0.5rem;
text-align: right;
vertical-align: middle;
white-space: nowrap;
}
`;
@@ -92,7 +92,7 @@ function mortalityAsString (era: ExtrinsicEra, hexBlockNumber: string): string {
return `mortal, valid from #${formatNumber(mortal.birth(blockNumber))} to #${formatNumber(mortal.death(blockNumber))}`;
}
function Details ({ className, isDecoded, payload: { era, nonce, tip }, request: { blockNumber, genesisHash, method, specVersion: hexSpec }, url }: Props): React.ReactElement<Props> {
function Extrinsic ({ className, isDecoded, payload: { era, nonce, tip }, request: { blockNumber, genesisHash, method, specVersion: hexSpec }, url }: Props): React.ReactElement<Props> {
const chain = useRef(findChain(genesisHash)).current;
const specVersion = useRef(bnToBn(hexSpec)).current;
const [decoded, setDecoded] = useState<Decoded>({ json: null, method: null });
@@ -136,7 +136,7 @@ function Details ({ className, isDecoded, payload: { era, nonce, tip }, request:
);
}
export default styled(Details)`
export default styled(Extrinsic)`
border: 0;
display: block;
font-size: 0.75rem;
@@ -3,21 +3,23 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { ExtrinsicPayload } from '@polkadot/types/interfaces';
import { AccountJson, RequestExtrinsicSign } from '@polkadot/extension/background/types';
import { AccountJson, RequestSign } from '@polkadot/extension/background/types';
import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
import React, { useContext, useState, useEffect } from 'react';
import { TypeRegistry, createType } from '@polkadot/types';
import { ActionBar, ActionContext, Address, Link } from '../../components';
import { approveSignPassword, approveSignSignature, cancelSignRequest } from '../../messaging';
import Details from './Details';
import Bytes from './Bytes';
import Extrinsic from './Extrinsic';
import Qr from './Qr';
import Unlock from './Unlock';
interface Props {
account: AccountJson;
isFirst: boolean;
request: RequestExtrinsicSign;
request: RequestSign;
signId: string;
url: string;
}
@@ -27,16 +29,18 @@ const registry = new TypeRegistry();
export default function Request ({ account: { isExternal }, isFirst, request, signId, url }: Props): React.ReactElement<Props> | null {
const onAction = useContext(ActionContext);
const [payload, setPayload] = useState<ExtrinsicPayload | null>(null);
const [hexBytes, setHexBytes] = useState<string | null>(null);
const [extrinsic, setExtrinsic] = useState<ExtrinsicPayload | null>(null);
useEffect((): void => {
setPayload(createType(registry, 'ExtrinsicPayload', request, { version: request.version }));
const inner = request.inner;
if ((inner as SignerPayloadRaw).data) {
setHexBytes((inner as SignerPayloadRaw).data);
} else {
setExtrinsic(createType(registry, 'ExtrinsicPayload', inner, { version: (inner as SignerPayloadJSON).version }));
}
}, [request]);
if (!payload) {
return null;
}
const _onCancel = (): Promise<void> =>
cancelSignRequest(signId)
.then((): void => onAction())
@@ -49,30 +53,46 @@ export default function Request ({ account: { isExternal }, isFirst, request, si
approveSignSignature(signId, signature)
.then((): void => onAction())
.catch((error: Error) => console.error(error));
return (
<Address
address={request.address}
genesisHash={request.genesisHash}
>
{isExternal && isFirst
? <Qr
payload={payload}
request={request}
onSignature={_onSignature}
/>
: <Details
isDecoded={isFirst}
payload={payload}
request={request}
url={url}
/>
}
{isFirst && !isExternal && <Unlock onSign={_onSign} />}
<ActionBar>
<div />
<Link isDanger onClick={_onCancel}>Cancel</Link>
</ActionBar>
</Address>
);
if (extrinsic !== null) {
const payload = request.inner as SignerPayloadJSON;
return (
<Address
address={payload.address}
genesisHash={payload.genesisHash}
>
{isExternal && isFirst
? <Qr
payload={extrinsic}
request={payload}
onSignature={_onSignature}
/>
: <Extrinsic
isDecoded={isFirst}
payload={extrinsic}
request={payload}
url={url}
/>
}
{isFirst && !isExternal && <Unlock onSign={_onSign} />}
<ActionBar>
<div />
<Link isDanger onClick={_onCancel}>Cancel</Link>
</ActionBar>
</Address>
);
} else if (hexBytes !== null) {
const payload = request.inner as SignerPayloadRaw;
return (
<Address address={payload.address}>
<Bytes bytes={payload.data} url={url} />
{isFirst && !isExternal && <Unlock onSign={_onSign} />}
<ActionBar>
<div />
<Link isDanger onClick={_onCancel}>Cancel</Link>
</ActionBar>
</Address>
);
} else {
return null;
}
}
@@ -0,0 +1,23 @@
// Copyright 2019 @polkadot/extension authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { KeyringPair } from '@polkadot/keyring/types';
import { RequestSign } from './types';
import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
import { u8aToHex, hexToU8a } from '@polkadot/util';
import { TypeRegistry } from '@polkadot/types';
export default class RequestBytesSign implements RequestSign {
inner: SignerPayloadJSON | SignerPayloadRaw;
constructor (inner: SignerPayloadRaw) {
this.inner = inner;
}
sign (_registry: TypeRegistry, pair: KeyringPair): { signature: string } {
const inner = this.inner as SignerPayloadRaw;
const signedBytes = pair.sign(hexToU8a(inner.data));
return { signature: u8aToHex(signedBytes) };
}
}
@@ -0,0 +1,22 @@
// Copyright 2019 @polkadot/extension authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { createType, TypeRegistry } from '@polkadot/types';
import { KeyringPair } from '@polkadot/keyring/types';
import { RequestSign } from './types';
import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
export default class RequestExtrinsicSign implements RequestSign {
inner: SignerPayloadJSON | SignerPayloadRaw;
constructor (inner: SignerPayloadJSON) {
this.inner = inner;
}
sign (registry: TypeRegistry, pair: KeyringPair): { signature: string } {
const inner = this.inner as SignerPayloadJSON;
const extrinsic = createType(registry, 'ExtrinsicPayload', this.inner, { version: inner.version });
return extrinsic.sign(pair);
}
}
@@ -8,7 +8,7 @@ import { AccountJson, AuthorizeRequest, MessageTypes, RequestAccountCreateExtern
import extension from 'extensionizer';
import keyring from '@polkadot/ui-keyring';
import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
import { TypeRegistry, createType } from '@polkadot/types';
import { TypeRegistry } from '@polkadot/types';
import { assert, isHex } from '@polkadot/util';
import { keyExtractSuri, mnemonicGenerate, mnemonicValidate } from '@polkadot/util-crypto';
@@ -153,7 +153,7 @@ export default class Extension {
assert(queued, 'Unable to find request');
const { request, resolve, reject } = queued;
const pair = keyring.getPair(request.address);
const pair = keyring.getPair(request.inner.address);
if (!pair) {
reject(new Error('Unable to find pair'));
@@ -162,10 +162,7 @@ export default class Extension {
}
pair.decodePkcs8(password);
const payload = createType(registry, 'ExtrinsicPayload', request, { version: request.version });
const result = payload.sign(pair);
const result = request.sign(registry, pair);
pair.lock();
resolve({
@@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AccountJson, AuthorizeRequest, RequestAuthorizeTab, RequestExtrinsicSign, ResponseExtrinsicSign, SigningRequest } from '../types';
import { AccountJson, AuthorizeRequest, RequestAuthorizeTab, RequestSign, ResponseSigning, SigningRequest } from '../types';
import extension from 'extensionizer';
import { BehaviorSubject } from 'rxjs';
@@ -28,8 +28,8 @@ type AuthUrls = Record<string, {
interface SignRequest {
account: AccountJson;
id: string;
request: RequestExtrinsicSign;
resolve: (result: ResponseExtrinsicSign) => void;
request: RequestSign;
resolve: (result: ResponseSigning) => void;
reject: (error: Error) => void;
url: string;
}
@@ -125,8 +125,8 @@ export default class State {
};
}
private signComplete = (id: string, fn: Function): (result: ResponseExtrinsicSign | Error) => void => {
return (result: ResponseExtrinsicSign | Error): void => {
private signComplete = (id: string, fn: Function): (result: ResponseSigning | Error) => void => {
return (result: ResponseSigning | Error): void => {
delete this._signRequests[id];
this.updateIconSign(true);
@@ -211,7 +211,7 @@ export default class State {
return this._signRequests[id];
}
public signQueue (url: string, request: RequestExtrinsicSign, account: AccountJson): Promise<ResponseExtrinsicSign> {
public sign (url: string, request: RequestSign, account: AccountJson): Promise<ResponseSigning> {
const id = getId();
return new Promise((resolve, reject): void => {
@@ -3,12 +3,16 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { InjectedAccount } from '@polkadot/extension-inject/types';
import { KeyringPair } from '@polkadot/keyring/types';
import { RequestAuthorizeTab, ResponseSigning, RequestTypes, ResponseTypes, MessageTypes } from '../types';
import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { RequestAuthorizeTab, RequestExtrinsicSign, ResponseExtrinsicSign, RequestTypes, ResponseTypes, MessageTypes } from '../types';
import keyring from '@polkadot/ui-keyring';
import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
import { assert } from '@polkadot/util';
import RequestBytesSign from '../RequestBytesSign';
import RequestExtrinsicSign from '../RequestExtrinsicSign';
import State from './State';
import { createSubscription, unsubscribe } from './subscriptions';
@@ -50,13 +54,22 @@ export default class Tabs {
return true;
}
private extrinsicSign (url: string, request: RequestExtrinsicSign): Promise<ResponseExtrinsicSign> {
const { address } = request;
private getSigningPair (address: string): KeyringPair {
const pair = keyring.getPair(address);
assert(pair, 'Unable to find keypair');
return pair;
}
return this.state.signQueue(url, request, { address, ...pair.meta });
private bytesSign (url: string, request: SignerPayloadRaw): Promise<ResponseSigning> {
const address = request.address;
const pair = this.getSigningPair(address);
return this.state.sign(url, new RequestBytesSign(request), { address, ...pair.meta });
}
private extrinsicSign (url: string, request: SignerPayloadJSON): Promise<ResponseSigning> {
const address = request.address;
const pair = this.getSigningPair(address);
return this.state.sign(url, new RequestExtrinsicSign(request), { address, ...pair.meta });
}
public async handle<TMessageType extends MessageTypes> (id: string, type: TMessageType, request: RequestTypes[TMessageType], url: string, port: chrome.runtime.Port): Promise<ResponseTypes[keyof ResponseTypes]> {
@@ -74,8 +87,11 @@ export default class Tabs {
case 'pub(accounts.subscribe)':
return this.accountsSubscribe(url, id, port);
case 'pub(bytes.sign)':
return this.bytesSign(url, request as SignerPayloadRaw);
case 'pub(extrinsic.sign)':
return this.extrinsicSign(url, request as RequestExtrinsicSign);
return this.extrinsicSign(url, request as SignerPayloadJSON);
default:
throw new Error(`Unable to handle message of type ${type}`);
+12 -6
View File
@@ -3,8 +3,10 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { InjectedAccount } from '@polkadot/extension-inject/types';
import { SignerPayloadJSON } from '@polkadot/types/types';
import { KeypairType } from '@polkadot/util-crypto/types';
import { KeyringPair } from '@polkadot/keyring/types';
import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
import { TypeRegistry } from '@polkadot/types';
type KeysWithDefinedValues<T> = {
[K in keyof T]: T[K] extends undefined ? never : K
@@ -36,7 +38,7 @@ export interface AuthorizeRequest {
export interface SigningRequest {
account: AccountJson;
id: string;
request: RequestExtrinsicSign;
request: RequestSign;
url: string;
}
@@ -63,7 +65,8 @@ export interface RequestSignatures {
'pub(accounts.list)': [RequestAccountList, InjectedAccount[]];
'pub(accounts.subscribe)': [RequestAccountSubscribe, boolean, InjectedAccount[]];
'pub(authorize.tab)': [RequestAuthorizeTab, null];
'pub(extrinsic.sign)': [RequestExtrinsicSign, ResponseExtrinsicSign];
'pub(bytes.sign)': [SignerPayloadRaw, ResponseSigning];
'pub(extrinsic.sign)': [SignerPayloadJSON, ResponseSigning];
}
export type MessageTypes = keyof RequestSignatures;
@@ -130,8 +133,6 @@ export type RequestAccountList = null;
export type RequestAccountSubscribe = null;
export type RequestExtrinsicSign = SignerPayloadJSON;
export interface RequestSigningApprovePassword {
id: string;
password: string;
@@ -184,7 +185,7 @@ export type TransportResponseMessage<TMessageType extends MessageTypes> =
? TransportResponseMessageSub<TMessageType>
: never;
export interface ResponseExtrinsicSign {
export interface ResponseSigning {
id: string;
signature: string;
}
@@ -211,3 +212,8 @@ export type SubscriptionMessageTypes = NoUndefinedValues<{
export type MessageTypesWithSubscriptions = keyof SubscriptionMessageTypes;
export type MessageTypesWithNoSubscriptions = Exclude<MessageTypes, keyof SubscriptionMessageTypes>
export interface RequestSign {
inner: SignerPayloadJSON | SignerPayloadRaw;
sign(registry: TypeRegistry, pair: KeyringPair): { signature: string };
}
+9 -10
View File
@@ -3,7 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { Signer as SignerInterface, SignerResult } from '@polkadot/api/types';
import { SignerPayloadJSON } from '@polkadot/types/types';
import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
import { SendRequest } from './types';
let sendRequest: SendRequest;
@@ -30,16 +30,15 @@ export default class Signer implements SignerInterface {
};
}
// TODO To implement signing of arbitrary payloads via signRaw
// public async signRaw (payload: SignerPayloadRaw): Promise<SignerResult> {
// const id = ++nextId;
// const result = await sendRequest('bytes.sign', payload);
public async signRaw (payload: SignerPayloadRaw): Promise<SignerResult> {
const id = ++nextId;
const result = await sendRequest('pub(bytes.sign)', payload);
// return {
// ...result,
// id
// };
// }
return {
...result,
id
};
}
// NOTE We don't listen to updates at all, if we do we can interpret the
// resuklt as provided by the API here