diff --git a/class/wallets/abstract-hd-electrum-wallet.ts b/class/wallets/abstract-hd-electrum-wallet.ts index bb731df05..0e849f8bb 100644 --- a/class/wallets/abstract-hd-electrum-wallet.ts +++ b/class/wallets/abstract-hd-electrum-wallet.ts @@ -159,9 +159,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } async generateFromEntropy(user: Buffer) { - const random = await randomBytes(user.length < 32 ? 32 - user.length : 0); - const buf = Buffer.concat([user, random], 32); - this.secret = bip39.entropyToMnemonic(buf.toString('hex')); + if (user.length !== 32 && user.length !== 16) { + throw new Error('Entropy has to be 16 or 32 bytes long'); + } + this.secret = bip39.entropyToMnemonic(user.toString('hex')); } _getExternalWIFByIndex(index: number): string | false { diff --git a/class/wallets/legacy-wallet.ts b/class/wallets/legacy-wallet.ts index 2b73ca8e4..b3b0657ce 100644 --- a/class/wallets/legacy-wallet.ts +++ b/class/wallets/legacy-wallet.ts @@ -63,18 +63,10 @@ export class LegacyWallet extends AbstractWallet { } async generateFromEntropy(user: Buffer): Promise { - let i = 0; - do { - i += 1; - const random = await randomBytes(user.length < 32 ? 32 - user.length : 0); - const buf = Buffer.concat([user, random], 32); - try { - this.secret = ECPair.fromPrivateKey(buf).toWIF(); - return; - } catch (e) { - if (i === 5) throw e; - } - } while (true); + if (user.length !== 32) { + throw new Error('Entropy should be 32 bytes'); + } + this.secret = ECPair.fromPrivateKey(user).toWIF(); } getAddress(): string | false { diff --git a/components/FloatButtons.tsx b/components/FloatButtons.tsx index a0c6059ed..f9d9b3a1a 100644 --- a/components/FloatButtons.tsx +++ b/components/FloatButtons.tsx @@ -131,7 +131,7 @@ interface FButtonProps { last?: boolean; disabled?: boolean; onPress: () => void; - onLongPress: () => void; + onLongPress?: () => void; } export const FButton = ({ text, icon, width, first, last, ...props }: FButtonProps) => { diff --git a/loc/en.json b/loc/en.json index d04f3045a..7ff631601 100644 --- a/loc/en.json +++ b/loc/en.json @@ -43,7 +43,8 @@ "entropy": { "save": "Save", "title": "Entropy", - "undo": "Undo" + "undo": "Undo", + "amountOfEntropy": "{bits} of {limit} bits" }, "errors": { "broadcast": "Broadcast failed.", @@ -385,6 +386,8 @@ "add_bitcoin": "Bitcoin", "add_bitcoin_explain": "Simple and powerful Bitcoin wallet", "add_create": "Create", + "add_entropy": "Entropy", + "add_entropy_bytes": "{bytes} bytes of entropy", "add_entropy_generated": "{gen} bytes of generated entropy", "add_entropy_provide": "Provide entropy via dice rolls", "add_entropy_remain": "{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.", @@ -398,6 +401,10 @@ "add_title": "Add Wallet", "add_wallet_name": "Name", "add_wallet_type": "Type", + "add_wallet_seed_length": "Seed Length", + "add_wallet_seed_length_message": "Choose the length of the seed phrase you wish to use for this wallet.", + "add_wallet_seed_length_12": "12 words", + "add_wallet_seed_length_24": "24 words", "clipboard_bitcoin": "You have a Bitcoin address on your clipboard. Would you like to use it for a transaction?", "clipboard_lightning": "You have a Lightning invoice on your clipboard. Would you like to use it for a transaction?", "details_address": "Address", diff --git a/navigation/LazyLoadAddWalletStack.tsx b/navigation/LazyLoadAddWalletStack.tsx index e7042207b..5b0e65b39 100644 --- a/navigation/LazyLoadAddWalletStack.tsx +++ b/navigation/LazyLoadAddWalletStack.tsx @@ -11,7 +11,7 @@ const ImportWallet = lazy(() => import('../screen/wallets/import')); const PleaseBackup = lazy(() => import('../screen/wallets/PleaseBackup')); const PleaseBackupLNDHub = lazy(() => import('../screen/wallets/pleaseBackupLNDHub')); const PleaseBackupLdk = lazy(() => import('../screen/wallets/pleaseBackupLdk')); -const ProvideEntropy = lazy(() => import('../screen/wallets/provideEntropy')); +const ProvideEntropy = lazy(() => import('../screen/wallets/ProvideEntropy')); const WalletsAddMultisig = lazy(() => import('../screen/wallets/addMultisig')); const WalletsAddMultisigStep2 = lazy(() => import('../screen/wallets/addMultisigStep2')); const WalletsAddMultisigHelp = lazy(() => import('../screen/wallets/addMultisigHelp')); diff --git a/screen/wallets/Add.tsx b/screen/wallets/Add.tsx index 59090dbf8..1fe687b76 100644 --- a/screen/wallets/Add.tsx +++ b/screen/wallets/Add.tsx @@ -3,6 +3,7 @@ import { useNavigation } from '@react-navigation/native'; import React, { useEffect, useReducer } from 'react'; import { ActivityIndicator, + Alert, Keyboard, KeyboardAvoidingView, LayoutAnimation, @@ -53,7 +54,7 @@ interface State { label: string; selectedWalletType: ButtonSelected; backdoorPressed: number; - entropy: string | any[] | undefined; + entropy: Buffer | undefined; entropyButtonText: string; } @@ -161,18 +162,13 @@ const WalletsAdd: React.FC = () => { }); }, [colorScheme, setOptions]); - const entropyGenerated = (newEntropy: string | any[]) => { + const entropyGenerated = (newEntropy: Buffer) => { let entropyTitle; if (!newEntropy) { entropyTitle = loc.wallets.add_entropy_provide; - } else if (newEntropy.length < 32) { - entropyTitle = loc.formatString(loc.wallets.add_entropy_remain, { - gen: newEntropy.length, - rem: 32 - newEntropy.length, - }); } else { - entropyTitle = loc.formatString(loc.wallets.add_entropy_generated, { - gen: newEntropy.length, + entropyTitle = loc.formatString(loc.wallets.add_entropy_bytes, { + bytes: newEntropy.length, }); } setEntropy(newEntropy); @@ -203,7 +199,7 @@ const WalletsAdd: React.FC = () => { dispatch({ type: 'INCREMENT_BACKDOOR_PRESSED', payload: value }); }; - const setEntropy = (value: string | any[]) => { + const setEntropy = (value: Buffer) => { dispatch({ type: 'SET_ENTROPY', payload: value }); }; @@ -236,7 +232,6 @@ const WalletsAdd: React.FC = () => { if (selectedWalletType === ButtonSelected.ONCHAIN) { if (entropy) { try { - // @ts-ignore: Return later to update await w.generateFromEntropy(entropy); } catch (e: any) { console.log(e.toString()); @@ -334,8 +329,34 @@ const WalletsAdd: React.FC = () => { }; const navigateToEntropy = () => { - // @ts-ignore: Return later to update - navigate('ProvideEntropy', { onGenerated: entropyGenerated }); + Alert.alert( + loc.wallets.add_wallet_seed_length, + loc.wallets.add_wallet_seed_length_message, + [ + { + text: loc._.cancel, + onPress: () => {}, + style: 'default', + }, + { + text: loc.wallets.add_wallet_seed_length_12, + onPress: () => { + // @ts-ignore: Return later to update + navigate('ProvideEntropy', { onGenerated: entropyGenerated, words: 12 }); + }, + style: 'default', + }, + { + text: loc.wallets.add_wallet_seed_length_24, + onPress: () => { + // @ts-ignore: Return later to update + navigate('ProvideEntropy', { onGenerated: entropyGenerated, words: 24 }); + }, + style: 'default', + }, + ], + { cancelable: true }, + ); }; const navigateToImportWallet = () => { diff --git a/screen/wallets/provideEntropy.js b/screen/wallets/ProvideEntropy.tsx similarity index 54% rename from screen/wallets/provideEntropy.js rename to screen/wallets/ProvideEntropy.tsx index 2bc0073f2..472d8af86 100644 --- a/screen/wallets/provideEntropy.js +++ b/screen/wallets/ProvideEntropy.tsx @@ -1,8 +1,18 @@ import { useNavigation, useRoute } from '@react-navigation/native'; import BN from 'bignumber.js'; -import PropTypes from 'prop-types'; -import React, { useReducer, useState } from 'react'; -import { Dimensions, Image, PixelRatio, ScrollView, StyleSheet, Text, TouchableOpacity, useWindowDimensions, View } from 'react-native'; +import React, { useEffect, useReducer, useState } from 'react'; +import { + Alert, + Dimensions, + Image, + PixelRatio, + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View, + useWindowDimensions, +} from 'react-native'; import { Icon } from 'react-native-elements'; import { BlueSpacing20 } from '../../BlueComponents'; @@ -11,48 +21,81 @@ import SafeArea from '../../components/SafeArea'; import { Tabs } from '../../components/Tabs'; import { BlueCurrentTheme, useTheme } from '../../components/themes'; import loc from '../../loc'; +import { randomBytes } from '../../class/rng'; -const ENTROPY_LIMIT = 256; +export enum EActionType { + push = 'push', + pop = 'pop', + limit = 'limit', + noop = 'noop', +} -const shiftLeft = (value, places) => value.multipliedBy(2 ** places); -const shiftRight = (value, places) => value.div(2 ** places).dp(0, BN.ROUND_DOWN); +type TEntropy = { value: number; bits: number }; +type TState = { entropy: BN; bits: number; items: number[]; limit: number }; +type TAction = + | ({ type: EActionType.push } & TEntropy) + | { type: EActionType.pop } + | { type: EActionType.limit; limit: number } + | { type: EActionType.noop }; +type TPush = (v: TEntropy | null) => void; +type TPop = () => void; -const initialState = { entropy: BN(0), bits: 0, items: [] }; -export const eReducer = (state = initialState, action) => { +const initialState: TState = { + entropy: BN(0), + bits: 0, + items: [], + limit: 256, +}; + +const shiftLeft = (value: BN, places: number) => value.multipliedBy(2 ** places); +const shiftRight = (value: BN, places: number) => value.div(2 ** places).dp(0, BN.ROUND_DOWN); + +export const eReducer = (state: TState = initialState, action: TAction): TState => { switch (action.type) { - case 'push': { - let { value, bits } = action; + case EActionType.noop: + return state; + + case EActionType.push: { + let value: number | BN = action.value; + let bits: number = action.bits; + if (value >= 2 ** bits) { throw new TypeError("Can't push value exceeding size in bits"); } - if (state.bits === ENTROPY_LIMIT) return state; - if (state.bits + bits > ENTROPY_LIMIT) { - value = shiftRight(BN(value), bits + state.bits - ENTROPY_LIMIT); - bits = ENTROPY_LIMIT - state.bits; + if (state.bits === state.limit) return state; + if (state.bits + bits > state.limit) { + value = shiftRight(BN(value), bits + state.bits - state.limit); + bits = state.limit - state.bits; } const entropy = shiftLeft(state.entropy, bits).plus(value); const items = [...state.items, bits]; - return { entropy, bits: state.bits + bits, items }; + return { ...state, entropy, bits: state.bits + bits, items }; } - case 'pop': { + + case EActionType.pop: { if (state.bits === 0) return state; - const bits = state.items.pop(); + const bits = state.items.pop()!; const entropy = shiftRight(state.entropy, bits); - return { entropy, bits: state.bits - bits, items: [...state.items] }; + return { ...state, entropy, bits: state.bits - bits, items: [...state.items] }; } + + case EActionType.limit: { + return { ...state, limit: action.limit }; + } + default: return state; } }; -export const entropyToHex = ({ entropy, bits }) => { +export const entropyToHex = ({ entropy, bits }: { entropy: BN; bits: number }): string => { if (bits === 0) return '0x'; const hex = entropy.toString(16); const hexSize = Math.floor((bits - 1) / 4) + 1; return '0x' + '0'.repeat(hexSize - hex.length) + hex; }; -export const getEntropy = (number, base) => { +export const getEntropy = (number: number, base: number): TEntropy | null => { if (base === 1) return null; let maxPow = 1; while (2 ** (maxPow + 1) <= base) { @@ -77,12 +120,12 @@ export const getEntropy = (number, base) => { }; // cut entropy to bytes, convert to Buffer -export const convertToBuffer = ({ entropy, bits }) => { +export const convertToBuffer = ({ entropy, bits }: { entropy: BN; bits: number }): Buffer => { if (bits < 8) return Buffer.from([]); const bytes = Math.floor(bits / 8); // convert to byte array - let arr = []; + const arr: string[] = []; const ent = entropy.toString(16).split('').reverse(); ent.forEach((v, index) => { if (index % 2 === 1) { @@ -91,18 +134,19 @@ export const convertToBuffer = ({ entropy, bits }) => { arr.unshift(v); } }); - arr = arr.map(i => parseInt(i, 16)); + + let arr2 = arr.map(i => parseInt(i, 16)); if (arr.length > bytes) { - arr.shift(); - } else if (arr.length < bytes) { - const zeros = [...Array(bytes - arr.length)].map(() => 0); - arr = [...zeros, ...arr]; + arr2.shift(); + } else if (arr2.length < bytes) { + const zeros = [...Array(bytes - arr2.length)].map(() => 0); + arr2 = [...zeros, ...arr2]; } - return Buffer.from(arr); + return Buffer.from(arr2); }; -const Coin = ({ push }) => ( +const Coin = ({ push }: { push: TPush }) => ( push(getEntropy(0, 2))} style={styles.coinBody}> @@ -113,11 +157,24 @@ const Coin = ({ push }) => ( ); -Coin.propTypes = { - push: PropTypes.func.isRequired, +const diceIcon = (i: number): string => { + switch (i) { + case 1: + return 'dice-one'; + case 2: + return 'dice-two'; + case 3: + return 'dice-three'; + case 4: + return 'dice-four'; + case 5: + return 'dice-five'; + default: + return 'dice-six'; + } }; -const Dice = ({ push, sides }) => { +const Dice = ({ push, sides }: { push: TPush; sides: number }) => { const { width } = useWindowDimensions(); const { colors } = useTheme(); const diceWidth = width / 4; @@ -132,22 +189,6 @@ const Dice = ({ push, sides }) => { backgroundColor: colors.elevated, }, }); - const diceIcon = i => { - switch (i) { - case 1: - return 'dice-one'; - case 2: - return 'dice-two'; - case 3: - return 'dice-three'; - case 4: - return 'dice-four'; - case 5: - return 'dice-five'; - default: - return 'dice-six'; - } - }; return ( @@ -168,48 +209,53 @@ const Dice = ({ push, sides }) => { ); }; -Dice.propTypes = { - sides: PropTypes.number.isRequired, - push: PropTypes.func.isRequired, -}; - const buttonFontSize = PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22 ? 22 : PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26); -const Buttons = ({ pop, save, colors }) => ( +const Buttons = ({ pop, save, colors }: { pop: TPop; save: () => void; colors: any }) => ( } - text={loc.entropy.undo} /> } - text={loc.entropy.save} /> ); -Buttons.propTypes = { - pop: PropTypes.func.isRequired, - save: PropTypes.func.isRequired, - colors: PropTypes.shape.isRequired, +const TollTab = ({ active }: { active: boolean }) => { + const { colors } = useTheme(); + return ; +}; + +const D6Tab = ({ active }: { active: boolean }) => { + const { colors } = useTheme(); + return ; +}; + +const D20Tab = ({ active }: { active: boolean }) => { + const { colors } = useTheme(); + return ; }; const Entropy = () => { const [entropy, dispatch] = useReducer(eReducer, initialState); - const { onGenerated } = useRoute().params; + // @ts-ignore: navigation is not typed yet + const { onGenerated, words } = useRoute().params; const navigation = useNavigation(); const [tab, setTab] = useState(1); const [show, setShow] = useState(false); @@ -223,12 +269,65 @@ const Entropy = () => { }, }); - const push = v => v && dispatch({ type: 'push', value: v.value, bits: v.bits }); - const pop = () => dispatch({ type: 'pop' }); - const save = () => { - navigation.pop(); - const buf = convertToBuffer(entropy); - onGenerated(buf); + useEffect(() => { + dispatch({ type: EActionType.limit, limit: words === 24 ? 256 : 128 }); + }, [dispatch, words]); + + const handlePush: TPush = v => { + if (v === null) { + dispatch({ type: EActionType.noop }); + } else { + dispatch({ type: EActionType.push, value: v.value, bits: v.bits }); + } + }; + + const handlePop: TPop = () => { + dispatch({ type: EActionType.pop }); + }; + + const handleSave = async () => { + let buf = convertToBuffer(entropy); + const bufLength = words === 24 ? 32 : 16; + const remaining = bufLength - buf.length; + + let entropyTitle = ''; + if (buf.length < bufLength) { + entropyTitle = loc.formatString(loc.wallets.add_entropy_remain, { + gen: buf.length, + rem: remaining, + }); + } else { + entropyTitle = loc.formatString(loc.wallets.add_entropy_generated, { + gen: buf.length, + }); + } + + Alert.alert( + loc.wallets.add_entropy, + entropyTitle, + [ + { + text: loc._.ok, + onPress: async () => { + if (remaining > 0) { + const random = await randomBytes(remaining); + buf = Buffer.concat([buf, random], bufLength); + } + + // @ts-ignore: navigation is not typed yet + navigation.pop(); + onGenerated(buf); + }, + style: 'default', + }, + { + text: loc._.cancel, + onPress: () => {}, + style: 'default', + }, + ], + { cancelable: true }, + ); }; const hex = entropyToHex(entropy); @@ -240,34 +339,19 @@ const Entropy = () => { setShow(!show)}> - {show ? hex : `${bits} of 256 bits`} + + {show ? hex : loc.formatString(loc.entropy.amountOfEntropy, { bits, limit: entropy.limit })} + - ( - - ), - // eslint-disable-next-line react/no-unstable-nested-components - ({ active }) => ( - - ), - // eslint-disable-next-line react/no-unstable-nested-components - ({ active }) => ( - - ), - ]} - /> + - {tab === 0 && } - {tab === 1 && } - {tab === 2 && } + {tab === 0 && } + {tab === 1 && } + {tab === 2 && } - + ); }; diff --git a/tests/unit/hd-segwit-p2sh-wallet.test.ts b/tests/unit/hd-segwit-p2sh-wallet.test.ts index 9bd652036..84d1b2226 100644 --- a/tests/unit/hd-segwit-p2sh-wallet.test.ts +++ b/tests/unit/hd-segwit-p2sh-wallet.test.ts @@ -140,31 +140,26 @@ describe('P2SH Segwit HD (BIP49)', () => { }); it('can consume user generated entropy', async () => { - const hd = new HDSegwitP2SHWallet(); - const zeroes = [...Array(32)].map(() => 0); - await hd.generateFromEntropy(Buffer.from(zeroes)); + const hd1 = new HDSegwitP2SHWallet(); + const zeroes16 = [...Array(16)].map(() => 0); + await hd1.generateFromEntropy(Buffer.from(zeroes16)); + assert.strictEqual(hd1.getSecret(), 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'); + + const hd2 = new HDSegwitP2SHWallet(); + const zeroes32 = [...Array(32)].map(() => 0); + await hd2.generateFromEntropy(Buffer.from(zeroes32)); assert.strictEqual( - hd.getSecret(), + hd2.getSecret(), 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art', ); }); - it('can fullfill user generated entropy if less than 32 bytes provided', async () => { + it('throws an error if not 32 bytes provided', async () => { const hd = new HDSegwitP2SHWallet(); - const zeroes = [...Array(16)].map(() => 0); - await hd.generateFromEntropy(Buffer.from(zeroes)); - const secret = hd.getSecret(); - assert.strictEqual(secret.startsWith('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon'), true); - - const secretWithoutChecksum = secret.split(' '); - secretWithoutChecksum.pop(); - const secretWithoutChecksumString = secretWithoutChecksum.join(' '); - assert.strictEqual( - secretWithoutChecksumString.endsWith('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon'), - false, - ); - - assert.ok(secret.split(' ').length === 12 || secret.split(' ').length === 24); + const values = [...Array(31)].map(() => 1); + await assert.rejects(async () => await hd.generateFromEntropy(Buffer.from(values)), { + message: 'Entropy has to be 16 or 32 bytes long', + }); }); it('can sign and verify messages', async () => { diff --git a/tests/unit/legacy-wallet.test.js b/tests/unit/legacy-wallet.test.js index 574a26b46..a5ff4fe70 100644 --- a/tests/unit/legacy-wallet.test.js +++ b/tests/unit/legacy-wallet.test.js @@ -1,10 +1,7 @@ import assert from 'assert'; import * as bitcoin from 'bitcoinjs-lib'; -import { ECPairFactory } from 'ecpair'; -import ecc from '../../blue_modules/noble_ecc'; import { LegacyWallet } from '../../class'; -const ECPair = ECPairFactory(ecc); describe('Legacy wallet', () => { it('can validate addresses', () => { @@ -146,17 +143,12 @@ describe('Legacy wallet', () => { assert.strictEqual(l.getSecret(), 'KwFfNUhSDaASSAwtG7ssQM1uVX8RgX5GHWnnLfhfiQDigjioWXHH'); }); - it('can fullfill user generated entropy if less than 32 bytes provided', async () => { + it('throws an error if not 32 bytes provided', async () => { const l = new LegacyWallet(); - const values = [...Array(16)].map(() => 1); - await l.generateFromEntropy(Buffer.from(values)); - assert.strictEqual(l.getSecret().startsWith('KwFfNUhSDaASSAwtG7ssQM'), true); - assert.strictEqual(l.getSecret().endsWith('GHWnnLfhfiQDigjioWXHH'), false); - const keyPair = ECPair.fromWIF(l.getSecret()); - assert.strictEqual(keyPair.privateKey.toString('hex').startsWith('01010101'), true); - assert.strictEqual(keyPair.privateKey.toString('hex').endsWith('01010101'), false); - assert.strictEqual(keyPair.privateKey.toString('hex').endsWith('00000000'), false); - assert.strictEqual(keyPair.privateKey.toString('hex').endsWith('ffffffff'), false); + const values = [...Array(31)].map(() => 1); + await assert.rejects(async () => await l.generateFromEntropy(Buffer.from(values)), { + message: 'Entropy should be 32 bytes', + }); }); it('can sign and verify messages', async () => { diff --git a/tests/unit/provide-entropy.test.js b/tests/unit/provide-entropy.test.ts similarity index 61% rename from tests/unit/provide-entropy.test.js rename to tests/unit/provide-entropy.test.ts index 81888eda3..8e108938f 100644 --- a/tests/unit/provide-entropy.test.js +++ b/tests/unit/provide-entropy.test.ts @@ -1,69 +1,69 @@ import assert from 'assert'; -import { convertToBuffer, entropyToHex, eReducer, getEntropy } from '../../screen/wallets/provideEntropy'; +import { convertToBuffer, EActionType, entropyToHex, eReducer, getEntropy } from '../../screen/wallets/ProvideEntropy'; describe('Entropy reducer and format', () => { it('handles push and pop correctly', () => { - let state = eReducer(undefined, { type: null }); + let state = eReducer(undefined, { type: EActionType.noop }); assert.equal(entropyToHex(state), '0x'); - state = eReducer(state, { type: 'push', value: 0, bits: 1 }); + state = eReducer(state, { type: EActionType.push, value: 0, bits: 1 }); assert.equal(entropyToHex(state), '0x0'); - state = eReducer(state, { type: 'push', value: 0, bits: 1 }); + state = eReducer(state, { type: EActionType.push, value: 0, bits: 1 }); assert.equal(entropyToHex(state), '0x0'); - state = eReducer(state, { type: 'push', value: 0, bits: 3 }); + state = eReducer(state, { type: EActionType.push, value: 0, bits: 3 }); assert.equal(entropyToHex(state), '0x00'); - state = eReducer(state, { type: 'pop' }); + state = eReducer(state, { type: EActionType.pop }); assert.equal(entropyToHex(state), '0x0'); - state = eReducer(state, { type: 'pop' }); - state = eReducer(state, { type: 'pop' }); + state = eReducer(state, { type: EActionType.pop }); + state = eReducer(state, { type: EActionType.pop }); assert.equal(entropyToHex(state), '0x'); - state = eReducer(state, { type: 'push', value: 1, bits: 1 }); + state = eReducer(state, { type: EActionType.push, value: 1, bits: 1 }); assert.equal(entropyToHex(state), '0x1'); // 0b1 - state = eReducer(state, { type: 'push', value: 0, bits: 1 }); + state = eReducer(state, { type: EActionType.push, value: 0, bits: 1 }); assert.equal(entropyToHex(state), '0x2'); // 0b10 - state = eReducer(state, { type: 'push', value: 0b01, bits: 2 }); + state = eReducer(state, { type: EActionType.push, value: 0b01, bits: 2 }); assert.equal(entropyToHex(state), '0x9'); // 0b1001 - state = eReducer(state, { type: 'push', value: 0b10, bits: 2 }); + state = eReducer(state, { type: EActionType.push, value: 0b10, bits: 2 }); assert.equal(entropyToHex(state), '0x26'); // 0b100110 }); it('handles 128 bits correctly', () => { - const state = eReducer(undefined, { type: 'push', value: 0, bits: 128 }); + const state = eReducer(undefined, { type: EActionType.push, value: 0, bits: 128 }); assert.equal(entropyToHex(state), '0x00000000000000000000000000000000'); }); it('handles 256 bits correctly', () => { - let state = eReducer(undefined, { type: null }); // get init state + let state = eReducer(undefined, { type: EActionType.noop }); // get init state // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const i of [...Array(256)]) { - state = eReducer(state, { type: 'push', value: 1, bits: 1 }); + state = eReducer(state, { type: EActionType.push, value: 1, bits: 1 }); } assert.equal(entropyToHex(state), '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); }); it('handles pop when empty without error', () => { - const state = eReducer(undefined, { type: 'pop' }); + const state = eReducer(undefined, { type: EActionType.pop }); assert.equal(entropyToHex(state), '0x'); }); it('handles 256 bits limit', () => { - let state = eReducer(undefined, { type: 'push', value: 0, bits: 254 }); - state = eReducer(state, { type: 'push', value: 0b101, bits: 3 }); + let state = eReducer(undefined, { type: EActionType.push, value: 0, bits: 254 }); + state = eReducer(state, { type: EActionType.push, value: 0b101, bits: 3 }); assert.equal(entropyToHex(state), '0x0000000000000000000000000000000000000000000000000000000000000002'); }); it('Throws error if you try to push value exceeding size in bits', () => { - assert.throws(() => eReducer(undefined, { type: 'push', value: 8, bits: 3 }), { + assert.throws(() => eReducer(undefined, { type: EActionType.push, value: 8, bits: 3 }), { name: 'TypeError', message: "Can't push value exceeding size in bits", }); @@ -104,48 +104,48 @@ describe('getEntropy function', () => { describe('convertToBuffer function', () => { it('zero bits', () => { - const state = eReducer(undefined, { type: null }); + const state = eReducer(undefined, { type: EActionType.noop }); assert.deepEqual(convertToBuffer(state), Buffer.from([])); }); it('8 zero bits', () => { - const state = eReducer(undefined, { type: 'push', value: 0, bits: 8 }); + const state = eReducer(undefined, { type: EActionType.push, value: 0, bits: 8 }); assert.deepEqual(convertToBuffer(state), Buffer.from([0])); }); it('8 filled bits', () => { - const state = eReducer(undefined, { type: 'push', value: 0b11111111, bits: 8 }); + const state = eReducer(undefined, { type: EActionType.push, value: 0b11111111, bits: 8 }); assert.deepEqual(convertToBuffer(state), Buffer.from([0b11111111])); }); it('9 zero bits', () => { - const state = eReducer(undefined, { type: 'push', value: 0, bits: 9 }); + const state = eReducer(undefined, { type: EActionType.push, value: 0, bits: 9 }); assert.deepEqual(convertToBuffer(state), Buffer.from([0])); }); it('9 filled bits', () => { - const state = eReducer(undefined, { type: 'push', value: 0b111111111, bits: 9 }); + const state = eReducer(undefined, { type: EActionType.push, value: 0b111111111, bits: 9 }); assert.deepEqual(convertToBuffer(state), Buffer.from([0b11111111])); }); it('9 bits', () => { - const state = eReducer(undefined, { type: 'push', value: 0b111100111, bits: 9 }); + const state = eReducer(undefined, { type: EActionType.push, value: 0b111100111, bits: 9 }); assert.deepEqual(convertToBuffer(state), Buffer.from([0b11100111])); }); it('3 bytes', () => { - let state = eReducer(undefined, { type: 'push', value: 1, bits: 8 }); - state = eReducer(state, { type: 'push', value: 2, bits: 8 }); - state = eReducer(state, { type: 'push', value: 3, bits: 8 }); + let state = eReducer(undefined, { type: EActionType.push, value: 1, bits: 8 }); + state = eReducer(state, { type: EActionType.push, value: 2, bits: 8 }); + state = eReducer(state, { type: EActionType.push, value: 3, bits: 8 }); assert.deepEqual(convertToBuffer(state), Buffer.from([1, 2, 3])); }); it('256 bits or 32bytes', () => { - let state = eReducer(undefined, { type: null }); // get init state + let state = eReducer(undefined, { type: EActionType.noop }); // get init state // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const i of [...Array(256)]) { - state = eReducer(state, { type: 'push', value: 1, bits: 1 }); + state = eReducer(state, { type: EActionType.push, value: 1, bits: 1 }); } const bytes = [...Array(32)].map(() => 255);