Generate SSS shares from mnemonic words

This commit is contained in:
Ada
2026-01-30 16:31:09 -08:00
parent c07f1f20d5
commit fb1377eb4b
3 changed files with 334 additions and 20 deletions

View File

@@ -22,6 +22,14 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
import { colors, typography, spacing, borderRadius, shadows } from '../theme/colors';
import { SystemStatus, KillSwitchLog } from '../types';
import VaultScreen from './VaultScreen';
import {
SSSShare,
mnemonicToEntropy,
splitSecret,
formatShareCompact,
serializeShare,
verifyShares,
} from '../utils/sss';
// Nautical-themed mnemonic word list (unique words only)
const MNEMONIC_WORDS = [
@@ -52,11 +60,39 @@ const generateMnemonic = (wordCount = 12) => {
return words;
};
const splitMnemonic = (words: string[]) => [
words.slice(0, 4),
words.slice(4, 8),
words.slice(8, 12),
];
/**
* Generate SSS shares from mnemonic words
* Uses Shamir's Secret Sharing (3,2) threshold scheme
*/
const generateSSSShares = (words: string[]): SSSShare[] => {
try {
// Convert mnemonic to entropy (big integer)
const entropy = mnemonicToEntropy(words, MNEMONIC_WORDS);
// Split entropy into 3 shares using SSS
const shares = splitSecret(entropy);
// Verify shares can recover the original (optional, for debugging)
if (__DEV__) {
const isValid = verifyShares(shares, entropy);
if (!isValid) {
console.warn('SSS verification failed!');
} else {
console.log('SSS shares verified successfully');
}
}
return shares;
} catch (error) {
console.error('Failed to generate SSS shares:', error);
// Fallback: return empty shares (should not happen in production)
return [
{ x: 1, y: BigInt(0), label: 'device' },
{ x: 2, y: BigInt(0), label: 'cloud' },
{ x: 3, y: BigInt(0), label: 'heir' },
];
}
};
// Icon names type for type safety
type StatusIconName = 'checkmark-circle' | 'warning' | 'alert-circle';
@@ -127,7 +163,7 @@ export default function SentinelScreen() {
const [showVault, setShowVault] = useState(false);
const [showMnemonic, setShowMnemonic] = useState(false);
const [mnemonicWords, setMnemonicWords] = useState<string[]>([]);
const [mnemonicParts, setMnemonicParts] = useState<string[][]>([]);
const [sssShares, setSssShares] = useState<SSSShare[]>([]);
const [showEmailForm, setShowEmailForm] = useState(false);
const [emailAddress, setEmailAddress] = useState('');
const [isCapturing, setIsCapturing] = useState(false);
@@ -188,16 +224,20 @@ export default function SentinelScreen() {
const openVaultWithMnemonic = () => {
const words = generateMnemonic();
const parts = splitMnemonic(words);
const shares = generateSSSShares(words);
setMnemonicWords(words);
setMnemonicParts(parts);
setSssShares(shares);
setShowMnemonic(true);
setShowVault(false);
setShowEmailForm(false);
setEmailAddress('');
AsyncStorage.setItem('sentinel_mnemonic_part_local', parts[0].join(' ')).catch(() => {
// Best-effort local store; UI remains available
});
// Store Share A (device share) locally
if (shares[0]) {
AsyncStorage.setItem('sentinel_share_device', serializeShare(shares[0])).catch(() => {
// Best-effort local store; UI remains available
});
}
};
const handleScreenshot = async () => {
@@ -515,7 +555,7 @@ export default function SentinelScreen() {
<Text style={styles.mnemonicTitle}>12-Word Mnemonic</Text>
</View>
<Text style={styles.mnemonicSubtitle}>
Your mnemonic is split into 3 parts (4/4/4). Part 1 is stored locally.
Your seed is protected by SSS (3,2) threshold encryption. Any 2 shares can restore your vault.
</Text>
<View style={styles.mnemonicBlock}>
<Text style={styles.mnemonicBlockText}>
@@ -524,19 +564,25 @@ export default function SentinelScreen() {
</View>
<View style={styles.partGrid}>
<View style={[styles.partCard, styles.partCardStored]}>
<Text style={styles.partLabel}>PART 1 LOCAL</Text>
<Text style={styles.partValue}>{mnemonicParts[0]?.join(' ')}</Text>
<Text style={styles.partLabel}>SHARE A DEVICE</Text>
<Text style={styles.partValue}>
{sssShares[0] ? formatShareCompact(sssShares[0]) : '---'}
</Text>
<Text style={styles.partHint}>Stored on this device</Text>
</View>
<View style={styles.partCard}>
<Text style={styles.partLabel}>PART 2 CLOUD NODE</Text>
<Text style={styles.partValue}>{mnemonicParts[1]?.join(' ')}</Text>
<Text style={styles.partHint}>To be synced</Text>
<Text style={styles.partLabel}>SHARE B CLOUD</Text>
<Text style={styles.partValue}>
{sssShares[1] ? formatShareCompact(sssShares[1]) : '---'}
</Text>
<Text style={styles.partHint}>To be synced to Sentinel</Text>
</View>
<View style={styles.partCard}>
<Text style={styles.partLabel}>PART 3 HEIR</Text>
<Text style={styles.partValue}>{mnemonicParts[2]?.join(' ')}</Text>
<Text style={styles.partHint}>To be assigned</Text>
<Text style={styles.partLabel}>SHARE C HEIR</Text>
<Text style={styles.partValue}>
{sssShares[2] ? formatShareCompact(sssShares[2]) : '---'}
</Text>
<Text style={styles.partHint}>For your heir (2-of-3 required)</Text>
</View>
</View>
<TouchableOpacity