Unverified Commit 9e8d654a authored by Hanwen Cheng's avatar Hanwen Cheng Committed by GitHub
Browse files

feat: enable seed export for derived account (#638)

* add rust seed export function

* add get secret with ref function

* stash

* add rust function for derive mini secret key

* fix test on rust part

* add bindings on native part

* add js bindings

* add hard derivation check function and tests

* add test in rust for passworded keypairs secret

* new path secret screen

* add new hook for unlocking

* refactor and improve unlocking

* add genesisHash in the secret QR

* add e2e test and fix ios building

* fix rust test

* update os version
parent 53f255ff
Pipeline #96907 failed with stages
in 54 seconds
......@@ -233,6 +233,26 @@ public class EthkeyBridge extends ReactContextBaseJavaModule {
}
}
@ReactMethod
public void substrateSecretWithRef(double seedRef, String suriSuffix, Promise promise) {
try {
String derivedSubstrateSecret = ethkeySubstrateMiniSecretKeyWithRef(Double.doubleToRawLongBits(seedRef), suriSuffix);
promise.resolve(derivedSubstrateSecret);
} catch (Exception e) {
rejectWithException(promise, "substrate secret", e);
}
}
@ReactMethod
public void substrateSecret(String suri, Promise promise) {
try {
String derivedSubstrateSecret = ethkeySubstrateMiniSecretKey(suri);
promise.resolve(derivedSubstrateSecret);
} catch (Exception e) {
rejectWithException(promise, "substrate secret with ref", e);
}
}
private static native String ethkeyBrainwalletAddress(String seed);
private static native String ethkeyBrainwalletBIP39Address(String seed);
private static native String ethkeyBrainwalletSign(String seed, String message);
......@@ -255,4 +275,6 @@ public class EthkeyBridge extends ReactContextBaseJavaModule {
private static native String ethkeySubstrateBrainwalletSignWithRef(long seed_ref, String suriSuffix, String message);
private static native String ethkeySubstrateWalletAddressWithRef(long seedRef, String suriSuffix, int prefix);
private static native String ethkeyBrainWalletAddressWithRef(long seedRef);
private static native String ethkeySubstrateMiniSecretKey(String suri);
private static native String ethkeySubstrateMiniSecretKeyWithRef(long seedRef, String suriSuffix);
}
......@@ -58,4 +58,6 @@ RCT_EXTERN_METHOD(brainWalletSignWithRef:(int64_t)seedRef message:(NSString*)mes
RCT_EXTERN_METHOD(substrateSignWithRef:(int64_t)seedRef suri_suffix:(NSString*)suri_suffix message:(NSString*)message resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(substrateAddressWithRef:(int64_t)seedRef suri_suffix:(NSString*)suri_suffix prefix:(NSUInteger*)prefix resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(brainWalletAddressWithRef:(int64_t)seedRef resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(substrateSecret:(NSString*)suri resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(substrateSecretWithRef:(int64_t*)seedRef suri_suffix:(NSString*)suri_suffix resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
@end
......@@ -28,11 +28,11 @@ func handle_error<T, U>(
@objc(EthkeyBridge)
class EthkeyBridge: NSObject {
public static func requiresMainQueueSetup() -> Bool {
return true;
}
@objc func brainWalletAddress(_ seed: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -44,7 +44,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func brainWalletSign(_ seed: String, message: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -56,7 +56,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func rlpItem(_ rlp: String, position: UInt32, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -68,7 +68,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func keccak(_ data: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -80,7 +80,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func blake2b(_ data: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -92,7 +92,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func ethSign(_ data: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -104,7 +104,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func blockiesIcon(_ seed: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -116,7 +116,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func randomPhrase(_ wordsNumber:NSInteger, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -128,7 +128,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func encryptData(_ data: String, password: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -140,7 +140,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func decryptData(_ data: String, password: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -152,7 +152,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func qrCode(_ data: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -164,7 +164,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func qrCodeHex(_ data: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -176,7 +176,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func substrateAddress(_ seed: String, prefix: UInt32, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -188,7 +188,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func substrateSign(_ seed: String, message: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -200,7 +200,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func schnorrkelVerify(_ seed: String, message: String, signature: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -209,7 +209,7 @@ class EthkeyBridge: NSObject {
// return a bool. no cleanup
success: { return $0 })
}
@objc func decryptDataRef(_ data: String, password: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -218,7 +218,7 @@ class EthkeyBridge: NSObject {
// return a long. no cleanup
success: { return $0 })
}
@objc func destroyDataRef(_ data_ref: Int64, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -227,7 +227,7 @@ class EthkeyBridge: NSObject {
// return zero. no cleanup
success: { return 0 })
}
@objc func brainWalletSignWithRef(_ seed_ref: Int64, message: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -239,7 +239,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func substrateSignWithRef(_ seed_ref: Int64, suri_suffix: String, message: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -251,7 +251,7 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func brainWalletAddressWithRef(_ seed_ref: Int64, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -263,8 +263,8 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func substrateAddressWithRef(_ seed_ref: Int64, suri_suffix: String, prefix: UInt32, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
......@@ -276,4 +276,28 @@ class EthkeyBridge: NSObject {
return val
})
}
@objc func substrateSecret(_ suri: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
reject: reject,
get_result: { substrate_mini_secret_key($0, suri) },
success: { (res: Optional<UnsafePointer<CChar>>) -> String in
let val = String(cString: res!)
signer_destroy_string(res!)
return val
})
}
@objc func substrateSecretWithRef(_ seed_ref: Int64, suri_suffix: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
handle_error(
resolve: resolve,
reject: reject,
get_result: { substrate_mini_secret_key_with_ref($0, seed_ref, suri_suffix) },
success: { (res: Optional<UnsafePointer<CChar>>) -> String in
let val = String(cString: res!)
signer_destroy_string(res!)
return val
})
}
}
......@@ -133,7 +133,7 @@
"build": "yarn xcbuild:githubActions",
"type": "ios.simulator",
"device": {
"os": "iOS 13.4",
"os": "iOS 13.5",
"type": "iPhone 8"
}
},
......
......@@ -79,3 +79,7 @@ const char* substrate_brainwallet_sign_with_ref(struct ExternError*, int64_t see
const char* substrate_address_with_ref(struct ExternError*, int64_t seed_ref, const char* suri_suffix, const unsigned prefix);
const char* brain_wallet_address_with_ref(struct ExternError*, int64_t seed_ref);
const char* substrate_mini_secret_key_with_ref(struct ExternError*, int64_t seed_ref, const char* suri_suffix);
const char* substrate_mini_secret_key(struct ExternError*, const char* suri);
......@@ -18,12 +18,12 @@ use bip39::{Language, Mnemonic, MnemonicType};
use blake2_rfc::blake2b::blake2b;
use blockies::Ethereum;
use ethsign::{keyfile::Crypto, Protected};
use pixelate::{Color, Image, BLACK};
use pixelate::{BLACK, Color, Image};
use qrcodegen::{QrCode, QrCodeEcc};
use rlp::decode_list;
use rustc_hex::{FromHex, ToHex};
use tiny_keccak::keccak256 as keccak;
use tiny_keccak::Keccak;
use tiny_keccak::keccak256 as keccak;
use eth::{KeyPair, PhraseKind};
use result::{Error, Result};
......@@ -59,8 +59,8 @@ fn qrcode_bytes(data: &[u8]) -> crate::Result<String> {
width: qr.size() as usize,
scale: 16,
}
.render(&mut result)
.map_err(|e| crate::Error::Pixelate(e))?;
.render(&mut result)
.map_err(|e| crate::Error::Pixelate(e))?;
Ok(base64png(&result))
}
......@@ -219,6 +219,28 @@ export! {
Ok(keypair.ss58_address(prefix))
}
@Java_io_parity_signer_EthkeyBridge_ethkeySubstrateMiniSecretKey
fn substrate_mini_secret_key(
suri: &str
) -> crate::Result<String> {
let bytes = sr25519::KeyPair::get_derived_secret(&suri)
.ok_or(crate::Error::KeyPairIsNone)?;
Ok(bytes.to_hex())
}
@Java_io_parity_signer_EthkeyBridge_ethkeySubstrateMiniSecretKeyWithRef
fn substrate_mini_secret_key_with_ref (
seed_ref: i64,
suri_suffix: &str
) -> crate::Result<String> {
let seed = unsafe { Box::from_raw(seed_ref as *mut String) };
let suri = format!("{}{}", &seed, suri_suffix);
let bytes = sr25519::KeyPair::get_derived_secret(&suri)
.ok_or(crate::Error::KeyPairIsNone)?;
let _ = Box::into_raw(seed) as i64;
Ok(bytes.to_hex())
}
@Java_io_parity_signer_EthkeyBridge_substrateBrainwalletSign
fn substrate_brainwallet_sign(
suri: &str,
......@@ -334,6 +356,7 @@ mod tests {
static SEED_PHRASE: &str =
"grant jaguar wish bench exact find voice habit tank pony state salmon";
static SURI_SUFFIX: &str = "//hard/soft/0";
static SURI_SUFFIX_HARD: &str = "//hard";
static ENCRYPTED_SEED: &str = "{\"cipher\":\"aes-128-ctr\",\"cipherparams\":{\"iv\":\"47b4b75d13045ff7569da858e234f7ea\"},\"ciphertext\":\"ca1cf5387822b70392c4aeec729676f91ab00a795d7593fb7e52ecc333dbc4a1acbedc744b5d8d519c714e194bd741995244c8128bfdce6c184d6bda4ca136ed265eedcee9\",\"kdf\":\"pbkdf2\",\"kdfparams\":{\"c\":10240,\"dklen\":32,\"prf\":\"hmac-sha256\",\"salt\":\"b4a2d1edd1a70fe2eb48d7aff15c19e234f6aa211f5142dddb05a59af12b3381\"},\"mac\":\"b38a54eb382f2aa1a8be2f7b86fe040fe112d0f42fea03fac186dccdd7ae3eb9\"}";
static PIN: &str = "000000";
static SUBSTRATE_ADDRESS: &str = "5D4kaJXj5HVoBw2tFFsDj56BjZdPhXKxgGxZuKk4K3bKqHZ6";
......@@ -385,6 +408,27 @@ mod tests {
assert_eq!(expected, generated);
}
#[test]
fn test_substrate_secret() {
let data_pointer = decrypt_data_ref(ENCRYPTED_SEED, String::from(PIN)).unwrap();
let suri = format!("{}{}", SEED_PHRASE, SURI_SUFFIX_HARD);
let expected = "0c4a1f0e772497883ba79c484dfed441008c38572769ab40260a959127949665";
let generated = substrate_mini_secret_key(&suri).unwrap();
assert_eq!(expected, generated);
let passworded_suri = format!("{}///password", SURI_SUFFIX_HARD);
let generated_passworded_secret= substrate_mini_secret_key_with_ref(data_pointer, &passworded_suri).unwrap();
let expected_passworded_secret = "057687d479e550b1c0caca121db7e7519c573ebb6a7ce6f771213e41900181f6";
assert_eq!(expected_passworded_secret, generated_passworded_secret);
}
#[test]
fn test_substrate_secret_with_ref() {
let data_pointer = decrypt_data_ref(ENCRYPTED_SEED, String::from(PIN)).unwrap();
let expected = "0c4a1f0e772497883ba79c484dfed441008c38572769ab40260a959127949665";
let generated = substrate_mini_secret_key_with_ref(data_pointer, SURI_SUFFIX_HARD).unwrap();
assert_eq!(expected, generated);
}
#[test]
fn test_substrate_brainwallet_address_suri() {
let suri = format!("{}{}", SEED_PHRASE, SURI_SUFFIX);
......
use base58::ToBase58;
use bip39::{Language, Mnemonic};
use codec::{Decode, Encode};
use lazy_static::lazy_static;
use regex::Regex;
use schnorrkel::{ExpansionMode, MiniSecretKey, SecretKey, Signature};
use schnorrkel::derive::{ChainCode, Derivation};
use schnorrkel::{ExpansionMode, SecretKey, Signature};
use substrate_bip39::mini_secret_from_entropy;
use lazy_static::lazy_static;
pub struct KeyPair(schnorrkel::Keypair);
const SIGNING_CTX: &[u8] = b"substrate";
const JUNCTION_ID_LEN: usize = 32;
const CHAIN_CODE_LENGTH: usize = 32;
const MINI_SECRET_KEY_LENGTH: usize = 32;
impl KeyPair {
pub fn from_bip39_phrase(phrase: &str, password: Option<&str>) -> Option<KeyPair> {
......@@ -23,6 +25,46 @@ impl KeyPair {
))
}
fn derive_secret_key(&self, path: impl Iterator<Item=DeriveJunction>) -> Option<MiniSecretKey> {
let mut result: SecretKey = self.0.secret.clone();
let mut path_peekable = path.peekable();
let mut derived_result: Option<MiniSecretKey> = None;
while let Some(derive_junction) = path_peekable.next() {
if path_peekable.peek().is_some() {
result = match derive_junction {
DeriveJunction::Soft(cc) => result.derived_key_simple(ChainCode(cc), &[]).0,
DeriveJunction::Hard(cc) => derive_hard_junction(&result, cc),
}
}
let last_chain_code = derive_junction.unwrap_inner();
let (derived_mini_secret_key, _) = result.hard_derive_mini_secret_key(Some(ChainCode(last_chain_code)), b"");
derived_result = Some(derived_mini_secret_key);
}
derived_result
}
pub fn get_derived_secret(suri: &str) -> Option<[u8; MINI_SECRET_KEY_LENGTH]> {
lazy_static! {
static ref RE_SURI: Regex = {
Regex::new(r"^(?P<phrase>\w+( \w+)*)?(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$")
.expect("constructed from known-good static value; qed")
};
static ref RE_JUNCTION: Regex =
Regex::new(r"/(/?[^/]+)").expect("constructed from known-good static value; qed");
}
let cap = RE_SURI.captures(suri)?;
let paths = RE_JUNCTION
.captures_iter(&cap["path"])
.map(|j| DeriveJunction::from(&j[1]));
let pair = Self::from_bip39_phrase(
cap.name("phrase").map(|p| p.as_str())?,
cap.name("password").map(|p| p.as_str()),
)?;
let mini_secret_key = pair.derive_secret_key(paths)?;
Some(*mini_secret_key.as_bytes())
}
// Should match implementation at https://github.com/paritytech/substrate/blob/master/core/primitives/src/crypto.rs#L653-L682
pub fn from_suri(suri: &str) -> Option<KeyPair> {
lazy_static! {
......@@ -33,21 +75,18 @@ impl KeyPair {
static ref RE_JUNCTION: Regex =
Regex::new(r"/(/?[^/]+)").expect("constructed from known-good static value; qed");
}
let cap = RE_SURI.captures(suri)?;
let path = RE_JUNCTION
let paths = RE_JUNCTION
.captures_iter(&cap["path"])
.map(|j| DeriveJunction::from(&j[1]));
let pair = Self::from_bip39_phrase(
cap.name("phrase").map(|p| p.as_str())?,
cap.name("password").map(|p| p.as_str()),
)?;
Some(pair.derive(path))
Some(pair.derive(paths))
}
fn derive(&self, path: impl Iterator<Item = DeriveJunction>) -> Self {
fn derive(&self, path: impl Iterator<Item=DeriveJunction>) -> Self {
let init = self.0.secret.clone();
let result = path.fold(init, |acc, j| match j {
DeriveJunction::Soft(cc) => acc.derived_key_simple(ChainCode(cc), &[]).0,
......
......@@ -25,6 +25,7 @@ interface Props {
size?: number;
height?: number;
style?: ViewStyle;
testID?: string;
}
export default function QrView(props: Props): React.ReactElement {
......@@ -69,6 +70,7 @@ export default function QrView(props: Props): React.ReactElement {
},
props.style
]}
testID={props.testID}
>
{qr !== '' && (
<Image source={{ uri: qr }} style={{ height: size, width: size }} />
......
......@@ -20,7 +20,7 @@ import { StyleSheet, Text, View } from 'react-native';
import colors from 'styles/colors';
import fonts from 'styles/fonts';
export default function UnknownAccountWarning({
export function UnknownAccountWarning({
isPath
}: {
isPath?: boolean;
......@@ -62,6 +62,21 @@ export default function UnknownAccountWarning({
);
}
export function PasswordedAccountExportWarning(): React.ReactElement {
return (
<View style={styles.warningView}>
<Text style={styles.warningTitle}>Warning</Text>
<Text style={styles.warningText}>
The secret is generated with the input password.
{'\n'}
{'\n'}
Please make sure you have input the correct password, a wrong password
will lead to a different QR code.
</Text>
</View>
);
}
const styles = StyleSheet.create({
warningText: {
color: colors.text.main,
......
......@@ -49,6 +49,7 @@ import LegacyNetworkChooser from 'screens/LegacyNetworkChooser';
import PathDerivation from 'screens/PathDerivation';
import PathDetails from 'screens/PathDetails';
import PathManagement from 'screens/PathManagement';
import PathSecret from 'screens/PathSecret';
import PathsList from 'screens/PathsList';
import PrivacyPolicy from 'screens/PrivacyPolicy';
import QrScanner from 'modules/sign/screens/QrScanner';
......@@ -150,6 +151,7 @@ export const AppNavigator = (): React.ReactElement => (
<ScreenStack.Screen name="PathDerivation" component={PathDerivation} />
<ScreenStack.Screen name="PathDetails" component={PathDetails} />
<ScreenStack.Screen name="PathsList" component={PathsList} />
<ScreenStack.Screen name="PathSecret" component={PathSecret} />
<ScreenStack.Screen name="PathManagement" component={PathManagement} />
<ScreenStack.Screen name="PinNew" component={PinNew} />
<ScreenStack.Screen name="PinUnlock" component={PinUnlock} />
......
......@@ -29,7 +29,7 @@ import {
navigateToLegacyAccountList
} from 'utils/navigationHelpers';
import fontStyles from 'styles/fontStyles';
import UnknownAccountWarning from 'components/UnknownAccountWarning';
import { UnknownAccountWarning } from 'components/Warnings';
import { withAccountStore } from 'utils/HOC';
import AccountIcon from 'components/AccountIcon';
import { NavigationAccountProps } from 'types/props';
......
......@@ -36,15 +36,17 @@ import {
getNetworkKey,
getPathName,
getPathsWithSubstrateNetworkKey,
isSubstrateHardDerivedPath,
isSubstratePath
} from 'utils/identitiesUtils';
import { alertDeleteAccount, alertPathDeletionError } from 'utils/alertUtils';
import {
navigateToPathDerivation,
navigateToPathsList
navigateToPathsList,
useUnlockSeed
} from 'utils/navigationHelpers';
import { generateAccountId } from 'utils/account';
import UnknownAccountWarning from 'components/UnknownAccountWarning';
import { UnknownAccountWarning } from 'components/Warnings';
import { useSeedRef } from 'utils/seedRefHooks';
import QrScannerTab from 'components/QrScannerTab';
......@@ -67,6 +69,7 @@ export function PathDetailsView({
const address = getAddressWithPath(path, currentIdentity);
const accountName = getPathName(path, currentIdentity);
const { isSeedRefValid } = useSeedRef(currentIdentity.encryptedSeed);
const { unlockWithoutPassword, unlockWithPassword } = useUnlockSeed();
if (!address) return <View />;
const isUnknownNetwork = networkKey === UnknownNetworkKeys.UNKNOWN;
const formattedNetworkKey = isUnknownNetwork ? defaultNetworkKey : networkKey;
......@@ -75,7 +78,7 @@ export function PathDetailsView({
networkKey: formattedNetworkKey
});
const onOptionSelect = (value: string): void => {
const onOptionSelect = async (value: string): Promise<void> => {
switch (value) {
case 'PathDelete':
alertDeleteAccount('this account', async () => {
......@@ -98,6 +101,27 @@ export function PathDetailsView({
}
});
break;
case 'PathSecret': {
const pathMeta = currentIdentity.meta.get(path)!;
if (pathMeta.hasPassword) {
await unlockWithPassword(
password => ({
name: 'PathSecret',
params: {
password,
path
}
}),
isSeedRefValid
);
} else {
await unlockWithoutPassword(
{ name: 'PathSecret', params: { path } },
isSeedRefValid
);
}
break;
}
case 'PathDerivation':
navigateToPathDerivation(navigation, path, isSeedRefValid);
break;
......@@ -125,6 +149,12 @@ export function PathDetailsView({
text: 'Derive Account',
value: 'PathDerivation'
},
{