Compare commits
1 Commits
dev_pr_tes
...
218b2e8b29
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
218b2e8b29 |
@@ -14,9 +14,11 @@ import {
|
|||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
import { Ionicons, Feather, MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons';
|
import { Ionicons, Feather, MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons';
|
||||||
import { colors, typography, spacing, borderRadius, shadows } from '../theme/colors';
|
import { colors, typography, spacing, borderRadius, shadows } from '../theme/colors';
|
||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
import { Heir, HeirStatus, PaymentStrategy } from '../types';
|
import { Heir, HeirStatus, PaymentStrategy } from '../types';
|
||||||
import HeritageScreen from './HeritageScreen';
|
import HeritageScreen from './HeritageScreen';
|
||||||
|
import { VAULT_STORAGE_KEYS } from './SentinelScreen';
|
||||||
|
|
||||||
// Mock heirs data
|
// Mock heirs data
|
||||||
const initialHeirs: Heir[] = [
|
const initialHeirs: Heir[] = [
|
||||||
@@ -308,6 +310,18 @@ export default function MeScreen() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleResetVault = async () => {
|
||||||
|
try {
|
||||||
|
await AsyncStorage.multiRemove([
|
||||||
|
VAULT_STORAGE_KEYS.INITIALIZED,
|
||||||
|
VAULT_STORAGE_KEYS.SHARE_DEVICE,
|
||||||
|
]);
|
||||||
|
Alert.alert('Done', 'Vault state reset. Go to Sentinel → Open Shadow Vault to see first-time flow.');
|
||||||
|
} catch (e) {
|
||||||
|
Alert.alert('Error', 'Failed to reset vault state.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
@@ -885,6 +899,21 @@ export default function MeScreen() {
|
|||||||
<Text style={styles.sanctumValue}>View</Text>
|
<Text style={styles.sanctumValue}>View</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{__DEV__ && (
|
||||||
|
<View style={styles.sanctumSection}>
|
||||||
|
<Text style={styles.tideLabel}>DEV ONLY</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.devResetButton}
|
||||||
|
onPress={handleResetVault}
|
||||||
|
activeOpacity={0.85}
|
||||||
|
>
|
||||||
|
<Ionicons name="refresh" size={16} color={colors.nautical.coral} />
|
||||||
|
<Text style={styles.devResetText}>Reset Vault State</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.sanctumHint}>Clear hasVaultInitialized & Share A. Test first-open flow.</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
<View style={styles.tideModalButtons}>
|
<View style={styles.tideModalButtons}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -1814,6 +1843,23 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: typography.fontSize.sm,
|
fontSize: typography.fontSize.sm,
|
||||||
color: colors.me.textSecondary,
|
color: colors.me.textSecondary,
|
||||||
},
|
},
|
||||||
|
devResetButton: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: spacing.sm,
|
||||||
|
paddingVertical: spacing.sm,
|
||||||
|
paddingHorizontal: spacing.md,
|
||||||
|
backgroundColor: `${colors.nautical.coral}15`,
|
||||||
|
borderRadius: borderRadius.lg,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: `${colors.nautical.coral}40`,
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
},
|
||||||
|
devResetText: {
|
||||||
|
fontSize: typography.fontSize.sm,
|
||||||
|
color: colors.nautical.coral,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
sanctumAlert: {
|
sanctumAlert: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
|
|||||||
import { colors, typography, spacing, borderRadius, shadows } from '../theme/colors';
|
import { colors, typography, spacing, borderRadius, shadows } from '../theme/colors';
|
||||||
import { SystemStatus, KillSwitchLog } from '../types';
|
import { SystemStatus, KillSwitchLog } from '../types';
|
||||||
import VaultScreen from './VaultScreen';
|
import VaultScreen from './VaultScreen';
|
||||||
|
import BiometricModal from '../components/common/BiometricModal';
|
||||||
import {
|
import {
|
||||||
SSSShare,
|
SSSShare,
|
||||||
mnemonicToEntropy,
|
mnemonicToEntropy,
|
||||||
@@ -31,6 +32,12 @@ import {
|
|||||||
verifyShares,
|
verifyShares,
|
||||||
} from '../utils/sss';
|
} from '../utils/sss';
|
||||||
|
|
||||||
|
// Vault storage keys (for testing: clear these to simulate first-open)
|
||||||
|
export const VAULT_STORAGE_KEYS = {
|
||||||
|
INITIALIZED: 'sentinel_vault_initialized',
|
||||||
|
SHARE_DEVICE: 'sentinel_share_device',
|
||||||
|
} as const;
|
||||||
|
|
||||||
// Nautical-themed mnemonic word list (unique words only)
|
// Nautical-themed mnemonic word list (unique words only)
|
||||||
const MNEMONIC_WORDS = [
|
const MNEMONIC_WORDS = [
|
||||||
'anchor', 'harbor', 'compass', 'lighthouse', 'current', 'ocean', 'tide', 'voyage',
|
'anchor', 'harbor', 'compass', 'lighthouse', 'current', 'ocean', 'tide', 'voyage',
|
||||||
@@ -166,9 +173,19 @@ export default function SentinelScreen() {
|
|||||||
const [sssShares, setSssShares] = useState<SSSShare[]>([]);
|
const [sssShares, setSssShares] = useState<SSSShare[]>([]);
|
||||||
const [showEmailForm, setShowEmailForm] = useState(false);
|
const [showEmailForm, setShowEmailForm] = useState(false);
|
||||||
const [emailAddress, setEmailAddress] = useState('');
|
const [emailAddress, setEmailAddress] = useState('');
|
||||||
|
const [emailRecipientType, setEmailRecipientType] = useState<'self' | 'heir'>('self');
|
||||||
const [isCapturing, setIsCapturing] = useState(false);
|
const [isCapturing, setIsCapturing] = useState(false);
|
||||||
|
const [hasVaultInitialized, setHasVaultInitialized] = useState<boolean | null>(null);
|
||||||
|
const [showSetupBiometric, setShowSetupBiometric] = useState(false);
|
||||||
const mnemonicRef = useRef<View>(null);
|
const mnemonicRef = useRef<View>(null);
|
||||||
|
|
||||||
|
// Load vault init status on mount (1.1)
|
||||||
|
useEffect(() => {
|
||||||
|
AsyncStorage.getItem(VAULT_STORAGE_KEYS.INITIALIZED)
|
||||||
|
.then((val) => setHasVaultInitialized(val === 'true'))
|
||||||
|
.catch(() => setHasVaultInitialized(false));
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Pulse animation
|
// Pulse animation
|
||||||
const pulseAnimation = Animated.loop(
|
const pulseAnimation = Animated.loop(
|
||||||
@@ -222,7 +239,7 @@ export default function SentinelScreen() {
|
|||||||
};
|
};
|
||||||
}, [pulseAnim, glowAnim, rotateAnim]);
|
}, [pulseAnim, glowAnim, rotateAnim]);
|
||||||
|
|
||||||
const openVaultWithMnemonic = () => {
|
const startFirstTimeSetup = () => {
|
||||||
const words = generateMnemonic();
|
const words = generateMnemonic();
|
||||||
const shares = generateSSSShares(words);
|
const shares = generateSSSShares(words);
|
||||||
setMnemonicWords(words);
|
setMnemonicWords(words);
|
||||||
@@ -231,12 +248,40 @@ export default function SentinelScreen() {
|
|||||||
setShowVault(false);
|
setShowVault(false);
|
||||||
setShowEmailForm(false);
|
setShowEmailForm(false);
|
||||||
setEmailAddress('');
|
setEmailAddress('');
|
||||||
|
setEmailRecipientType('self');
|
||||||
|
|
||||||
// Store Share A (device share) locally
|
|
||||||
if (shares[0]) {
|
if (shares[0]) {
|
||||||
AsyncStorage.setItem('sentinel_share_device', serializeShare(shares[0])).catch(() => {
|
AsyncStorage.setItem(VAULT_STORAGE_KEYS.SHARE_DEVICE, serializeShare(shares[0])).catch(() => {});
|
||||||
// Best-effort local store; UI remains available
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const completeSetupAndEnterVault = () => {
|
||||||
|
setShowMnemonic(false);
|
||||||
|
setShowEmailForm(false);
|
||||||
|
setEmailAddress('');
|
||||||
|
setShowSetupBiometric(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetupBiometricSuccess = () => {
|
||||||
|
setShowSetupBiometric(false);
|
||||||
|
AsyncStorage.setItem(VAULT_STORAGE_KEYS.INITIALIZED, 'true').catch(() => {});
|
||||||
|
setHasVaultInitialized(true);
|
||||||
|
setShowVault(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetupBiometricSkip = () => {
|
||||||
|
setShowSetupBiometric(false);
|
||||||
|
AsyncStorage.setItem(VAULT_STORAGE_KEYS.INITIALIZED, 'true').catch(() => {});
|
||||||
|
setHasVaultInitialized(true);
|
||||||
|
setShowVault(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenVault = () => {
|
||||||
|
if (hasVaultInitialized === true) {
|
||||||
|
setShowVault(true);
|
||||||
|
setShowMnemonic(false);
|
||||||
|
} else {
|
||||||
|
startFirstTimeSetup();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -252,8 +297,7 @@ export default function SentinelScreen() {
|
|||||||
url: uri,
|
url: uri,
|
||||||
message: 'Sentinel key backup',
|
message: 'Sentinel key backup',
|
||||||
});
|
});
|
||||||
setShowMnemonic(false);
|
completeSetupAndEnterVault();
|
||||||
setShowVault(true);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Alert.alert('Screenshot failed', 'Please try again or use email backup.');
|
Alert.alert('Screenshot failed', 'Please try again or use email backup.');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -265,6 +309,10 @@ export default function SentinelScreen() {
|
|||||||
setShowEmailForm(true);
|
setShowEmailForm(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCompleteBackupLocal = () => {
|
||||||
|
completeSetupAndEnterVault();
|
||||||
|
};
|
||||||
|
|
||||||
const handleSendEmail = async () => {
|
const handleSendEmail = async () => {
|
||||||
const trimmed = emailAddress.trim();
|
const trimmed = emailAddress.trim();
|
||||||
if (!trimmed || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
|
if (!trimmed || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
|
||||||
@@ -272,16 +320,21 @@ export default function SentinelScreen() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const subject = encodeURIComponent('Sentinel Vault Recovery Key');
|
const subject = encodeURIComponent(
|
||||||
const body = encodeURIComponent(`Your 12-word mnemonic:\n${mnemonicWords.join(' ')}`);
|
emailRecipientType === 'heir'
|
||||||
|
? 'Sentinel Vault - Share C (Heir)'
|
||||||
|
: 'Sentinel Vault Recovery Key'
|
||||||
|
);
|
||||||
|
const body = encodeURIComponent(
|
||||||
|
emailRecipientType === 'heir'
|
||||||
|
? `Share C (for heir, 2-of-3 required to recover):\n${sssShares[2] ? serializeShare(sssShares[2]) : ''}\n\nKeep this secure. Combined with Share B from Sentinel cloud, this can restore the vault.`
|
||||||
|
: `Your 12-word mnemonic (backup for yourself):\n${mnemonicWords.join(' ')}`
|
||||||
|
);
|
||||||
const mailtoUrl = `mailto:${trimmed}?subject=${subject}&body=${body}`;
|
const mailtoUrl = `mailto:${trimmed}?subject=${subject}&body=${body}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Linking.openURL(mailtoUrl);
|
await Linking.openURL(mailtoUrl);
|
||||||
setShowMnemonic(false);
|
completeSetupAndEnterVault();
|
||||||
setShowEmailForm(false);
|
|
||||||
setEmailAddress('');
|
|
||||||
setShowVault(true);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Alert.alert('Email failed', 'Unable to open email client.');
|
Alert.alert('Email failed', 'Unable to open email client.');
|
||||||
}
|
}
|
||||||
@@ -450,7 +503,7 @@ export default function SentinelScreen() {
|
|||||||
</View>
|
</View>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.vaultAccessButton}
|
style={styles.vaultAccessButton}
|
||||||
onPress={openVaultWithMnemonic}
|
onPress={handleOpenVault}
|
||||||
activeOpacity={0.8}
|
activeOpacity={0.8}
|
||||||
accessibilityLabel="Open Shadow Vault"
|
accessibilityLabel="Open Shadow Vault"
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
@@ -535,6 +588,12 @@ export default function SentinelScreen() {
|
|||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
style={styles.mnemonicOverlay}
|
style={styles.mnemonicOverlay}
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||||
|
>
|
||||||
|
<ScrollView
|
||||||
|
style={styles.mnemonicScroll}
|
||||||
|
contentContainerStyle={styles.mnemonicScrollContent}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
>
|
>
|
||||||
<View ref={mnemonicRef} collapsable={false}>
|
<View ref={mnemonicRef} collapsable={false}>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
@@ -602,18 +661,36 @@ export default function SentinelScreen() {
|
|||||||
style={styles.mnemonicSecondaryButton}
|
style={styles.mnemonicSecondaryButton}
|
||||||
onPress={handleEmailBackup}
|
onPress={handleEmailBackup}
|
||||||
activeOpacity={0.85}
|
activeOpacity={0.85}
|
||||||
accessibilityLabel="Send mnemonic backup via email"
|
accessibilityLabel="Send backup via email"
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
>
|
>
|
||||||
<Text style={styles.mnemonicSecondaryText}>EMAIL BACKUP</Text>
|
<Text style={styles.mnemonicSecondaryText}>EMAIL BACKUP</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
{showEmailForm ? (
|
{showEmailForm ? (
|
||||||
<View style={styles.emailForm}>
|
<View style={styles.emailForm}>
|
||||||
|
<View style={styles.emailTypeRow}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.emailTypeButton, emailRecipientType === 'self' && styles.emailTypeButtonActive]}
|
||||||
|
onPress={() => setEmailRecipientType('self')}
|
||||||
|
>
|
||||||
|
<Text style={[styles.emailTypeText, emailRecipientType === 'self' && styles.emailTypeTextActive]}>
|
||||||
|
To Myself
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.emailTypeButton, emailRecipientType === 'heir' && styles.emailTypeButtonActive]}
|
||||||
|
onPress={() => setEmailRecipientType('heir')}
|
||||||
|
>
|
||||||
|
<Text style={[styles.emailTypeText, emailRecipientType === 'heir' && styles.emailTypeTextActive]}>
|
||||||
|
To Heir
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.emailInput}
|
style={styles.emailInput}
|
||||||
value={emailAddress}
|
value={emailAddress}
|
||||||
onChangeText={setEmailAddress}
|
onChangeText={setEmailAddress}
|
||||||
placeholder="you@email.com"
|
placeholder={emailRecipientType === 'heir' ? 'heir@email.com' : 'you@email.com'}
|
||||||
placeholderTextColor={colors.sentinel.textSecondary}
|
placeholderTextColor={colors.sentinel.textSecondary}
|
||||||
keyboardType="email-address"
|
keyboardType="email-address"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
@@ -630,10 +707,30 @@ export default function SentinelScreen() {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.mnemonicTertiaryButton}
|
||||||
|
onPress={handleCompleteBackupLocal}
|
||||||
|
activeOpacity={0.85}
|
||||||
|
accessibilityLabel="I've completed backup locally"
|
||||||
|
accessibilityRole="button"
|
||||||
|
>
|
||||||
|
<Text style={styles.mnemonicTertiaryText}>COMPLETE BACKUP (LOCAL)</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
</View>
|
</View>
|
||||||
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* Biometric setup prompt after first-time backup (1.4) */}
|
||||||
|
<BiometricModal
|
||||||
|
visible={showSetupBiometric}
|
||||||
|
onSuccess={handleSetupBiometricSuccess}
|
||||||
|
onCancel={handleSetupBiometricSkip}
|
||||||
|
title="Quick Unlock"
|
||||||
|
message="Enable Face ID / Touch ID for faster access next time?"
|
||||||
|
isDark
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -900,6 +997,13 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
padding: spacing.lg,
|
padding: spacing.lg,
|
||||||
},
|
},
|
||||||
|
mnemonicScroll: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
mnemonicScrollContent: {
|
||||||
|
flexGrow: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
mnemonicCard: {
|
mnemonicCard: {
|
||||||
borderRadius: borderRadius.xl,
|
borderRadius: borderRadius.xl,
|
||||||
padding: spacing.lg,
|
padding: spacing.lg,
|
||||||
@@ -1013,6 +1117,47 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
letterSpacing: typography.letterSpacing.wide,
|
letterSpacing: typography.letterSpacing.wide,
|
||||||
},
|
},
|
||||||
|
mnemonicTertiaryButton: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
paddingVertical: spacing.sm,
|
||||||
|
borderRadius: borderRadius.full,
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: spacing.sm,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.sentinel.cardBorder,
|
||||||
|
borderStyle: 'dashed',
|
||||||
|
},
|
||||||
|
mnemonicTertiaryText: {
|
||||||
|
color: colors.sentinel.textSecondary,
|
||||||
|
fontWeight: '600',
|
||||||
|
letterSpacing: typography.letterSpacing.wide,
|
||||||
|
fontSize: typography.fontSize.sm,
|
||||||
|
},
|
||||||
|
emailTypeRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: spacing.sm,
|
||||||
|
marginBottom: spacing.sm,
|
||||||
|
},
|
||||||
|
emailTypeButton: {
|
||||||
|
flex: 1,
|
||||||
|
paddingVertical: spacing.sm,
|
||||||
|
borderRadius: borderRadius.lg,
|
||||||
|
alignItems: 'center',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.sentinel.cardBorder,
|
||||||
|
},
|
||||||
|
emailTypeButtonActive: {
|
||||||
|
borderColor: colors.sentinel.primary,
|
||||||
|
backgroundColor: `${colors.sentinel.primary}15`,
|
||||||
|
},
|
||||||
|
emailTypeText: {
|
||||||
|
fontSize: typography.fontSize.sm,
|
||||||
|
color: colors.sentinel.textSecondary,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
emailTypeTextActive: {
|
||||||
|
color: colors.sentinel.primary,
|
||||||
|
},
|
||||||
emailForm: {
|
emailForm: {
|
||||||
marginTop: spacing.sm,
|
marginTop: spacing.sm,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,12 @@
|
|||||||
* - Secret is split into 3 shares
|
* - Secret is split into 3 shares
|
||||||
* - Any 2 shares can recover the original secret
|
* - Any 2 shares can recover the original secret
|
||||||
*
|
*
|
||||||
* Based on the Sentinel crypto_core_demo Python implementation.
|
* Correspondence with crypto_core_demo (Python):
|
||||||
|
* - sp_trust_sharding.py: split_to_shares(), recover_from_shares()
|
||||||
|
* - Same algorithm: f(x) = secret + a*x (mod P), Lagrange interpolation
|
||||||
|
* - Difference: entropy conversion. Python uses BIP-39 (mnemonic.to_entropy);
|
||||||
|
* we use custom word list index-based encoding for compatibility with
|
||||||
|
* existing MNEMONIC_WORDS. SSS split/recover logic is identical.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Use a large prime for the finite field
|
// Use a large prime for the finite field
|
||||||
|
|||||||
Reference in New Issue
Block a user