ADD: Entropy generator (#1233)

This commit is contained in:
Ivan 2020-06-29 15:58:43 +03:00 committed by GitHub
parent 003ad1213f
commit 09b81c5941
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 923 additions and 17 deletions

View file

@ -2537,6 +2537,42 @@ export function BlueBigCheckmark({ style }) {
);
}
const tabsStyles = StyleSheet.create({
root: {
flexDirection: 'row',
height: 50,
borderColor: '#e3e3e3',
borderBottomWidth: 1,
},
tabRoot: {
justifyContent: 'center',
alignItems: 'center',
borderColor: 'white',
borderBottomWidth: 2,
},
});
export const BlueTabs = ({ active, onSwitch, tabs }) => (
<View style={tabsStyles.root}>
{tabs.map((Tab, i) => (
<TouchableOpacity
key={i}
onPress={() => onSwitch(i)}
style={[
tabsStyles.tabRoot,
active === i && {
borderColor: BlueApp.settings.buttonAlternativeTextColor,
borderBottomWidth: 2,
},
{ width: width / tabs.length },
]}
>
<Tab active={active === i} />
</TouchableOpacity>
))}
</View>
);
export class DynamicQRCode extends Component {
constructor() {
super();

View file

@ -36,6 +36,7 @@ import HodlHodlMyContracts from './screen/wallets/hodlHodlMyContracts';
import Marketplace from './screen/wallets/marketplace';
import ReorderWallets from './screen/wallets/reorderWallets';
import SelectWallet from './screen/wallets/selectWallet';
import ProvideEntropy from './screen/wallets/provideEntropy';
import TransactionDetails from './screen/transactions/details';
import TransactionStatus from './screen/transactions/transactionStatus';
@ -161,6 +162,7 @@ const AddWalletRoot = () => (
<AddWalletStack.Screen name="ImportWallet" component={ImportWallet} options={ImportWallet.navigationOptions} />
<AddWalletStack.Screen name="PleaseBackup" component={PleaseBackup} options={PleaseBackup.navigationOptions} />
<AddWalletStack.Screen name="PleaseBackupLNDHub" component={PleaseBackupLNDHub} options={PleaseBackupLNDHub.navigationOptions} />
<AddWalletStack.Screen name="ProvideEntropy" component={ProvideEntropy} options={ProvideEntropy.navigationOptions} />
</AddWalletStack.Navigator>
);
@ -299,7 +301,7 @@ const Navigation = () => (
options={{ headerShown: false, animationEnabled: false }}
/>
<RootStack.Screen name="WalletsRoot" component={WalletsRoot} options={{ headerShown: false, animationEnabled: false }} />
<RootStack.Screen name="AddWalletRoot" component={AddWalletRoot} options={{ headerShown: false }} />
<RootStack.Screen name="AddWalletRoot" component={AddWalletRoot} options={{ headerShown: false, gestureEnabled: false }} />
<RootStack.Screen name="SendDetailsRoot" component={SendDetailsRoot} options={{ headerShown: false }} />
<RootStack.Screen name="LNDCreateInvoiceRoot" component={LNDCreateInvoiceRoot} options={{ headerShown: false }} />
<RootStack.Screen name="ScanLndInvoiceRoot" component={ScanLndInvoiceRoot} options={{ headerShown: false }} />

View file

@ -24,7 +24,6 @@
"_requiredBy": [
"/bc-ur"
],
"_resolved": "https://registry.npmjs.org/bc-bech32/-/bc-bech32-1.0.2.tgz",
"_spec": "1.0.2",
"_where": "/home/user/BlueWallet",
"description": "this library is for implementing [BlockChain Commons bc-32](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-0004-bc32.md)",

View file

@ -24,7 +24,6 @@
"_requiredBy": [
"/"
],
"_resolved": "https://registry.npmjs.org/bc-ur/-/bc-ur-0.1.6.tgz",
"_spec": "0.1.6",
"_where": "/home/user/BlueWallet",
"author": {

View file

@ -74,6 +74,12 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
this.secret = bip39.entropyToMnemonic(buf.toString('hex'));
}
async generateFromEntropy(user) {
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'));
}
_getExternalWIFByIndex(index) {
return this._getWIFByIndex(false, index);
}

View file

@ -47,6 +47,21 @@ export class LegacyWallet extends AbstractWallet {
this.secret = bitcoin.ECPair.makeRandom({ rng: () => buf }).toWIF();
}
async generateFromEntropy(user) {
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 = bitcoin.ECPair.fromPrivateKey(buf).toWIF();
return;
} catch (e) {
if (i === 5) throw e;
}
} while (true);
}
/**
*
* @returns {string}

BIN
img/coin1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
img/coin2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View file

@ -50,6 +50,9 @@
coming_soon: 'In die toekoms',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Beursiet',
@ -247,4 +250,9 @@
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,9 @@
coming_soon: 'Kuza ngokukhawuleza',
lightning: 'Umbane',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Ingxowa',
@ -250,4 +253,9 @@
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,9 @@ module.exports = {
coming_soon: 'Pròximament',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Detalls del moneder',
@ -247,4 +250,9 @@ module.exports = {
additional_info: 'Informació addicional',
open_direct_channel: 'Obrir un canal directe amb aquest node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -47,6 +47,9 @@ module.exports = {
coming_soon: 'Již brzy',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Peněženka',
@ -246,4 +249,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -47,6 +47,9 @@ module.exports = {
coming_soon: 'Kommer snart',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Wallet',
@ -247,4 +250,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -49,6 +49,9 @@ module.exports = {
coming_soon: 'Demnächst verfügbar',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Wallet',
@ -251,4 +254,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Direkten Kanal zu diesem Knoten eröffnen:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -50,6 +50,9 @@ module.exports = {
coming_soon: 'Σύντομα',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Πορτοφόλι',
@ -249,4 +252,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,9 @@ module.exports = {
coming_soon: 'Coming soon',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Wallet',
@ -247,4 +250,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,9 @@ module.exports = {
coming_soon: 'Viene pronto',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Detalles de la billetera',
@ -250,4 +253,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -50,6 +50,9 @@ module.exports = {
coming_soon: 'Tulossa',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Lompakko',
@ -249,4 +252,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -49,6 +49,9 @@ module.exports = {
coming_soon: 'Bientôt',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Portefeuille',
@ -249,4 +252,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -47,6 +47,9 @@ module.exports = {
coming_soon: 'Dolazi uskoro',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Volet',
@ -246,4 +249,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,9 @@ module.exports = {
coming_soon: 'Hamarosan',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Tárca',
@ -249,4 +252,9 @@ module.exports = {
additional_info: 'További információk',
open_direct_channel: 'Közvetlen csatorna nyitása erre a csomópontra:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,9 @@ module.exports = {
coming_soon: 'Akan datang',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Dompet',
@ -246,4 +249,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -50,6 +50,9 @@ module.exports = {
coming_soon: 'In arrivo',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Portafoglio',
@ -249,4 +252,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -47,6 +47,9 @@ module.exports = {
coming_soon: '準備中',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'ウォレット',
@ -248,4 +251,9 @@ module.exports = {
additional_info: '追加情報',
open_direct_channel: 'このノードの直接チャネルを作成:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,9 @@ module.exports = {
coming_soon: 'Kommer snart',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Lommebok',
@ -247,4 +250,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,9 @@ module.exports = {
coming_soon: 'Komt binnenkort',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Portemonnee',
@ -250,4 +253,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -50,6 +50,9 @@ module.exports = {
coming_soon: 'Em breve',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Carteira',
@ -253,4 +256,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,9 @@ module.exports = {
coming_soon: 'Brevemente',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'wallet',
@ -251,4 +254,9 @@ module.exports = {
additional_info: 'Informação adicional',
open_direct_channel: 'Abrir canal directo com este node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,10 @@ module.exports = {
coming_soon: 'Скоро будет',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Сгенерировать энторию с помощью игральных костей',
entropy_generated: '{gen} байтов сгенерированной энтории',
entropy_remain:
'{gen} байтов сгенерированной энтории. Оставшиеся {rem} байт будут получены из системного генератора случайных чисел.',
},
details: {
title: 'Информация о кошельке',
@ -251,4 +255,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Энтропия',
save: 'Сохранить',
undo: 'Отмена',
},
};

View file

@ -48,6 +48,9 @@ module.exports = {
coming_soon: 'Už čoskoro',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Peňaženka',
@ -246,4 +249,9 @@ module.exports = {
additional_info: 'Doplňujúce informácie',
open_direct_channel: 'Otvoriť priamy kanál s týmto uzlom:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,9 @@ module.exports = {
coming_soon: 'Kommer snart',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Plånbok',
@ -248,4 +251,9 @@ module.exports = {
additional_info: 'Ytterligare information',
open_direct_channel: 'Öppna en direkt kanal med denna nod:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -47,6 +47,9 @@ module.exports = {
coming_soon: 'เร็วๆนี้',
lightning: 'ไลท์นิง',
bitcoin: 'บิตคอยน์',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'กระเป๋าสตางค์',
@ -246,4 +249,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,9 @@ module.exports = {
coming_soon: 'Yakında',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Cüzdan',
@ -247,4 +250,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,9 @@ module.exports = {
coming_soon: 'Буде скоро',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Інформація про Гаманець',
@ -251,4 +254,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -48,6 +48,9 @@ module.exports = {
coming_soon: 'Coming soon',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: 'Wallet',
@ -247,4 +250,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -46,6 +46,9 @@ module.exports = {
coming_soon: '即将来临',
lightning: '闪电',
bitcoin: '比特币',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: '钱包',
@ -242,4 +245,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -46,6 +46,9 @@ module.exports = {
coming_soon: '即將來臨',
lightning: '閃電',
bitcoin: '比特幣',
entropy_provide: 'Provide entropy via dice rolls',
entropy_generated: '{gen} bytes of generated entropy',
entropy_remain: '{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.',
},
details: {
title: '錢包',
@ -242,4 +245,9 @@ module.exports = {
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
entropy: {
title: 'Entropy',
save: 'Save',
undo: 'Undo',
},
};

View file

@ -70,8 +70,8 @@
"@sentry/react-native": "1.4.5",
"amplitude-js": "5.11.0",
"assert": "1.5.0",
"bc-ur": "file:blue_modules/bc-ur",
"bc-bech32": "file:blue_modules/bc-bech32",
"bc-ur": "file:blue_modules/bc-ur",
"bech32": "1.1.3",
"bignumber.js": "9.0.0",
"bip21": "2.0.2",

View file

@ -70,8 +70,8 @@ const GeneralSettings = () => {
/>
<BlueCard>
<BlueText>
When enabled, you will see advanced options such as different wallet types and the ability to specify the LNDHub instance you
wish to connect to.
When enabled, you will see advanced options such as different wallet types, the ability to specify the LNDHub instance you wish
to connect to and custom entropy during wallet creation.
</BlueText>
</BlueCard>
<BlueSpacing20 />

View file

@ -166,6 +166,20 @@ export default class WalletsAdd extends Component {
);
}
let entropyTitle;
if (!this.state.entropy) {
entropyTitle = loc.wallets.add.entropy_provide;
} else if (this.state.entropy.length < 32) {
entropyTitle = loc.formatString(loc.wallets.add.entropy_remain, {
gen: this.state.entropy.length,
rem: 32 - this.state.entropy.length,
});
} else {
entropyTitle = loc.formatString(loc.wallets.add.entropy_generated, {
gen: this.state.entropy.length,
});
}
return (
<SafeBlueArea>
<StatusBar barStyle="light-content" />
@ -359,7 +373,18 @@ export default class WalletsAdd extends Component {
w.setLabel(this.state.label || loc.wallets.details.title);
}
if (this.state.activeBitcoin) {
await w.generate();
if (this.state.entropy) {
try {
await w.generateFromEntropy(this.state.entropy);
} catch (e) {
console.log(e.toString());
alert(e.toString());
this.props.navigation.goBack();
return;
}
} else {
await w.generate();
}
BlueApp.wallets.push(w);
await BlueApp.saveToDisk();
EV(EV.enum.WALLETS_COUNT_CHANGED);
@ -388,6 +413,15 @@ export default class WalletsAdd extends Component {
this.props.navigation.navigate('ImportWallet');
}}
/>
{this.state.isAdvancedOptionsEnabled && (
<BlueButtonLink
style={styles.import}
title={entropyTitle}
onPress={() => {
this.props.navigation.navigate('ProvideEntropy', { onGenerated: entropy => this.setState({ entropy }) });
}}
/>
)}
</View>
</KeyboardAvoidingView>
</ScrollView>

View file

@ -0,0 +1,375 @@
import React, { useReducer, useState } from 'react';
import PropTypes from 'prop-types';
import BN from 'bignumber.js';
import { Dimensions, View, ScrollView, Text, Image, TouchableOpacity, StyleSheet } from 'react-native';
import { Icon } from 'react-native-elements';
import { useNavigation, useRoute } from '@react-navigation/native';
import { SafeBlueArea, BlueNavigationStyle, BlueTabs } from '../../BlueComponents';
const loc = require('../../loc');
const BlueApp = require('../../BlueApp');
const ENTROPY_LIMIT = 256;
const shiftLeft = (value, places) => value.multipliedBy(2 ** places);
const shiftRight = (value, places) => value.div(2 ** places).dp(0, BN.ROUND_DOWN);
const initialState = { entropy: BN(0), bits: 0, items: [] };
export const eReducer = (state = initialState, action) => {
switch (action.type) {
case 'push': {
let { value, bits } = action;
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;
}
const entropy = shiftLeft(state.entropy, bits).plus(value);
const items = [...state.items, bits];
return { entropy, bits: state.bits + bits, items };
}
case 'pop': {
if (state.bits === 0) return state;
const bits = state.items.pop();
const entropy = shiftRight(state.entropy, bits);
return { entropy, bits: state.bits - bits, items: [...state.items] };
}
default:
return state;
}
};
export const entropyToHex = ({ entropy, bits }) => {
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) => {
if (base === 1) return null;
let maxPow = 1;
while (2 ** (maxPow + 1) <= base) {
maxPow += 1;
}
let bits = maxPow;
let summ = 0;
while (bits >= 1) {
const block = 2 ** bits;
if (number < summ + block) {
return { value: number - summ, bits };
}
summ += block;
bits -= 1;
}
return null;
};
// cut entropy to bytes, convert to Buffer
export const convertToBuffer = ({ entropy, bits }) => {
if (bits < 8) return Buffer.from([]);
const bytes = Math.floor(bits / 8);
// convert to byte array
let arr = [];
const ent = entropy.toString(16).split('').reverse();
ent.forEach((v, index) => {
if (index % 2 === 1) {
arr[0] = v + arr[0];
} else {
arr.unshift(v);
}
});
arr = 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];
}
return Buffer.from(arr);
};
const Coin = ({ push }) => (
<View style={styles.coinRoot}>
<TouchableOpacity onPress={() => push(getEntropy(0, 2))}>
<View style={styles.coinBody}>
<Image style={styles.coinImage} source={require('../../img/coin1.png')} />
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => push(getEntropy(1, 2))}>
<View style={styles.coinBody}>
<Image style={styles.coinImage} source={require('../../img/coin2.png')} />
</View>
</TouchableOpacity>
</View>
);
Coin.propTypes = {
push: PropTypes.func.isRequired,
};
const Dice = ({ push, sides }) => {
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 style={styles.diceScroll} contentContainerStyle={styles.diceContainer}>
{[...Array(sides)].map((_, i) => (
<TouchableOpacity key={i} onPress={() => push(getEntropy(i, sides))}>
<View style={styles.diceRoot}>
{sides === 6 ? (
<Icon style={styles.diceIcon} name={diceIcon(i + 1)} size={70} color="grey" type="font-awesome-5" />
) : (
<View style={styles.dice}>
<Text>{i + 1}</Text>
</View>
)}
</View>
</TouchableOpacity>
))}
</ScrollView>
);
};
Dice.propTypes = {
sides: PropTypes.number.isRequired,
push: PropTypes.func.isRequired,
};
const Buttons = ({ pop, save }) => (
<View style={styles.buttonsRoot}>
<TouchableOpacity onPress={pop}>
<View style={styles.buttonsBody}>
<View style={styles.buttonsRow}>
<View style={styles.buttonsIcon}>
<Icon name="undo" size={16} type="font-awesome" color={BlueApp.settings.buttonAlternativeTextColor} />
</View>
<Text style={styles.buttonsLabel}>{loc.entropy.undo}</Text>
</View>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={save}>
<View style={styles.buttonsBody}>
<View style={styles.buttonsRow}>
<View style={styles.buttonsIcon}>
<Icon name="arrow-down" size={16} type="font-awesome" color={BlueApp.settings.buttonAlternativeTextColor} />
</View>
<Text style={[styles.buttonsLabel, styles.buttonsLabelRight]}>{loc.entropy.save}</Text>
</View>
</View>
</TouchableOpacity>
</View>
);
Buttons.propTypes = {
pop: PropTypes.func.isRequired,
save: PropTypes.func.isRequired,
};
const Entropy = () => {
const [entropy, dispatch] = useReducer(eReducer, initialState);
const { onGenerated } = useRoute().params;
const navigation = useNavigation();
const [tab, setTab] = useState(1);
const [show, setShow] = useState(false);
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);
};
const hex = entropyToHex(entropy);
let bits = entropy.bits.toString();
bits = ' '.repeat(bits.length < 3 ? 3 - bits.length : 0) + bits;
return (
<SafeBlueArea>
<TouchableOpacity onPress={() => setShow(!show)}>
<View style={styles.entropy}>
<Text style={styles.entropyText}>{show ? hex : `${bits} of 256 bits`}</Text>
</View>
</TouchableOpacity>
<BlueTabs
active={tab}
onSwitch={setTab}
tabs={[
({ active }) => (
<Icon
name="toll"
type="material"
color={active ? BlueApp.settings.buttonAlternativeTextColor : BlueApp.settings.buttonBackgroundColor}
/>
),
({ active }) => (
<Icon
name="dice"
type="font-awesome-5"
color={active ? BlueApp.settings.buttonAlternativeTextColor : BlueApp.settings.buttonBackgroundColor}
/>
),
({ active }) => (
<Icon
name="dice-d20"
type="font-awesome-5"
color={active ? BlueApp.settings.buttonAlternativeTextColor : BlueApp.settings.buttonBackgroundColor}
/>
),
]}
/>
{tab === 0 && <Coin push={push} />}
{tab === 1 && <Dice sides={6} push={push} />}
{tab === 2 && <Dice sides={20} push={push} />}
<Buttons pop={pop} save={save} />
</SafeBlueArea>
);
};
Entropy.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func,
goBack: PropTypes.func,
}),
};
Entropy.navigationOptions = () => ({
...BlueNavigationStyle(),
title: loc.entropy.title,
});
const styles = StyleSheet.create({
entropy: {
padding: 5,
marginLeft: 10,
marginRight: 10,
backgroundColor: '#f2f2f2',
borderRadius: 9,
minHeight: 49,
paddingHorizontal: 8,
justifyContent: 'center',
flexDirection: 'row',
alignItems: 'center',
},
entropyText: {
fontSize: 15,
fontFamily: 'Courier',
},
coinRoot: {
flex: 1,
justifyContent: 'center',
flexDirection: 'row',
flexWrap: 'wrap',
backgroundColor: 'white',
},
coinBody: {
flex: 0.33,
justifyContent: 'center',
alignItems: 'center',
aspectRatio: 1,
borderWidth: 1,
borderRadius: 5,
borderColor: 'grey',
margin: 10,
},
coinImage: {
flex: 0.9,
aspectRatio: 1,
},
diceScroll: {
backgroundColor: 'white',
},
diceContainer: {
alignItems: 'flex-start',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
paddingBottom: 100,
},
diceRoot: {
width: Dimensions.get('window').width / 4,
aspectRatio: 1,
},
dice: {
margin: 3,
borderWidth: 1,
borderRadius: 5,
borderColor: 'grey',
justifyContent: 'center',
alignItems: 'center',
aspectRatio: 1,
},
diceIcon: {
margin: 3,
justifyContent: 'center',
alignItems: 'center',
aspectRatio: 1,
color: 'grey',
},
buttonsRoot: {
flexDirection: 'row',
alignSelf: 'center',
backgroundColor: 'transparent',
position: 'absolute',
bottom: 30,
borderRadius: 30,
minHeight: 48,
overflow: 'hidden',
},
buttonsBody: {
flex: 1,
minWidth: 130,
backgroundColor: BlueApp.settings.buttonBackgroundColor,
},
buttonsRow: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
buttonsIcon: {
minWidth: 30,
minHeight: 30,
left: 5,
backgroundColor: 'transparent',
transform: [{ rotate: '-45deg' }],
alignItems: 'center',
marginBottom: -11,
},
buttonsLabel: {
color: BlueApp.settings.buttonAlternativeTextColor,
fontWeight: '500',
left: 5,
backgroundColor: 'transparent',
paddingRight: 20,
},
buttonsLabelRight: {
paddingRight: 20,
},
});
export default Entropy;

View file

@ -1,31 +1,33 @@
/* global jest */
jest.mock('react-native-watch-connectivity', () => {
return {
getIsWatchAppInstalled: jest.fn(),
subscribeToMessages: jest.fn(),
updateApplicationContext: jest.fn(),
}
})
};
});
jest.mock('react-native-secure-key-store', () => {
return {
setResetOnAppUninstallTo: jest.fn(),
}
})
};
});
jest.mock('react-native-quick-actions', () => {
return {
clearShortcutItems: jest.fn(),
setQuickActions: jest.fn(),
isSupported: jest.fn(),
}
})
};
});
jest.mock('react-native-default-preference', () => {
return {
setName: jest.fn(),
set: jest.fn(),
}
})
};
});
jest.mock('react-native-fs', () => {
return {
@ -70,5 +72,7 @@ jest.mock('react-native-fs', () => {
TemporaryDirectoryPath: jest.fn(),
LibraryDirectoryPath: jest.fn(),
PicturesDirectoryPath: jest.fn(),
}
})
};
});
jest.mock('react-native-gesture-handler', () => jest.requireActual('react-native-gesture-handler/__mocks__/RNGestureHandlerModule.js'));

