Basic QR tests (+ number encoding fix) (#168)

* Basic QR tests (+ number encoding fix)

* skipEncoding for Address display

* Fixup comments
This commit is contained in:
Jaco Greeff
2019-07-29 17:40:08 +02:00
committed by GitHub
parent 0ad431ba87
commit f7bd11a293
8 changed files with 148 additions and 69 deletions
+2 -1
View File
@@ -19,7 +19,8 @@
"clean": "polkadot-dev-clean-build",
"demo:identicon": "webpack-serve --config packages/ui-identicon/webpack.config.js --content packages/ui-identicon --port 3000",
"postinstall": "polkadot-dev-yarn-only",
"test": "jest --coverage"
"test": "jest --coverage",
"test:one": "jest"
},
"devDependencies": {
"@babel/core": "^7.5.5",
+7 -31
View File
@@ -7,16 +7,14 @@ import { BaseProps } from './types';
import React from 'react';
import qrcode from 'qrcode-generator';
import styled from 'styled-components';
import { u8aConcat } from '@polkadot/util';
import { xxhashAsHex } from '@polkadot/util-crypto';
import { createSize } from './constants';
import { encodeNumber, decodeString } from './util';
import { createFrames, createImgSize, decodeString } from './util';
interface Props extends BaseProps {
size?: number;
skipEncoding?: boolean;
value: Uint8Array;
withMulti?: boolean;
}
interface State {
@@ -28,8 +26,6 @@ interface State {
}
const FRAME_DELAY = 2100;
const FRAME_SIZE = 1716;
const MULTIPART = new Uint8Array([0]);
function getDataUrl (value: string): string {
const qr = qrcode(0, 'M');
@@ -40,26 +36,6 @@ function getDataUrl (value: string): string {
return qr.createDataURL(16, 0);
}
function createFrames (input: Uint8Array): string[] {
const frames = [];
let idx = 0;
while (idx < input.length) {
frames.push(input.subarray(idx, idx + FRAME_SIZE));
idx += FRAME_SIZE;
}
return frames.map((frame, index: number): string =>
decodeString(u8aConcat(
MULTIPART,
encodeNumber(frames.length),
encodeNumber(index),
frame
))
);
}
class Display extends React.PureComponent<Props, State> {
public state: State = {
frames: [],
@@ -69,16 +45,16 @@ class Display extends React.PureComponent<Props, State> {
valueHash: null
};
public static getDerivedStateFromProps ({ value, withMulti = true }: Props, prevState: State): Pick<State, never> | null {
public static getDerivedStateFromProps ({ value, skipEncoding = false }: Props, prevState: State): Pick<State, never> | null {
const valueHash = xxhashAsHex(value);
if (valueHash === prevState.valueHash) {
return null;
}
const frames: string[] = withMulti
? createFrames(value)
: [decodeString(value)];
const frames: string[] = skipEncoding
? [decodeString(value)]
: createFrames(value);
return {
frames,
@@ -113,7 +89,7 @@ class Display extends React.PureComponent<Props, State> {
return (
<div
className={className}
style={createSize(size)}
style={createImgSize(size)}
>
<div
className='ui--qr-Display'
+3 -9
View File
@@ -5,11 +5,9 @@
import { BaseProps } from './types';
import React from 'react';
import { u8aConcat } from '@polkadot/util';
import { xxhashAsHex } from '@polkadot/util-crypto';
import { ADDRESS_PREFIX } from './constants';
import { encodeString } from './util';
import { createAddressPayload } from './util';
import QrDisplay from './Display';
interface Props extends BaseProps {
@@ -21,8 +19,6 @@ interface State {
dataHash: string | null;
}
const PREFIX = encodeString(ADDRESS_PREFIX);
export default class DisplayExtrinsic extends React.PureComponent<Props, State> {
public state: State = {
data: null,
@@ -30,10 +26,7 @@ export default class DisplayExtrinsic extends React.PureComponent<Props, State>
};
public static getDerivedStateFromProps ({ address }: Props, prevState: State): State | null {
const data = u8aConcat(
PREFIX,
encodeString(address)
);
const data = createAddressPayload(address);
const dataHash = xxhashAsHex(data);
if (dataHash === prevState.dataHash) {
@@ -54,6 +47,7 @@ export default class DisplayExtrinsic extends React.PureComponent<Props, State>
return (
<QrDisplay
className={className}
skipEncoding={true}
style={style}
value={data}
/>
+3 -13
View File
@@ -5,9 +5,9 @@
import { BaseProps } from './types';
import React from 'react';
import { u8aConcat } from '@polkadot/util';
import { decodeAddress, xxhashAsHex } from '@polkadot/util-crypto';
import { xxhashAsHex } from '@polkadot/util-crypto';
import { createSignPayload } from './util';
import QrDisplay from './Display';
interface Props extends BaseProps {
@@ -20,10 +20,6 @@ interface State {
dataHash: string | null;
}
const SUBSTRATE = new Uint8Array([0x53]);
const CRYPTO_SR25519 = new Uint8Array([0x01]);
const SIGN_TX = new Uint8Array([0x00]);
export default class DisplayPayload extends React.PureComponent<Props, State> {
public state: State = {
data: null,
@@ -31,13 +27,7 @@ export default class DisplayPayload extends React.PureComponent<Props, State> {
};
public static getDerivedStateFromProps ({ address, payload }: Props, prevState: State): State | null {
const data = u8aConcat(
SUBSTRATE,
CRYPTO_SR25519,
SIGN_TX,
decodeAddress(address),
payload
);
const data = createSignPayload(address, payload);
const dataHash = xxhashAsHex(data);
if (dataHash === prevState.dataHash) {
+2 -2
View File
@@ -8,7 +8,7 @@ import React from 'react';
import Reader from 'react-qr-reader';
import styled from 'styled-components';
import { createSize } from './constants';
import { createImgSize } from './util';
interface Props extends BaseProps {
delay?: number;
@@ -29,7 +29,7 @@ class Scan extends React.PureComponent<Props> {
return (
<div
className={className}
style={createSize(size)}
style={createImgSize(size)}
>
<Reader
className='ui--qr-Scan'
+10 -12
View File
@@ -2,20 +2,18 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
const DEFAULT_SIZE = 300;
const DEFAULT_IMG_SIZE = 300;
const ADDRESS_PREFIX = 'substrate:';
function createSize (size: number = DEFAULT_SIZE): Record<string, string> {
const height = `${size}px`;
return {
height,
width: height
};
}
const FRAME_SIZE = 1716;
const SUBSTRATE_ID = new Uint8Array([0x53]);
const CRYPTO_SR25519 = new Uint8Array([0x01]);
const CMD_SIGN_TX = new Uint8Array([0x00]);
export {
ADDRESS_PREFIX,
DEFAULT_SIZE,
createSize
CMD_SIGN_TX,
CRYPTO_SR25519,
DEFAULT_IMG_SIZE,
FRAME_SIZE,
SUBSTRATE_ID
};
+67
View File
@@ -0,0 +1,67 @@
// Copyright 2017-2019 @polkadot/react-qr 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 { u8aToHex, u8aToString } from '@polkadot/util';
import { createAddressPayload, createSignPayload, createFrames, encodeNumber, encodeString } from './util';
describe('util', (): void => {
describe('encodeNumber', (): void => {
it('encodes 1 correctly', (): void => {
expect(
encodeNumber(1)
).toEqual(new Uint8Array([0, 1]));
});
it('encodes 257 correctly', (): void => {
expect(
encodeNumber(257)
).toEqual(new Uint8Array([1, 1]));
});
});
describe('createAddressPayload', (): void => {
it('encodes an address properly', (): void => {
expect(
u8aToString(
createAddressPayload('5GKhfyctwmW5LQdGaHTyU9qq2yDtggdJo719bj5ZUxnVGtmX')
)
).toEqual('substrate:5GKhfyctwmW5LQdGaHTyU9qq2yDtggdJo719bj5ZUxnVGtmX');
});
});
describe('createSignPayload', (): void => {
it('encodes a payload properly', (): void => {
expect(
u8aToHex(
createSignPayload('5HbgaJEuVN5qGbkhgtuDQANivSWwHXWsC2erP1SQUXgciTVq', 'THIS IS SPARTA!')
)
).toEqual(
'0x' + // prefix
'53' + // substrate
'01' + // sr25519
'00' + // sign tx
'f4cd755672a8f9542ca9da4fbf2182e79135d94304002e6a09ffc96fef6e6c4c' + // publickey
'544849532049532053504152544121' // THIS IS SPARTA!
);
});
});
describe('createFrames', (): void => {
it('encodes frames properly', (): void => {
expect(
createFrames(
createSignPayload('5HbgaJEuVN5qGbkhgtuDQANivSWwHXWsC2erP1SQUXgciTVq', '0x12345678')
).map((str): string => u8aToHex(encodeString(str)))
).toEqual([
'0x' +
'00' + // multipart
'0001' + // length
'0000' + // index
'530100' + // payload info, substrate + sr25519 + signtx
'f4cd755672a8f9542ca9da4fbf2182e79135d94304002e6a09ffc96fef6e6c4c' + // publicKey
'12345678' // data
]);
});
});
});
+54 -1
View File
@@ -2,8 +2,15 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { u8aConcat, u8aToU8a } from '@polkadot/util';
import { decodeAddress } from '@polkadot/util-crypto';
import { ADDRESS_PREFIX, CMD_SIGN_TX, CRYPTO_SR25519, DEFAULT_IMG_SIZE, FRAME_SIZE, SUBSTRATE_ID } from './constants';
const MULTIPART = new Uint8Array([0]);
export function encodeNumber (value: number): Uint8Array {
return new Uint8Array([value >> 8, value & 256]);
return new Uint8Array([value >> 8, value & 0xff]);
}
export function encodeString (value: string): Uint8Array {
@@ -21,3 +28,49 @@ export function decodeString (value: Uint8Array): string {
return str + String.fromCharCode(code);
}, '');
}
export function createAddressPayload (address: string): Uint8Array {
return u8aConcat(
encodeString(ADDRESS_PREFIX),
encodeString(address)
);
}
export function createSignPayload (address: string, payload: string | Uint8Array): Uint8Array {
return u8aConcat(
SUBSTRATE_ID,
CRYPTO_SR25519,
CMD_SIGN_TX,
decodeAddress(address),
u8aToU8a(payload)
);
}
export function createFrames (input: Uint8Array): string[] {
const frames = [];
let idx = 0;
while (idx < input.length) {
frames.push(input.subarray(idx, idx + FRAME_SIZE));
idx += FRAME_SIZE;
}
return frames.map((frame, index: number): string =>
decodeString(u8aConcat(
MULTIPART,
encodeNumber(frames.length),
encodeNumber(index),
frame
))
);
}
export function createImgSize (size: number = DEFAULT_IMG_SIZE): Record<string, string> {
const height = `${size}px`;
return {
height,
width: height
};
}