diff --git a/App.tsx b/App.tsx
index 5f1c42be5..a898cb374 100644
--- a/App.tsx
+++ b/App.tsx
@@ -143,6 +143,7 @@ import EditFee from './views/EditFee';
// Embedded LND
import Seed from './views/Settings/Seed';
import SeedRecovery from './views/Settings/SeedRecovery';
+import SeedQRExport from './views/Settings/SeedQRExport';
import Sync from './views/Sync';
import SyncRecovery from './views/SyncRecovery';
import LspExplanationFees from './views/Explanations/LspExplanationFees';
@@ -624,6 +625,10 @@ export default class App extends React.PureComponent {
name="SeedRecovery" // @ts-ignore:next-line
component={SeedRecovery}
/>
+
;
@@ -109,6 +111,15 @@ export default class Seed extends React.PureComponent {
);
+ const QRExport = () => (
+ navigation.navigate('SeedQRExport')}
+ style={{ marginLeft: 20 }}
+ >
+
+
+ );
+
return (
{
}}
rightComponent={
understood && seedPhrase ? (
-
+
+
+
+
) : undefined
}
navigation={navigation}
diff --git a/views/Settings/SeedQRExport.tsx b/views/Settings/SeedQRExport.tsx
new file mode 100644
index 000000000..7f33cb4f9
--- /dev/null
+++ b/views/Settings/SeedQRExport.tsx
@@ -0,0 +1,387 @@
+import React from 'react';
+import { StyleSheet, Text, View } from 'react-native';
+import { inject, observer } from 'mobx-react';
+import { StackNavigationProp } from '@react-navigation/stack';
+import { Tab } from 'react-native-elements';
+
+import BIP32Factory from 'bip32';
+import ecc from '../../zeus_modules/noble_ecc';
+
+// You must wrap a tiny-secp256k1 compatible implementation
+const bip32 = BIP32Factory(ecc);
+
+const aez = require('aez');
+const crc32 = require('fast-crc32c/impls/js_crc32c');
+const scrypt = require('scrypt-js').scrypt;
+
+import Button from '../../components/Button';
+import CollapsedQR from '../../components/CollapsedQR';
+import Header from '../../components/Header';
+import LoadingIndicator from '../../components/LoadingIndicator';
+import Screen from '../../components/Screen';
+import { ErrorMessage } from '../../components/SuccessErrorMessage';
+
+import stores from '../../stores/Stores';
+import SettingsStore from '../../stores/SettingsStore';
+
+import { BIP39_WORD_LIST } from '../../utils/Bip39Utils';
+import { themeColor } from '../../utils/ThemeUtils';
+import { localeString } from '../../utils/LocaleUtils';
+
+interface SeedQRExportProps {
+ navigation: StackNavigationProp;
+ SettingsStore: SettingsStore;
+}
+
+interface SeedQRExportState {
+ tab: number;
+ loading: boolean;
+ nodeBase58Segwit: string;
+ nodeBase58NativeSegwit: string;
+ error: string;
+}
+
+const AEZEED_DEFAULT_PASSPHRASE = 'aezeed',
+ AEZEED_VERSION = 0,
+ SCRYPT_N = 32768,
+ SCRYPT_R = 8,
+ SCRYPT_P = 1,
+ SCRYPT_KEY_LENGTH = 32,
+ ENCIPHERED_LENGTH = 33,
+ SALT_LENGTH = 5,
+ AD_LENGTH = SALT_LENGTH + 1,
+ AEZ_TAU = 4,
+ CHECKSUM_LENGTH = 4,
+ CHECKSUM_OFFSET = ENCIPHERED_LENGTH - CHECKSUM_LENGTH,
+ SALT_OFFSET = CHECKSUM_OFFSET - SALT_LENGTH;
+
+@inject('SettingsStore')
+@observer
+export default class SeedQRExport extends React.PureComponent<
+ SeedQRExportProps,
+ SeedQRExportState
+> {
+ state = {
+ tab: 0,
+ loading: true,
+ nodeBase58Segwit: '',
+ nodeBase58NativeSegwit: '',
+ error: ''
+ };
+
+ lpad(str: string, padString: string, length: number) {
+ while (str.length < length) {
+ str = padString + str;
+ }
+ return str;
+ }
+
+ getAD(salt: any) {
+ const ad = Buffer.alloc(AD_LENGTH, AEZEED_VERSION);
+ salt.copy(ad, 1);
+ return ad;
+ }
+
+ UNSAFE_componentWillMount() {
+ // make sure we have latest settings and the seed phrase is accessible
+ this.props.SettingsStore.getSettings().then(() => {
+ const { SettingsStore } = this.props;
+ const { seedPhrase }: any = SettingsStore;
+
+ const bits = seedPhrase
+ .map((word: string) => {
+ const index = BIP39_WORD_LIST.indexOf(word);
+ return this.lpad(index.toString(2), '0', 11);
+ })
+ .join('');
+
+ const seedBytes = bits
+ .match(/(.{1,8})/g)
+ .map((bin: string) => parseInt(bin, 2));
+ const seed = Buffer.from(seedBytes);
+ if (!seed || seed.length === 0 || seed[0] !== AEZEED_VERSION) {
+ this.setState({
+ loading: false,
+ error: 'Invalid seed or version!'
+ });
+ return;
+ }
+
+ const salt = seed.slice(SALT_OFFSET, SALT_OFFSET + SALT_LENGTH);
+
+ const password = Buffer.from(AEZEED_DEFAULT_PASSPHRASE, 'utf8');
+
+ const cipherSeed = seed.slice(1, SALT_OFFSET);
+
+ const checksum = seed.slice(CHECKSUM_OFFSET);
+
+ const newChecksum = crc32.calculate(seed.slice(0, CHECKSUM_OFFSET));
+ if (newChecksum !== checksum.readUInt32BE(0)) {
+ this.setState({
+ loading: false,
+ error: 'Invalid seed checksum!'
+ });
+ return;
+ }
+
+ try {
+ scrypt(
+ password,
+ salt,
+ SCRYPT_N,
+ SCRYPT_R,
+ SCRYPT_P,
+ SCRYPT_KEY_LENGTH
+ ).then((key: string) => {
+ if (key) {
+ const plainSeedBytes = aez.decrypt(
+ key,
+ null,
+ [this.getAD(salt)],
+ AEZ_TAU,
+ cipherSeed
+ );
+ if (plainSeedBytes == null) {
+ this.setState({
+ loading: false,
+ error: 'Decryption failed. Invalid passphrase?'
+ });
+ return;
+ } else {
+ // const version = plainSeedBytes.readUInt8(0);
+ // const birthday = plainSeedBytes.readUInt16BE(1);
+ const entropy = plainSeedBytes
+ .slice(3)
+ .toString('hex');
+
+ const SEGWIT_MAINNET = {
+ label: 'BTC (Bitcoin, SegWit, BIP49)',
+ config: {
+ messagePrefix:
+ '\u0018Bitcoin Signed Message:\n',
+ bech32: 'bc',
+ bip32: {
+ public: 0x049d7cb2,
+ private: 0x049d7878
+ },
+ pubKeyHash: 0,
+ scriptHash: 5,
+ wif: 128,
+ bip44: 0x00
+ }
+ };
+
+ const SEGWIT_TESTNET = {
+ label: 'BTC (Bitcoin Testnet, SegWit, BIP49)',
+ config: {
+ messagePrefix:
+ '\u0018Bitcoin Signed Message:\n',
+ bech32: 'tb',
+ bip32: {
+ public: 0x044a5262,
+ private: 0x044a4e28
+ },
+ pubKeyHash: 111,
+ scriptHash: 196,
+ wif: 239,
+ bip44: 0x01
+ }
+ };
+
+ const NATIVE_SEGWIT_MAINNET = {
+ label: 'BTC (Bitcoin, Native SegWit, BIP84)',
+ config: {
+ messagePrefix:
+ '\u0018Bitcoin Signed Message:\n',
+ bech32: 'bc',
+ bip32: {
+ public: 0x04b24746,
+ private: 0x04b2430c
+ },
+ pubKeyHash: 0,
+ scriptHash: 5,
+ wif: 128,
+ bip44: 0x00
+ }
+ };
+
+ const NATIVE_SEGWIT_TESTNET = {
+ label: 'BTC (Bitcoin Testnet, Native SegWit, BIP84)',
+ config: {
+ messagePrefix:
+ '\u0018Bitcoin Signed Message:\n',
+ bech32: 'tb',
+ bip32: {
+ public: 0x045f1cf6,
+ private: 0x045f18bc
+ },
+ pubKeyHash: 111,
+ scriptHash: 196,
+ wif: 239,
+ bip44: 0x01
+ }
+ };
+
+ const nodeBase58Segwit = bip32
+ .fromSeed(
+ Buffer.from(entropy, 'hex'),
+ stores.nodeInfoStore.nodeInfo.isTestNet
+ ? SEGWIT_TESTNET.config
+ : SEGWIT_MAINNET.config
+ )
+ .toBase58();
+
+ const nodeBase58NativeSegwit = bip32
+ .fromSeed(
+ Buffer.from(entropy, 'hex'),
+ stores.nodeInfoStore.nodeInfo.isTestNet
+ ? NATIVE_SEGWIT_TESTNET.config
+ : NATIVE_SEGWIT_MAINNET.config
+ )
+ .toBase58();
+
+ this.setState({
+ loading: false,
+ nodeBase58Segwit,
+ nodeBase58NativeSegwit
+ });
+ }
+ }
+ });
+ } catch (e) {
+ console.log('Scrypt err', e);
+ this.setState({
+ loading: false,
+ error: 'Error execution scrypt'
+ });
+ }
+ });
+ }
+
+ render() {
+ const { navigation } = this.props;
+ const {
+ tab,
+ loading,
+ nodeBase58Segwit,
+ nodeBase58NativeSegwit,
+ error
+ } = this.state;
+
+ return (
+
+
+ {error && }
+
+ {nodeBase58Segwit && (
+
+ this.setState({
+ tab: e
+ })
+ }
+ indicatorStyle={{
+ backgroundColor: themeColor('text'),
+ height: 3
+ }}
+ variant="primary"
+ >
+
+
+
+ )}
+
+ {tab === 0 && nodeBase58Segwit && (
+
+ )}
+ {tab === 1 && nodeBase58NativeSegwit && (
+
+ )}
+
+
+ {loading && (
+
+
+
+ {localeString(
+ 'views.Settings.SeedQRExport.pleaseWait'
+ )}
+
+
+ )}
+
+
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ tabTitleStyle: {
+ fontFamily: 'PPNeueMontreal-Book',
+ fontSize: 12
+ }
+});
diff --git a/yarn.lock b/yarn.lock
index a00a7b618..0436acf5a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3570,6 +3570,14 @@ aes-js@^3.1.2:
resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a"
integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==
+aez@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/aez/-/aez-1.0.1.tgz#e4efbef7113a92102f03754bfdf08380c4b0dec8"
+ integrity sha512-AtZEVcZcOLBAcNevz2e+Zu1zSLuPjtUA6CFY+ie8tF0IshKfcpJ0LiwnTqePOKaNidQQM/MfvtzUFOfAvHz5wQ==
+ dependencies:
+ blakejs "^1.1.0"
+ safe-buffer "^5.1.1"
+
ajv@^6.10.0, ajv@^6.12.4:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@@ -3998,6 +4006,13 @@ bignumber.js@^9.0.1:
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c"
integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==
+bindings@^1.3.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
+ integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
+ dependencies:
+ file-uri-to-path "1.0.0"
+
bip174@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.1.tgz#ef3e968cf76de234a546962bcf572cc150982f9f"
@@ -4058,6 +4073,11 @@ bl@~0.8.1:
dependencies:
readable-stream "~1.0.26"
+blakejs@^1.1.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814"
+ integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==
+
bluebird@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
@@ -5705,6 +5725,13 @@ fast-base64-decode@^1.0.0:
resolved "https://registry.yarnpkg.com/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz#b434a0dd7d92b12b43f26819300d2dafb83ee418"
integrity sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==
+fast-crc32c@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/fast-crc32c/-/fast-crc32c-2.0.0.tgz#1f7365ec5b47ec23bdfe15c99d13288c9285c6cb"
+ integrity sha512-LIREwygxtxzHF11oLJ4xIVKu/ZWNgrj/QaGvaSD8ZggIsgCyCtSYevlrpWVqNau57ZwezV8K1HFBSjQ7FcRbTQ==
+ optionalDependencies:
+ sse4_crc32 "^6.0.1"
+
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -5776,6 +5803,11 @@ file-entry-cache@^6.0.1:
dependencies:
flat-cache "^3.0.4"
+file-uri-to-path@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
+ integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
+
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
@@ -8009,6 +8041,11 @@ node-abort-controller@^3.1.1:
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548"
integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==
+node-addon-api@^1.3.0:
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d"
+ integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==
+
node-addon-api@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762"
@@ -9551,6 +9588,11 @@ scheduler@^0.23.0:
dependencies:
loose-envify "^1.1.0"
+scrypt-js@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312"
+ integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==
+
secp256k1@^4.0.2:
version "4.0.4"
resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.4.tgz#58f0bfe1830fe777d9ca1ffc7574962a8189f8ab"
@@ -9818,6 +9860,14 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
+sse4_crc32@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/sse4_crc32/-/sse4_crc32-6.0.1.tgz#3511c747ce48a224e0554ebb23d5835ba08a9637"
+ integrity sha512-FUTYXpLroqytNKWIfHzlDWoy9E4tmBB/RklNMy6w3VJs+/XEYAHgbiylg4SS43iOk/9bM0BlJ2EDpFAGT66IoQ==
+ dependencies:
+ bindings "^1.3.0"
+ node-addon-api "^1.3.0"
+
stack-utils@^2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f"