Summary
- Adds split_secret(&[u8; 32]) -> Result<[ShamirShare; 3], CryptoError> — splits a 32-byte DID rotation key into 3 shares using a degree-1polynomial over GF(2^8)
- Adds combine_shares(&ShamirShare, &ShamirShare) -> Result<Zeroizing<[u8; 32]>, CryptoError> — reconstructs the secret from any 2 of the 3 shares via Lagrange interpolation at x=0
- GF(2^8) arithmetic uses the AES irreducible polynomial (0x11b); secret bytes are always in the non-branching argument position of gf_mul (no timing channel on secret data)
- ShamirShare.data is Zeroizing<[u8; 32]> — zeroized on drop, consistent with existing key handling conventions
Security properties
- Information-theoretic: a single share is uniformly random and reveals nothing about the secret (guaranteed by SSS construction, not just computational hardness)
- 2-of-3 threshold: shares at x=1 (device/iCloud), x=2 (relay escrow), x=3 (user backup); relay stores share 2 encrypted via existing encrypt_private_key
- No new dependencies: implemented from scratch using workspace primitives (rand_core, zeroize)
Test plan
- split_shares_have_correct_indices / split_shares_are_32_bytes
- combine_shares_1_and_2, _1_and_3, _2_and_3 — all pairs reconstruct
- combine_is_commutative — argument order doesn't matter
- round_trip_all_zeros / round_trip_all_ones / round_trip_all_pairs — integration
- shares_are_not_plaintext — share data ≠ secret
- combine_duplicate_indices_fails / combine_with_index_zero_fails — invalid input
- gf_mul_by_zero_is_zero / gf_mul_by_one_is_identity / gf_mul_is_commutative
- gf_inv_produces_correct_inverse — exhaustive: a·a⁻¹ = 1 for all 255 non-zero elements
Closes MM-93