Skip to content
This repository has been archived by the owner on Jun 7, 2023. It is now read-only.

Commit

Permalink
Link up trit-based checksums (#1172)
Browse files Browse the repository at this point in the history
* Update #getChecksum implementation to accept trit inputs

* Link up trit based (seed) checksums
  • Loading branch information
laumair authored and rajivshah3 committed Mar 19, 2019
1 parent ee78d3b commit 685d54f
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 77 deletions.
61 changes: 35 additions & 26 deletions src/mobile/__tests__/ui/components/Checksum.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import React from 'react';
import PropTypes from 'prop-types';
import { shallow } from 'enzyme';
import { Checksum } from 'ui/components/Checksum';
import { MAX_SEED_TRITS } from 'shared-modules/libs/iota/utils';
import { trytesToTrits } from 'shared-modules/libs/iota/converter';
import { latestAddressWithoutChecksum, latestAddressChecksum } from 'shared-modules/__tests__/__samples__/addresses';
import theme from '../../../__mocks__/theme';

const getProps = (overrides) =>
assign(
{},
{
seed: '9'.repeat(81),
seed: new Int8Array(MAX_SEED_TRITS),
theme,
t: () => '',
showModal: noop,
Expand All @@ -20,8 +23,8 @@ const getProps = (overrides) =>

describe('Testing Checksum component', () => {
describe('propTypes', () => {
it('should require a seed string as a prop', () => {
expect(Checksum.propTypes.seed).toEqual(PropTypes.string.isRequired);
it('should require a seed object as a prop', () => {
expect(Checksum.propTypes.seed).toEqual(PropTypes.object.isRequired);
});

it('should require a theme object as a prop', () => {
Expand Down Expand Up @@ -49,39 +52,45 @@ describe('Testing Checksum component', () => {
});
});

describe('instance methods', () => {
describe('static methods', () => {
describe('when called', () => {
describe('#getChecksumValue', () => {
describe('when seed length is not zero and seed contains any character other than (A-Z, 9)', () => {
it('should return "!"', () => {
const props = getProps({ seed: '-!' });

const instance = shallow(<Checksum {...props} />).instance();
const checksum = instance.getChecksumValue();

expect(checksum).toEqual('!');
describe('#getValue', () => {
describe(`when seed length is not zero and seed length is less than ${MAX_SEED_TRITS}`, () => {
it('should return "< 81"', () => {
expect(Checksum.getValue(new Int8Array(MAX_SEED_TRITS - 10))).toEqual('< 81');
});
});

describe('when seed length is not zero and seed length is less than 81', () => {
it('should return "< 81"', () => {
const props = getProps({ seed: 'A'.repeat(80) });
describe(`when seed length is greater than ${MAX_SEED_TRITS}`, () => {
it('should return "> 81"', () => {
expect(Checksum.getValue(new Int8Array(MAX_SEED_TRITS + 10))).toEqual('> 81');
});
});

const instance = shallow(<Checksum {...props} />).instance();
const checksum = instance.getChecksumValue();
describe(`when seed length is equal to ${MAX_SEED_TRITS}`, () => {
it('should return computed checksum of seed (in trytes)', () => {
const expectedChecksum = latestAddressChecksum.slice(-3);
const actualChecksum = Checksum.getValue(trytesToTrits(latestAddressWithoutChecksum));

expect(checksum).toEqual('< 81');
expect(expectedChecksum).toEqual(actualChecksum);
});
});
});

describe('when seed length is 81 and seed contains valid characters (A-Z, 9)', () => {
it('should return computed checksum of seed', () => {
const props = getProps({ seed: '9'.repeat(81) });

const instance = shallow(<Checksum {...props} />).instance();
const checksum = instance.getChecksumValue();
describe('#getStyle', () => {
describe(`when seed length is equal to ${MAX_SEED_TRITS}`, () => {
it('should return "theme.primary.color"', () => {
expect(Checksum.getStyle(theme, new Int8Array(MAX_SEED_TRITS))).toEqual({
color: theme.primary.color,
});
});
});

expect(checksum).toEqual('KZW');
describe(`when seed length is not equal to ${MAX_SEED_TRITS}`, () => {
it('should return "theme.body.color"', () => {
expect(Checksum.getStyle(theme, new Int8Array(MAX_SEED_TRITS + 100))).toEqual({
color: theme.body.color,
});
});
});
});
Expand Down
50 changes: 32 additions & 18 deletions src/mobile/src/ui/components/Checksum.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import size from 'lodash/size';
import React, { Component } from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import PropTypes from 'prop-types';
import { VALID_SEED_REGEX, getChecksum } from 'shared-modules/libs/iota/utils';
import { MAX_SEED_TRITS, getChecksum } from 'shared-modules/libs/iota/utils';
import { tritsToChars } from 'shared-modules/libs/iota/converter';
import { withNamespaces } from 'react-i18next';
import { width } from 'libs/dimensions';
import { Styling } from 'ui/theme/general';
Expand Down Expand Up @@ -29,7 +31,7 @@ const styles = StyleSheet.create({
export class Checksum extends Component {
static propTypes = {
/** @ignore */
seed: PropTypes.string.isRequired,
seed: PropTypes.object.isRequired,
/** @ignore */
theme: PropTypes.object.isRequired,
/** @ignore */
Expand All @@ -40,37 +42,49 @@ export class Checksum extends Component {

/**
* Gets the checksum of a seed
*
* @method getValue
*
* @param {array} input
*
* @return {string} Checksum or symbol to be shown
*/
getChecksumValue() {
const { seed } = this.props;
let checksumValue = '...';
static getValue(input) {
const sizeOfInput = size(input);

if (seed.length !== 0 && !seed.match(VALID_SEED_REGEX)) {
checksumValue = '!';
} else if (seed.length !== 0 && seed.length < 81) {
checksumValue = '< 81';
} else if (seed.length === 81 && seed.match(VALID_SEED_REGEX)) {
checksumValue = getChecksum(seed);
if (sizeOfInput !== 0 && sizeOfInput < MAX_SEED_TRITS) {
return '< 81';
}

return checksumValue;
if (sizeOfInput > MAX_SEED_TRITS) {
return '> 81';
} else if (sizeOfInput === MAX_SEED_TRITS) {
return tritsToChars(getChecksum(input));
}

return '...';
}

/**
* Gets the color of the checksum
* @return {Object}
*
* @method getStyle
*
* @param {object} theme
* @param {array} seed
*
* @return {object}
*/
getChecksumStyle() {
const { theme, seed } = this.props;
if (seed.length === 81 && seed.match(VALID_SEED_REGEX)) {
static getStyle(theme, seed) {
if (size(seed) === MAX_SEED_TRITS) {
return { color: theme.primary.color };
}

return { color: theme.body.color };
}

render() {
const { t, theme } = this.props;
const { t, theme, seed } = this.props;

return (
<TouchableOpacity
Expand All @@ -80,7 +94,7 @@ export class Checksum extends Component {
<View style={styles.checksumContainer}>
<Icon name="info" size={width / 22} color={theme.body.color} />
<Text style={[styles.checksumText, { color: theme.body.color }]}>{t('checksum')}:</Text>
<Text style={[styles.checksum, this.getChecksumStyle()]}>{this.getChecksumValue()}</Text>
<Text style={[styles.checksum, Checksum.getStyle(theme, seed)]}>{Checksum.getValue(seed)}</Text>
</View>
</TouchableOpacity>
);
Expand Down
31 changes: 4 additions & 27 deletions src/mobile/src/ui/components/CustomTextInput.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import size from 'lodash/size';
import React, { Component } from 'react';
import { View, Text, TextInput, StyleSheet, TouchableOpacity } from 'react-native';
import { MAX_SEED_TRITS, VALID_SEED_REGEX, getChecksum } from 'shared-modules/libs/iota/utils';
import { VALID_SEED_REGEX } from 'shared-modules/libs/iota/utils';
import PropTypes from 'prop-types';
import { width, height } from 'libs/dimensions';
import { Styling } from 'ui/theme/general';
import { Icon } from 'ui/theme/icons';
import { Checksum } from 'ui/components/Checksum';
import { isAndroid } from 'libs/device';
import { stringToUInt8, UInt8ToString } from 'libs/crypto';
import { trytesToTrits, tritsToChars } from 'shared-modules/libs/iota/converter';
Expand Down Expand Up @@ -248,29 +248,6 @@ class CustomTextInput extends Component {
return this.state.isFocused ? focusedFieldLabel : unfocusedFieldLabel;
}

getChecksumValue() {
const { value } = this.props;
let checksumValue = '...';
if (value === null) {
return checksumValue;
} else if (size(value) !== 0 && size(value) < MAX_SEED_TRITS) {
checksumValue = '< 81';
} else if (size(value) > MAX_SEED_TRITS) {
checksumValue = '> 81';
} else if (size(value) === MAX_SEED_TRITS) {
checksumValue = getChecksum(tritsToChars(value)); // FIXME: Replace with trit checksum calculation
}
return checksumValue;
}

getChecksumStyle() {
const { theme, value } = this.props;
if (value && size(value) === MAX_SEED_TRITS) {
return { color: theme.primary.color };
}
return { color: theme.body.color };
}

renderQR(widgetBorderColor) {
const { theme, onQRPress, containerStyle } = this.props;
return (
Expand Down Expand Up @@ -468,8 +445,8 @@ class CustomTextInput extends Component {
},
]}
>
<Text style={[this.getChecksumStyle(), styles.checksumText]}>
{this.getChecksumValue()}
<Text style={[Checksum.getStyle(theme, value), styles.checksumText]}>
{Checksum.getValue(value)}
</Text>
</View>
</View>
Expand Down
2 changes: 1 addition & 1 deletion src/mobile/src/ui/views/onboarding/WriteSeedDown.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ class WriteSeedDown extends Component {
delay={100}
>
<ChecksumComponent
seed={tritsToChars(global.onboardingSeed)}
seed={global.onboardingSeed}
theme={theme}
showModal={this.openModal}
/>
Expand Down
42 changes: 42 additions & 0 deletions src/shared/__tests__/libs/iota/utils.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {
withRetriesOnDifferentNodes,
throwIfNodeNotHealthy,
isLastTritZero,
getChecksum,
} from '../../../libs/iota/utils';
import { latestAddressWithoutChecksum, latestAddressChecksum } from '../../__samples__/addresses';
import { trytesToTrits, tritsToChars } from '../../../libs/iota/converter';

describe('libs: iota/utils', () => {
describe('#isLastTritZero', () => {
Expand All @@ -23,6 +26,7 @@ describe('libs: iota/utils', () => {
});
});
});

describe('#convertFromTrytes', () => {
describe('when trytes passed as an argument contains all nines', () => {
it('should return a string "Empty"', () => {
Expand Down Expand Up @@ -177,4 +181,42 @@ describe('libs: iota/utils', () => {
});
});
});

describe('#getSeedChecksum', () => {
describe('when seed is in trytes', () => {
describe('when length is provided', () => {
it('should return checksum in trytes with provided length', () => {
const checksum = getChecksum(latestAddressWithoutChecksum, 9);

expect(checksum).to.equal(latestAddressChecksum);
});
});

describe('when length is not provided', () => {
it('should return checksum in trytes with default length', () => {
const checksum = getChecksum(latestAddressWithoutChecksum);

expect(checksum).to.equal(latestAddressChecksum.slice(-3));
});
});
});

describe('when seed is in trits', () => {
describe('when length is provided', () => {
it('should return checksum in trits with provided length', () => {
const checksum = getChecksum(trytesToTrits(latestAddressWithoutChecksum), 27);

expect(tritsToChars(checksum)).to.eql(latestAddressChecksum);
});
});

describe('when length is not provided', () => {
it('should return checksum in trits with default length', () => {
const checksum = getChecksum(trytesToTrits(latestAddressWithoutChecksum));

expect(tritsToChars(checksum)).to.equal(latestAddressChecksum.slice(-3));
});
});
});
});
});
26 changes: 21 additions & 5 deletions src/shared/libs/iota/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export const MAX_SEED_LENGTH = 81;

export const MAX_SEED_TRITS = MAX_SEED_LENGTH * 3;

export const SEED_CHECKSUM_LENGTH = 3;

export const ADDRESS_LENGTH_WITHOUT_CHECKSUM = MAX_SEED_LENGTH;

export const ADDRESS_LENGTH = 90;
Expand Down Expand Up @@ -64,15 +66,29 @@ export const convertFromTrytes = (trytes) => {
};

/**
* Gets checksum for seed
* Gets checksum.
*
* @method getChecksum
* @param {string} seed
*
* @returns {string}
* @param {string | array} input - seed trytes | seed trits
* @param {number} [length]
*
* @returns {string | array}
*/
export const getChecksum = (seed) => {
return iota.utils.addChecksum(seed, 3, false).substr(-3);
export const getChecksum = (
input,
// Trinity trytes to trits conversion creates Int8Array
length = isArray(input) || input instanceof Int8Array ? SEED_CHECKSUM_LENGTH * 3 : SEED_CHECKSUM_LENGTH,
) => {
return iota.utils
.addChecksum(
// https://github.com/iotaledger/iota.js/blob/develop/lib/utils/utils.js#L64
// iota.lib.js throws an exception for typed arrays
input instanceof Int8Array ? Array.from(input) : input,
length,
false,
)
.slice(-length);
};

/**
Expand Down

0 comments on commit 685d54f

Please sign in to comment.