mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-01-18 13:26:33 +01:00
Merge pull request #6397 from BlueWallet/entropy-ts
feat: Entropy refactor
This commit is contained in:
commit
3f52a597a4
@ -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 {
|
||||
|
@ -63,18 +63,10 @@ export class LegacyWallet extends AbstractWallet {
|
||||
}
|
||||
|
||||
async generateFromEntropy(user: Buffer): Promise<void> {
|
||||
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 {
|
||||
|
@ -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) => {
|
||||
|
@ -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",
|
||||
|
@ -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'));
|
||||
|
@ -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 = () => {
|
||||
|
@ -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 }) => (
|
||||
<View style={styles.coinRoot}>
|
||||
<TouchableOpacity accessibilityRole="button" onPress={() => push(getEntropy(0, 2))} style={styles.coinBody}>
|
||||
<Image style={styles.coinImage} source={require('../../img/coin1.png')} />
|
||||
@ -113,11 +157,24 @@ const Coin = ({ push }) => (
|
||||
</View>
|
||||
);
|
||||
|
||||
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 (
|
||||
<ScrollView contentContainerStyle={[styles.diceContainer, stylesHook.diceContainer]}>
|
||||
@ -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 }) => (
|
||||
<FContainer>
|
||||
<FButton
|
||||
onPress={pop}
|
||||
text={loc.entropy.undo}
|
||||
icon={
|
||||
<View style={styles.buttonsIcon}>
|
||||
<Icon name="undo" size={buttonFontSize} type="font-awesome" color={colors.buttonAlternativeTextColor} />
|
||||
</View>
|
||||
}
|
||||
text={loc.entropy.undo}
|
||||
/>
|
||||
<FButton
|
||||
onPress={save}
|
||||
text={loc.entropy.save}
|
||||
icon={
|
||||
<View style={styles.buttonsIcon}>
|
||||
<Icon name="arrow-down" size={buttonFontSize} type="font-awesome" color={colors.buttonAlternativeTextColor} />
|
||||
</View>
|
||||
}
|
||||
text={loc.entropy.save}
|
||||
/>
|
||||
</FContainer>
|
||||
);
|
||||
|
||||
Buttons.propTypes = {
|
||||
pop: PropTypes.func.isRequired,
|
||||
save: PropTypes.func.isRequired,
|
||||
colors: PropTypes.shape.isRequired,
|
||||
const TollTab = ({ active }: { active: boolean }) => {
|
||||
const { colors } = useTheme();
|
||||
return <Icon name="toll" type="material" color={active ? colors.buttonAlternativeTextColor : colors.buttonBackgroundColor} />;
|
||||
};
|
||||
|
||||
const D6Tab = ({ active }: { active: boolean }) => {
|
||||
const { colors } = useTheme();
|
||||
return <Icon name="dice" type="font-awesome-5" color={active ? colors.buttonAlternativeTextColor : colors.buttonBackgroundColor} />;
|
||||
};
|
||||
|
||||
const D20Tab = ({ active }: { active: boolean }) => {
|
||||
const { colors } = useTheme();
|
||||
return <Icon name="dice-d20" type="font-awesome-5" color={active ? colors.buttonAlternativeTextColor : colors.buttonBackgroundColor} />;
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
<BlueSpacing20 />
|
||||
<TouchableOpacity accessibilityRole="button" onPress={() => setShow(!show)}>
|
||||
<View style={[styles.entropy, stylesHook.entropy]}>
|
||||
<Text style={[styles.entropyText, stylesHook.entropyText]}>{show ? hex : `${bits} of 256 bits`}</Text>
|
||||
<Text style={[styles.entropyText, stylesHook.entropyText]}>
|
||||
{show ? hex : loc.formatString(loc.entropy.amountOfEntropy, { bits, limit: entropy.limit })}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Tabs
|
||||
active={tab}
|
||||
onSwitch={setTab}
|
||||
tabs={[
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
({ active }) => (
|
||||
<Icon name="toll" type="material" color={active ? colors.buttonAlternativeTextColor : colors.buttonBackgroundColor} />
|
||||
),
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
({ active }) => (
|
||||
<Icon name="dice" type="font-awesome-5" color={active ? colors.buttonAlternativeTextColor : colors.buttonBackgroundColor} />
|
||||
),
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
({ active }) => (
|
||||
<Icon name="dice-d20" type="font-awesome-5" color={active ? colors.buttonAlternativeTextColor : colors.buttonBackgroundColor} />
|
||||
),
|
||||
]}
|
||||
/>
|
||||
<Tabs active={tab} onSwitch={setTab} tabs={[TollTab, D6Tab, D20Tab]} />
|
||||
|
||||
{tab === 0 && <Coin push={push} />}
|
||||
{tab === 1 && <Dice sides={6} push={push} />}
|
||||
{tab === 2 && <Dice sides={20} push={push} />}
|
||||
{tab === 0 && <Coin push={handlePush} />}
|
||||
{tab === 1 && <Dice sides={6} push={handlePush} />}
|
||||
{tab === 2 && <Dice sides={20} push={handlePush} />}
|
||||
|
||||
<Buttons pop={pop} save={save} colors={colors} />
|
||||
<Buttons pop={handlePop} save={handleSave} colors={colors} />
|
||||
</SafeArea>
|
||||
);
|
||||
};
|
@ -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 () => {
|
||||
|
@ -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 () => {
|
||||
|
@ -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);
|
Loading…
Reference in New Issue
Block a user