Implement signRaw (#196)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
`;
|
||||
+2
-2
@@ -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}`);
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user