View file

@ -108,3 +108,31 @@ it('Legacy HD (BIP44) can generate addressess based on xpub', async function ()
assert.strictEqual(hd._getExternalAddressByIndex(1), '1QDCFcpnrZ4yrAQxmbvSgeUC9iZZ8ehcR5');
assert.strictEqual(hd._getInternalAddressByIndex(1), '13CW9WWBsWpDUvLtbFqYziWBWTYUoQb4nU');
});
it('can consume user generated entropy', async () => {
const hd = new HDSegwitP2SHWallet();
const zeroes = [...Array(32)].map(() => 0);
await hd.generateFromEntropy(Buffer.from(zeroes));
assert.strictEqual(
hd.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 () => {
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);
let secretWithoutChecksum = secret.split(' ');
secretWithoutChecksum.pop();
secretWithoutChecksum = secretWithoutChecksum.join(' ');
assert.strictEqual(
secretWithoutChecksum.endsWith('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon'),
false,
);
assert.ok(secret.split(' ').length === 12 || secret.split(' ').length === 24);
});

View file

@ -39,4 +39,33 @@ describe('Legacy wallet', () => {
assert.strictEqual(tx.outs.length, 1);
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
});
it("throws error if you can 't create wallet from this entropy", async () => {
const l = new LegacyWallet();
const zeroes = [...Array(32)].map(() => 0);
await assert.rejects(async () => await l.generateFromEntropy(Buffer.from(zeroes)), {
name: 'TypeError',
message: 'Private key not in range [1, n)',
});
});
it('can consume user generated entropy', async () => {
const l = new LegacyWallet();
const values = [...Array(32)].map(() => 1);
await l.generateFromEntropy(Buffer.from(values));
assert.strictEqual(l.getSecret(), 'KwFfNUhSDaASSAwtG7ssQM1uVX8RgX5GHWnnLfhfiQDigjioWXHH');
});
it('can fullfill user generated entropy if less than 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 = bitcoin.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);
});
});

