ghost-portal/packages/app-toolbox/src/Verify.tsx
Jaco Greeff f4f20575c3
Support sr25519 signatures (toolbox verify) (#1131)
* Support sr25519 signatures (toolbox verify)

* Rework verify layout

* Update packages/app-toolbox/src/Verify.tsx

Co-Authored-By: Thibaut Sardan <33178835+Tbaut@users.noreply.github.com>
2019-05-10 19:12:00 +02:00

228 lines
6.6 KiB
TypeScript

// Copyright 2017-2019 @polkadot/app-toolbox 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 { I18nProps as Props } from '@polkadot/ui-app/types';
import { KeypairType } from '@polkadot/util-crypto/types';
import React from 'react';
import { Dropdown, Icon, Input, InputAddress, Static } from '@polkadot/ui-app';
import keyring from '@polkadot/ui-keyring';
import uiSettings from '@polkadot/ui-settings';
import { isHex } from '@polkadot/util';
import { naclVerify, schnorrkelVerify } from '@polkadot/util-crypto';
import translate from './translate';
type CryptoTypes = KeypairType | 'unknown';
type CryptoOption = {
text: string,
value: string
};
type State = {
currentPublicKey: Uint8Array | null,
cryptoOptions: Array<CryptoOption>,
cryptoType: CryptoTypes,
defaultPublicKey?: Uint8Array,
data: string,
isHexData: boolean,
isValidAddress: boolean,
isValidSignature: boolean,
isValid: boolean,
signature: string
};
class Verify extends React.PureComponent<Props, State> {
state: State;
constructor (props: Props) {
super(props);
const { t } = this.props;
const pairs = keyring.getPairs();
const currentPair = pairs[0];
const currentPublicKey = currentPair
? currentPair.publicKey()
: null;
this.state = {
cryptoOptions: [{ value: 'unknown', text: t('Crypto not detected') }].concat(uiSettings.availableCryptos),
cryptoType: 'unknown',
currentPublicKey,
defaultPublicKey: currentPublicKey || void 0,
data: '',
isHexData: false,
isValidAddress: !!currentPair,
isValidSignature: false,
isValid: false,
signature: ''
};
}
render () {
const { t } = this.props;
const { cryptoOptions, cryptoType, data, isHexData } = this.state;
return (
<div className='toolbox--Verify'>
{this.renderAddress()}
<div className='ui--row'>
<Input
autoFocus
className='full'
help={t('The data that was signed. This is used in combination with the signature for the verification. It can either be hex or a string.')}
label={t('using the following data')}
onChange={this.onChangeData}
value={data}
/>
</div>
{this.renderSignature()}
<div className='ui--row'>
<Dropdown
defaultValue={cryptoType}
help={t('Cryptography used to create this signature. It is auto-detected on valid signatures.')}
isDisabled
label={t('signature crypto type')}
options={cryptoOptions}
/>
<Static
className='medium'
help={t('Detection on the input string to determine if it is hex or non-hex.')}
label={t('hex input data')}
value={
isHexData
? t('Yes')
: t('No')
}
/>
</div>
</div>
);
}
private renderAddress () {
const { t } = this.props;
const { defaultPublicKey, isValidAddress } = this.state;
return (
<div className='ui--row'>
<InputAddress
className='full'
defaultValue={defaultPublicKey}
help={t('The account that signed the input')}
isError={!isValidAddress}
isInput
label={t('verify using address')}
onChange={this.onChangeAddress}
/>
</div>
);
}
private renderSignature () {
const { t } = this.props;
const { isValid, isValidSignature, signature } = this.state;
return (
<div className='ui--row'>
<Input
className='full'
icon={
<Icon
color={isValid ? 'green' : (isValidSignature ? 'red' : void 0)}
name={isValid ? 'check circle' : (isValidSignature ? 'exclamation circle' : 'help circle')}
size='big'
/>
}
isError={!isValidSignature}
help={t('The signature as by the account being checked, supplied as a hex-formatted string.')}
label={t('the supplied signature')}
onChange={this.onChangeSignature}
value={signature}
/>
</div>
);
}
private nextState (newState: State): void {
this.setState(
(prevState: State): State => {
const { isHexData = prevState.isHexData, isValidAddress = prevState.isValidAddress, isValidSignature = prevState.isValidSignature, currentPublicKey = prevState.currentPublicKey, data = prevState.data, signature = prevState.signature } = newState;
let cryptoType: CryptoTypes = 'unknown';
let isValid = isValidAddress && isValidSignature;
// We cannot just use the keyring verify since it may be an address. So here we first check
// for ed25519, if not valid, we try against sr25519 - if neither are valid, well, we have
// not been able to validate the signature
if (isValid && currentPublicKey) {
let isValidSr = false;
let isValidEd = false;
try {
isValidEd = naclVerify(data, signature, currentPublicKey);
} catch (error) {
// do nothing, already set to false
}
if (isValidEd) {
cryptoType = 'ed25519';
} else {
try {
isValidSr = schnorrkelVerify(data, signature, currentPublicKey);
} catch (error) {
// do nothing, already set to false
}
if (isValidSr) {
cryptoType = 'sr25519';
} else {
isValid = false;
}
}
}
return {
cryptoType,
isHexData,
isValid,
isValidAddress,
isValidSignature,
currentPublicKey,
data,
signature
} as State;
}
);
}
private onChangeData = (data: string): void => {
const isHexData = isHex(data);
this.nextState({ data, isHexData } as State);
}
private onChangeSignature = (signature: string): void => {
const isValidSignature = isHex(signature) && signature.length === 130;
this.nextState({ signature, isValidSignature } as State);
}
private onChangeAddress = (accountId: string): void => {
let currentPublicKey;
try {
currentPublicKey = keyring.decodeAddress(accountId);
} catch (err) {
console.error(err);
}
const isValidAddress = currentPublicKey && currentPublicKey.length === 32;
this.nextState({ currentPublicKey, isValidAddress } as State);
}
}
export default translate(Verify);