diff --git a/packages/extension-ui/src/Popup/Signing/Bytes.tsx b/packages/extension-ui/src/Popup/Signing/Bytes.tsx new file mode 100644 index 0000000000..0782fd23e3 --- /dev/null +++ b/packages/extension-ui/src/Popup/Signing/Bytes.tsx @@ -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 { + return ( + + + + + + + + + + + +
from{url}
bytes{bytes}
+ ); +} + +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; + } +`; diff --git a/packages/extension-ui/src/Popup/Signing/Details.tsx b/packages/extension-ui/src/Popup/Signing/Extrinsic.tsx similarity index 95% rename from packages/extension-ui/src/Popup/Signing/Details.tsx rename to packages/extension-ui/src/Popup/Signing/Extrinsic.tsx index 543668a554..e112a76d5f 100644 --- a/packages/extension-ui/src/Popup/Signing/Details.tsx +++ b/packages/extension-ui/src/Popup/Signing/Extrinsic.tsx @@ -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 { +function Extrinsic ({ className, isDecoded, payload: { era, nonce, tip }, request: { blockNumber, genesisHash, method, specVersion: hexSpec }, url }: Props): React.ReactElement { const chain = useRef(findChain(genesisHash)).current; const specVersion = useRef(bnToBn(hexSpec)).current; const [decoded, setDecoded] = useState({ 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; diff --git a/packages/extension-ui/src/Popup/Signing/Request.tsx b/packages/extension-ui/src/Popup/Signing/Request.tsx index ec6952b3c9..2032f936db 100644 --- a/packages/extension-ui/src/Popup/Signing/Request.tsx +++ b/packages/extension-ui/src/Popup/Signing/Request.tsx @@ -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 | null { const onAction = useContext(ActionContext); - const [payload, setPayload] = useState(null); + const [hexBytes, setHexBytes] = useState(null); + const [extrinsic, setExtrinsic] = useState(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 => 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 ( -
- {isExternal && isFirst - ? - :
- } - {isFirst && !isExternal && } - -
- Cancel - -
- ); + if (extrinsic !== null) { + const payload = request.inner as SignerPayloadJSON; + return ( +
+ {isExternal && isFirst + ? + : + } + {isFirst && !isExternal && } + +
+ Cancel + +
+ ); + } else if (hexBytes !== null) { + const payload = request.inner as SignerPayloadRaw; + return ( +
+ + {isFirst && !isExternal && } + +
+ Cancel + +
+ ); + } else { + return null; + } } diff --git a/packages/extension/src/background/RequestBytesSign.ts b/packages/extension/src/background/RequestBytesSign.ts new file mode 100644 index 0000000000..031306c401 --- /dev/null +++ b/packages/extension/src/background/RequestBytesSign.ts @@ -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) }; + } +} diff --git a/packages/extension/src/background/RequestExtrinsicSign.ts b/packages/extension/src/background/RequestExtrinsicSign.ts new file mode 100644 index 0000000000..93b8279b93 --- /dev/null +++ b/packages/extension/src/background/RequestExtrinsicSign.ts @@ -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); + } +} diff --git a/packages/extension/src/background/handlers/Extension.ts b/packages/extension/src/background/handlers/Extension.ts index 3c19e4502b..8f614bc47e 100644 --- a/packages/extension/src/background/handlers/Extension.ts +++ b/packages/extension/src/background/handlers/Extension.ts @@ -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({ diff --git a/packages/extension/src/background/handlers/State.ts b/packages/extension/src/background/handlers/State.ts index dc2d927009..e760607914 100644 --- a/packages/extension/src/background/handlers/State.ts +++ b/packages/extension/src/background/handlers/State.ts @@ -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 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 { + public sign (url: string, request: RequestSign, account: AccountJson): Promise { const id = getId(); return new Promise((resolve, reject): void => { diff --git a/packages/extension/src/background/handlers/Tabs.ts b/packages/extension/src/background/handlers/Tabs.ts index b7d8212b05..d11ac46c9c 100644 --- a/packages/extension/src/background/handlers/Tabs.ts +++ b/packages/extension/src/background/handlers/Tabs.ts @@ -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 { - 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 { + 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 { + const address = request.address; + const pair = this.getSigningPair(address); + return this.state.sign(url, new RequestExtrinsicSign(request), { address, ...pair.meta }); } public async handle (id: string, type: TMessageType, request: RequestTypes[TMessageType], url: string, port: chrome.runtime.Port): Promise { @@ -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}`); diff --git a/packages/extension/src/background/types.ts b/packages/extension/src/background/types.ts index c2448dc693..e1ae236930 100644 --- a/packages/extension/src/background/types.ts +++ b/packages/extension/src/background/types.ts @@ -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 = { [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 = ? TransportResponseMessageSub : 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 + +export interface RequestSign { + inner: SignerPayloadJSON | SignerPayloadRaw; + sign(registry: TypeRegistry, pair: KeyringPair): { signature: string }; +} diff --git a/packages/extension/src/page/Signer.ts b/packages/extension/src/page/Signer.ts index ebfa24fc7f..24f7f8db6a 100644 --- a/packages/extension/src/page/Signer.ts +++ b/packages/extension/src/page/Signer.ts @@ -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 { - // const id = ++nextId; - // const result = await sendRequest('bytes.sign', payload); + public async signRaw (payload: SignerPayloadRaw): Promise { + 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