View file

@ -0,0 +1,146 @@
/* global it, describe */
import assert from 'assert';
import { eReducer, entropyToHex, getEntropy, convertToBuffer } from '../../screen/wallets/provideEntropy';
describe('Entropy reducer and format', () => {
it('handles push and pop correctly', () => {
let state = eReducer(undefined, { type: null });
assert.equal(entropyToHex(state), '0x');
state = eReducer(state, { type: 'push', value: 0, bits: 1 });
assert.equal(entropyToHex(state), '0x0');
state = eReducer(state, { type: 'push', value: 0, bits: 1 });
assert.equal(entropyToHex(state), '0x0');
state = eReducer(state, { type: 'push', value: 0, bits: 3 });
assert.equal(entropyToHex(state), '0x00');
state = eReducer(state, { type: 'pop' });
assert.equal(entropyToHex(state), '0x0');
state = eReducer(state, { type: 'pop' });
state = eReducer(state, { type: 'pop' });
assert.equal(entropyToHex(state), '0x');
state = eReducer(state, { type: 'push', value: 1, bits: 1 });
assert.equal(entropyToHex(state), '0x1'); // 0b1
state = eReducer(state, { type: 'push', value: 0, bits: 1 });
assert.equal(entropyToHex(state), '0x2'); // 0b10
state = eReducer(state, { type: 'push', value: 0b01, bits: 2 });
assert.equal(entropyToHex(state), '0x9'); // 0b1001
state = eReducer(state, { type: '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 });
assert.equal(entropyToHex(state), '0x00000000000000000000000000000000');
});
it('handles 256 bits correctly', () => {
let state = eReducer(undefined, { type: null }); // get init state
// eslint-disable-next-line no-unused-vars
for (const i of [...Array(256)]) {
state = eReducer(state, { type: 'push', value: 1, bits: 1 });
}
assert.equal(entropyToHex(state), '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
});
it('handles pop when empty without error', () => {
const state = eReducer(undefined, { type: '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 });
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 }), {
name: 'TypeError',
message: "Can't push value exceeding size in bits",
});
});
});
describe('getEntropy function', () => {
it('handles coin', () => {
assert.deepEqual(getEntropy(0, 2), { value: 0, bits: 1 });
assert.deepEqual(getEntropy(1, 2), { value: 1, bits: 1 });
});
it('handles 6 sides dice', () => {
assert.deepEqual(getEntropy(0, 6), { value: 0, bits: 2 });
assert.deepEqual(getEntropy(1, 6), { value: 1, bits: 2 });
assert.deepEqual(getEntropy(2, 6), { value: 2, bits: 2 });
assert.deepEqual(getEntropy(3, 6), { value: 3, bits: 2 });
assert.deepEqual(getEntropy(4, 6), { value: 0, bits: 1 });
assert.deepEqual(getEntropy(5, 6), { value: 1, bits: 1 });
});
it('handles odd numbers', () => {
assert.deepEqual(getEntropy(0, 3), { value: 0, bits: 1 });
assert.deepEqual(getEntropy(1, 3), { value: 1, bits: 1 });
assert.deepEqual(getEntropy(2, 3), null);
});
});
describe('convertToBuffer function', () => {
it('zero bits', () => {
const state = eReducer(undefined, { type: null });
assert.deepEqual(convertToBuffer(state), Buffer.from([]));
});
it('8 zero bits', () => {
const state = eReducer(undefined, { type: '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 });
assert.deepEqual(convertToBuffer(state), Buffer.from([0b11111111]));
});
it('9 zero bits', () => {
const state = eReducer(undefined, { type: '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 });
assert.deepEqual(convertToBuffer(state), Buffer.from([0b11111111]));
});
it('9 bits', () => {
const state = eReducer(undefined, { type: '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 });
assert.deepEqual(convertToBuffer(state), Buffer.from([1, 2, 3]));
});
it('256 bits or 32bytes', () => {
let state = eReducer(undefined, { type: null }); // get init state
// eslint-disable-next-line no-unused-vars
for (const i of [...Array(256)]) {
state = eReducer(state, { type: 'push', value: 1, bits: 1 });
}
const bytes = [...Array(32)].map(() => 255);
assert.deepEqual(convertToBuffer(state), Buffer.from(bytes));
});
});