diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f563c5a..aa65f9d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
# CHANGELOG
+## master
+
+Contributed:
+
+- Support signing of raw data via Qr (Thanks to https://github.com/Tbaut)
+
+Changes:
+
+- Raw signing interfaces will now always place a `...` wrapper around signed data
+
+
## 0.38.3 May 31, 2021
Contributed:
diff --git a/packages/extension-base/package.json b/packages/extension-base/package.json
index 728cd935..7a8f07eb 100644
--- a/packages/extension-base/package.json
+++ b/packages/extension-base/package.json
@@ -10,6 +10,7 @@
"dependencies": {
"@babel/runtime": "^7.14.0",
"@polkadot/api": "^4.12.2-6",
+ "@polkadot/extension-dapp": "^0.38.4-18",
"@polkadot/extension-inject": "^0.38.4-18",
"@polkadot/keyring": "^6.6.1",
"@polkadot/phishing": "^0.6.171",
diff --git a/packages/extension-base/src/background/RequestBytesSign.ts b/packages/extension-base/src/background/RequestBytesSign.ts
index 850917a2..e0a90614 100644
--- a/packages/extension-base/src/background/RequestBytesSign.ts
+++ b/packages/extension-base/src/background/RequestBytesSign.ts
@@ -5,8 +5,9 @@ import type { KeyringPair } from '@polkadot/keyring/types';
import type { SignerPayloadRaw } from '@polkadot/types/types';
import type { RequestSign } from './types';
+import { wrapBytes } from '@polkadot/extension-dapp/wrapBytes';
import { TypeRegistry } from '@polkadot/types';
-import { hexToU8a, u8aToHex } from '@polkadot/util';
+import { u8aToHex } from '@polkadot/util';
export default class RequestBytesSign implements RequestSign {
public readonly payload: SignerPayloadRaw;
@@ -19,7 +20,7 @@ export default class RequestBytesSign implements RequestSign {
return {
signature: u8aToHex(
pair.sign(
- hexToU8a(this.payload.data)
+ wrapBytes(this.payload.data)
)
)
};
diff --git a/packages/extension-base/src/utils/canDerive.ts b/packages/extension-base/src/utils/canDerive.ts
index ee17be19..5bae4031 100644
--- a/packages/extension-base/src/utils/canDerive.ts
+++ b/packages/extension-base/src/utils/canDerive.ts
@@ -3,6 +3,6 @@
import type { KeypairType } from '@polkadot/util-crypto/types';
-export default function canDerive (type?: KeypairType): boolean {
+export function canDerive (type?: KeypairType): boolean {
return !!type && ['ed25519', 'sr25519', 'ecdsa'].includes(type);
}
diff --git a/packages/extension-base/src/utils/index.ts b/packages/extension-base/src/utils/index.ts
index 7bd8f407..a2968738 100644
--- a/packages/extension-base/src/utils/index.ts
+++ b/packages/extension-base/src/utils/index.ts
@@ -1,4 +1,4 @@
// Copyright 2019-2021 @polkadot/extension authors & contributors
// SPDX-License-Identifier: Apache-2.0
-export { default as canDerive } from './canDerive';
+export { canDerive } from './canDerive';
diff --git a/packages/extension-dapp/src/index.ts b/packages/extension-dapp/src/index.ts
index 8b856577..65016cc9 100644
--- a/packages/extension-dapp/src/index.ts
+++ b/packages/extension-dapp/src/index.ts
@@ -8,6 +8,9 @@ import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';
import { documentReadyPromise } from './util';
+// expose utility functions
+export { unwrapBytes, wrapBytes } from './wrapBytes';
+
// just a helper (otherwise we cast all-over, so shorter and more readable)
const win = window as Window & InjectedWindow;
diff --git a/packages/extension-dapp/src/wrapBytes.spec.ts b/packages/extension-dapp/src/wrapBytes.spec.ts
new file mode 100644
index 00000000..1af4bcd0
--- /dev/null
+++ b/packages/extension-dapp/src/wrapBytes.spec.ts
@@ -0,0 +1,114 @@
+// Copyright 2019-2021 @polkadot/extension authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import { u8aConcat, u8aEq, u8aToString } from '@polkadot/util';
+
+import { POSTFIX, PREFIX, unwrapBytes, wrapBytes } from './wrapBytes';
+
+const TEST_DATA = 'this is just some random message that we expect to be wrapped along the way';
+const TEST_WRAP_EMPTY = `${u8aToString(PREFIX)}${u8aToString(POSTFIX)}`;
+const TEST_WRAP_FULL = `${u8aToString(PREFIX)}${TEST_DATA}${u8aToString(POSTFIX)}`;
+const TEST_WARP_HALF_PRE = `${u8aToString(PREFIX)}${TEST_DATA}`;
+const TEST_WRAP_HALF_POST = `${TEST_DATA}${u8aToString(POSTFIX)}`;
+
+describe('wrapBytes', (): void => {
+ it('wraps empty bytes', (): void => {
+ expect(
+ u8aEq(
+ wrapBytes(new Uint8Array()),
+ u8aConcat(PREFIX, POSTFIX)
+ )
+ ).toBe(true);
+ });
+
+ it('wraps when no wrapping is detected', (): void => {
+ expect(
+ u8aToString(
+ wrapBytes(TEST_DATA)
+ )
+ ).toEqual(TEST_WRAP_FULL);
+ });
+
+ it('wraps when only start wrap is detected', (): void => {
+ expect(
+ u8aToString(
+ wrapBytes(TEST_WARP_HALF_PRE)
+ )
+ ).toEqual(`${u8aToString(PREFIX)}${TEST_WARP_HALF_PRE}${u8aToString(POSTFIX)}`);
+ });
+
+ it('wraps when only end wrap is detected', (): void => {
+ expect(
+ u8aToString(
+ wrapBytes(TEST_WRAP_HALF_POST)
+ )
+ ).toEqual(`${u8aToString(PREFIX)}${TEST_WRAP_HALF_POST}${u8aToString(POSTFIX)}`);
+ });
+
+ it('does not re-wrap when a wrap is already present', (): void => {
+ expect(
+ u8aToString(
+ wrapBytes(TEST_WRAP_FULL)
+ )
+ ).toEqual(TEST_WRAP_FULL);
+ });
+
+ it('does not re-wrap when a wrap (empty data) is already present', (): void => {
+ expect(
+ u8aToString(
+ wrapBytes(TEST_WRAP_EMPTY)
+ )
+ ).toEqual(TEST_WRAP_EMPTY);
+ });
+});
+
+describe('unwrapBytes', (): void => {
+ it('unwraps empty bytes', (): void => {
+ expect(
+ u8aEq(
+ unwrapBytes(new Uint8Array()),
+ new Uint8Array()
+ )
+ ).toBe(true);
+ });
+
+ it('unwraps when no wrapping is detected', (): void => {
+ expect(
+ u8aToString(
+ unwrapBytes(TEST_DATA)
+ )
+ ).toEqual(TEST_DATA);
+ });
+
+ it('unwraps when no wrapping is detected (only start)', (): void => {
+ expect(
+ u8aToString(
+ unwrapBytes(TEST_WARP_HALF_PRE)
+ )
+ ).toEqual(TEST_WARP_HALF_PRE);
+ });
+
+ it('unwraps when no wrapping is detected (only end)', (): void => {
+ expect(
+ u8aToString(
+ unwrapBytes(TEST_WRAP_HALF_POST)
+ )
+ ).toEqual(TEST_WRAP_HALF_POST);
+ });
+
+ it('unwraps when a wrap is present', (): void => {
+ expect(
+ u8aToString(
+ unwrapBytes(TEST_WRAP_FULL)
+ )
+ ).toEqual(TEST_DATA);
+ });
+
+ it('unwraps when a an empty wrap is present', (): void => {
+ expect(
+ u8aToString(
+ unwrapBytes(TEST_WRAP_EMPTY)
+ )
+ ).toEqual('');
+ });
+});
diff --git a/packages/extension-dapp/src/wrapBytes.ts b/packages/extension-dapp/src/wrapBytes.ts
new file mode 100644
index 00000000..7ce606f2
--- /dev/null
+++ b/packages/extension-dapp/src/wrapBytes.ts
@@ -0,0 +1,31 @@
+// Copyright 2019-2021 @polkadot/extension authors & contributors
+// SPDX-License-Identifier: Apache-2.0
+
+import { u8aConcat, u8aEq, u8aToU8a } from '@polkadot/util';
+
+export const PREFIX = u8aToU8a('');
+export const POSTFIX = u8aToU8a('');
+
+const WRAP_LEN = PREFIX.length + POSTFIX.length;
+
+function isWrapped (u8a: Uint8Array): boolean {
+ return u8a.length >= WRAP_LEN &&
+ u8aEq(u8a.subarray(0, PREFIX.length), PREFIX) &&
+ u8aEq(u8a.slice(-POSTFIX.length), POSTFIX);
+}
+
+export function unwrapBytes (bytes: string | Uint8Array): Uint8Array {
+ const u8a = u8aToU8a(bytes);
+
+ return isWrapped(u8a)
+ ? u8a.subarray(PREFIX.length, u8a.length - POSTFIX.length)
+ : u8a;
+}
+
+export function wrapBytes (bytes: string | Uint8Array): Uint8Array {
+ const u8a = u8aToU8a(bytes);
+
+ return isWrapped(u8a)
+ ? u8a
+ : u8aConcat(PREFIX, u8a, POSTFIX);
+}
diff --git a/packages/extension-ui/package.json b/packages/extension-ui/package.json
index bcc005c8..2f69b92e 100644
--- a/packages/extension-ui/package.json
+++ b/packages/extension-ui/package.json
@@ -13,6 +13,7 @@
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/react-fontawesome": "^0.1.14",
"@polkadot/extension-base": "^0.38.4-18",
+ "@polkadot/extension-dapp": "^0.38.4-18",
"@polkadot/extension-inject": "^0.38.4-18",
"@polkadot/hw-ledger": "^6.6.1",
"@polkadot/keyring": "^6.6.1",
diff --git a/packages/extension-ui/src/Popup/Signing/Qr.tsx b/packages/extension-ui/src/Popup/Signing/Qr.tsx
index e67223e4..f592454f 100644
--- a/packages/extension-ui/src/Popup/Signing/Qr.tsx
+++ b/packages/extension-ui/src/Popup/Signing/Qr.tsx
@@ -6,8 +6,8 @@ import type { ExtrinsicPayload } from '@polkadot/types/interfaces';
import React, { useCallback, useMemo, useState } from 'react';
import styled from 'styled-components';
+import { wrapBytes } from '@polkadot/extension-dapp/wrapBytes';
import { QrDisplayPayload, QrScanSignature } from '@polkadot/react-qr';
-import { hexToU8a } from '@polkadot/util';
import { Button } from '../../components';
import useTranslation from '../../hooks/useTranslation';
@@ -34,7 +34,7 @@ function Qr ({ address, className, cmd, genesisHash, onSignature, payload }: Pro
case CMD_MORTAL:
return (payload as ExtrinsicPayload).toU8a();
case CMD_SIGN_MESSAGE:
- return hexToU8a(payload as string);
+ return wrapBytes(payload as string);
default:
return null;
}
diff --git a/packages/extension/webpack.shared.cjs b/packages/extension/webpack.shared.cjs
index 5d5b4c50..ba825dac 100644
--- a/packages/extension/webpack.shared.cjs
+++ b/packages/extension/webpack.shared.cjs
@@ -14,6 +14,7 @@ const packages = [
'extension',
'extension-base',
'extension-chains',
+ 'extension-dapp',
'extension-inject',
'extension-ui'
];
diff --git a/yarn.lock b/yarn.lock
index 787fd531..b3d83b14 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2143,6 +2143,7 @@ __metadata:
dependencies:
"@babel/runtime": ^7.14.0
"@polkadot/api": ^4.12.2-6
+ "@polkadot/extension-dapp": ^0.38.4-18
"@polkadot/extension-inject": ^0.38.4-18
"@polkadot/keyring": ^6.6.1
"@polkadot/phishing": ^0.6.171
@@ -2161,7 +2162,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@polkadot/extension-dapp@workspace:packages/extension-dapp":
+"@polkadot/extension-dapp@^0.38.4-18, @polkadot/extension-dapp@workspace:packages/extension-dapp":
version: 0.0.0-use.local
resolution: "@polkadot/extension-dapp@workspace:packages/extension-dapp"
dependencies:
@@ -2199,6 +2200,7 @@ __metadata:
"@fortawesome/free-solid-svg-icons": ^5.15.3
"@fortawesome/react-fontawesome": ^0.1.14
"@polkadot/extension-base": ^0.38.4-18
+ "@polkadot/extension-dapp": ^0.38.4-18
"@polkadot/extension-inject": ^0.38.4-18
"@polkadot/hw-ledger": ^6.6.1
"@polkadot/keyring": ^6.6.1