mirror of
https://github.com/pezkuwichain/pezkuwi-ui.git
synced 2026-06-20 01:11:10 +00:00
Basic QR tests (+ number encoding fix) (#168)
* Basic QR tests (+ number encoding fix) * skipEncoding for Address display * Fixup comments
This commit is contained in:
+2
-1
@@ -19,7 +19,8 @@
|
|||||||
"clean": "polkadot-dev-clean-build",
|
"clean": "polkadot-dev-clean-build",
|
||||||
"demo:identicon": "webpack-serve --config packages/ui-identicon/webpack.config.js --content packages/ui-identicon --port 3000",
|
"demo:identicon": "webpack-serve --config packages/ui-identicon/webpack.config.js --content packages/ui-identicon --port 3000",
|
||||||
"postinstall": "polkadot-dev-yarn-only",
|
"postinstall": "polkadot-dev-yarn-only",
|
||||||
"test": "jest --coverage"
|
"test": "jest --coverage",
|
||||||
|
"test:one": "jest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.5.5",
|
"@babel/core": "^7.5.5",
|
||||||
|
|||||||
@@ -7,16 +7,14 @@ import { BaseProps } from './types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import qrcode from 'qrcode-generator';
|
import qrcode from 'qrcode-generator';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { u8aConcat } from '@polkadot/util';
|
|
||||||
import { xxhashAsHex } from '@polkadot/util-crypto';
|
import { xxhashAsHex } from '@polkadot/util-crypto';
|
||||||
|
|
||||||
import { createSize } from './constants';
|
import { createFrames, createImgSize, decodeString } from './util';
|
||||||
import { encodeNumber, decodeString } from './util';
|
|
||||||
|
|
||||||
interface Props extends BaseProps {
|
interface Props extends BaseProps {
|
||||||
size?: number;
|
size?: number;
|
||||||
|
skipEncoding?: boolean;
|
||||||
value: Uint8Array;
|
value: Uint8Array;
|
||||||
withMulti?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@@ -28,8 +26,6 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const FRAME_DELAY = 2100;
|
const FRAME_DELAY = 2100;
|
||||||
const FRAME_SIZE = 1716;
|
|
||||||
const MULTIPART = new Uint8Array([0]);
|
|
||||||
|
|
||||||
function getDataUrl (value: string): string {
|
function getDataUrl (value: string): string {
|
||||||
const qr = qrcode(0, 'M');
|
const qr = qrcode(0, 'M');
|
||||||
@@ -40,26 +36,6 @@ function getDataUrl (value: string): string {
|
|||||||
return qr.createDataURL(16, 0);
|
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> {
|
class Display extends React.PureComponent<Props, State> {
|
||||||
public state: State = {
|
public state: State = {
|
||||||
frames: [],
|
frames: [],
|
||||||
@@ -69,16 +45,16 @@ class Display extends React.PureComponent<Props, State> {
|
|||||||
valueHash: null
|
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);
|
const valueHash = xxhashAsHex(value);
|
||||||
|
|
||||||
if (valueHash === prevState.valueHash) {
|
if (valueHash === prevState.valueHash) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const frames: string[] = withMulti
|
const frames: string[] = skipEncoding
|
||||||
? createFrames(value)
|
? [decodeString(value)]
|
||||||
: [decodeString(value)];
|
: createFrames(value);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
frames,
|
frames,
|
||||||
@@ -113,7 +89,7 @@ class Display extends React.PureComponent<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className}
|
className={className}
|
||||||
style={createSize(size)}
|
style={createImgSize(size)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className='ui--qr-Display'
|
className='ui--qr-Display'
|
||||||
|
|||||||
@@ -5,11 +5,9 @@
|
|||||||
import { BaseProps } from './types';
|
import { BaseProps } from './types';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { u8aConcat } from '@polkadot/util';
|
|
||||||
import { xxhashAsHex } from '@polkadot/util-crypto';
|
import { xxhashAsHex } from '@polkadot/util-crypto';
|
||||||
|
|
||||||
import { ADDRESS_PREFIX } from './constants';
|
import { createAddressPayload } from './util';
|
||||||
import { encodeString } from './util';
|
|
||||||
import QrDisplay from './Display';
|
import QrDisplay from './Display';
|
||||||
|
|
||||||
interface Props extends BaseProps {
|
interface Props extends BaseProps {
|
||||||
@@ -21,8 +19,6 @@ interface State {
|
|||||||
dataHash: string | null;
|
dataHash: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PREFIX = encodeString(ADDRESS_PREFIX);
|
|
||||||
|
|
||||||
export default class DisplayExtrinsic extends React.PureComponent<Props, State> {
|
export default class DisplayExtrinsic extends React.PureComponent<Props, State> {
|
||||||
public state: State = {
|
public state: State = {
|
||||||
data: null,
|
data: null,
|
||||||
@@ -30,10 +26,7 @@ export default class DisplayExtrinsic extends React.PureComponent<Props, State>
|
|||||||
};
|
};
|
||||||
|
|
||||||
public static getDerivedStateFromProps ({ address }: Props, prevState: State): State | null {
|
public static getDerivedStateFromProps ({ address }: Props, prevState: State): State | null {
|
||||||
const data = u8aConcat(
|
const data = createAddressPayload(address);
|
||||||
PREFIX,
|
|
||||||
encodeString(address)
|
|
||||||
);
|
|
||||||
const dataHash = xxhashAsHex(data);
|
const dataHash = xxhashAsHex(data);
|
||||||
|
|
||||||
if (dataHash === prevState.dataHash) {
|
if (dataHash === prevState.dataHash) {
|
||||||
@@ -54,6 +47,7 @@ export default class DisplayExtrinsic extends React.PureComponent<Props, State>
|
|||||||
return (
|
return (
|
||||||
<QrDisplay
|
<QrDisplay
|
||||||
className={className}
|
className={className}
|
||||||
|
skipEncoding={true}
|
||||||
style={style}
|
style={style}
|
||||||
value={data}
|
value={data}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
import { BaseProps } from './types';
|
import { BaseProps } from './types';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { u8aConcat } from '@polkadot/util';
|
import { xxhashAsHex } from '@polkadot/util-crypto';
|
||||||
import { decodeAddress, xxhashAsHex } from '@polkadot/util-crypto';
|
|
||||||
|
|
||||||
|
import { createSignPayload } from './util';
|
||||||
import QrDisplay from './Display';
|
import QrDisplay from './Display';
|
||||||
|
|
||||||
interface Props extends BaseProps {
|
interface Props extends BaseProps {
|
||||||
@@ -20,10 +20,6 @@ interface State {
|
|||||||
dataHash: string | null;
|
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> {
|
export default class DisplayPayload extends React.PureComponent<Props, State> {
|
||||||
public state: State = {
|
public state: State = {
|
||||||
data: null,
|
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 {
|
public static getDerivedStateFromProps ({ address, payload }: Props, prevState: State): State | null {
|
||||||
const data = u8aConcat(
|
const data = createSignPayload(address, payload);
|
||||||
SUBSTRATE,
|
|
||||||
CRYPTO_SR25519,
|
|
||||||
SIGN_TX,
|
|
||||||
decodeAddress(address),
|
|
||||||
payload
|
|
||||||
);
|
|
||||||
const dataHash = xxhashAsHex(data);
|
const dataHash = xxhashAsHex(data);
|
||||||
|
|
||||||
if (dataHash === prevState.dataHash) {
|
if (dataHash === prevState.dataHash) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import React from 'react';
|
|||||||
import Reader from 'react-qr-reader';
|
import Reader from 'react-qr-reader';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { createSize } from './constants';
|
import { createImgSize } from './util';
|
||||||
|
|
||||||
interface Props extends BaseProps {
|
interface Props extends BaseProps {
|
||||||
delay?: number;
|
delay?: number;
|
||||||
@@ -29,7 +29,7 @@ class Scan extends React.PureComponent<Props> {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className}
|
className={className}
|
||||||
style={createSize(size)}
|
style={createImgSize(size)}
|
||||||
>
|
>
|
||||||
<Reader
|
<Reader
|
||||||
className='ui--qr-Scan'
|
className='ui--qr-Scan'
|
||||||
|
|||||||
@@ -2,20 +2,18 @@
|
|||||||
// This software may be modified and distributed under the terms
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// 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:';
|
const ADDRESS_PREFIX = 'substrate:';
|
||||||
|
const FRAME_SIZE = 1716;
|
||||||
function createSize (size: number = DEFAULT_SIZE): Record<string, string> {
|
const SUBSTRATE_ID = new Uint8Array([0x53]);
|
||||||
const height = `${size}px`;
|
const CRYPTO_SR25519 = new Uint8Array([0x01]);
|
||||||
|
const CMD_SIGN_TX = new Uint8Array([0x00]);
|
||||||
return {
|
|
||||||
height,
|
|
||||||
width: height
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ADDRESS_PREFIX,
|
ADDRESS_PREFIX,
|
||||||
DEFAULT_SIZE,
|
CMD_SIGN_TX,
|
||||||
createSize
|
CRYPTO_SR25519,
|
||||||
|
DEFAULT_IMG_SIZE,
|
||||||
|
FRAME_SIZE,
|
||||||
|
SUBSTRATE_ID
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,8 +2,15 @@
|
|||||||
// This software may be modified and distributed under the terms
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// 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 {
|
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 {
|
export function encodeString (value: string): Uint8Array {
|
||||||
@@ -21,3 +28,49 @@ export function decodeString (value: Uint8Array): string {
|
|||||||
return str + String.fromCharCode(code);
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user