|
| 1 | +# Dropping Down the Stack |
| 2 | + |
| 3 | +Sequre is usually written at the top level — `@sequre` functions and tensor expressions. However, it also allows for a finer control: maybe a particular subroutine is better in MPC than HE, specific matmul strategy needs to be picked, or even the raw CKKS procedures invoked. |
| 4 | + |
| 5 | +This page traces two real examples from high-level code down through the layers, showing where each boundary is and when it makes sense to cross it. |
| 6 | + |
| 7 | +For the overall architecture (Layers 1–4), see the [Home page](../index.md). |
| 8 | + |
| 9 | +## Example A: PCA and `via_mpc` |
| 10 | + |
| 11 | +### Starting point: high-level PCA |
| 12 | + |
| 13 | +In [stdlib/sequre/stdlib/learn/pca.codon](../../stdlib/sequre/stdlib/learn/pca.codon), the randomized PCA routines are written as straightforward linear algebra — matrix products, orthonormalization, eigendecomposition. Most of the algorithm stays at the top level: `@` for matrix multiply, `.T` for transpose, slicing for submatrix extraction. |
| 14 | + |
| 15 | +But two operations can't be done (or are very expensive) in pure HE: orthonormalization and eigendecomposition. So PCA drops into MPC for those, using `via_mpc`: |
| 16 | + |
| 17 | +```python |
| 18 | +# Inside random_pca_without_projection: |
| 19 | +# Step 4 — orthonormalize needs comparisons, which HE can't do natively. |
| 20 | +r_mpp = (p_mpa @ data_mpp.T).via_mpc( |
| 21 | + lambda stensor: orthonormalize(mpc, stensor)) |
| 22 | + |
| 23 | +# Step 6 — eigendecomposition also needs MPC. |
| 24 | +u_mpa = z_cov_mpa.via_mpc( |
| 25 | + lambda stensor: eigen_decomp(mpc, stensor)[0][:top_components_count]) |
| 26 | +``` |
| 27 | + |
| 28 | +What happens when `.via_mpc(fn)` is called on an encrypted type like `MPA` or `MPP`: |
| 29 | + |
| 30 | +1. The encrypted data is collectively decrypted into additive shares (E2S protocol). |
| 31 | +2. The lambda runs on the resulting `Sharetensor` — using Beaver-triple MPC. |
| 32 | +3. The result is re-encrypted back into the original form (S2E protocol). |
| 33 | + |
| 34 | +So `via_mpc` is the boundary between "stay in HE" and "temporarily drop into MPC for this one operation." Everything around it — the `@` products, the slicing — stays in the HE world. |
| 35 | + |
| 36 | +### One layer down: type conversions |
| 37 | + |
| 38 | +The E2S and S2E steps are implemented in [stdlib/sequre/types/internal.codon](../../stdlib/sequre/types/internal.codon): |
| 39 | + |
| 40 | +- `Ciphertensor.to_sharetensor(mpc, ...)` — decrypts via the E2S protocol. |
| 41 | +- `Sharetensor.to_ciphertensor(mpc, ...)` — re-encrypts via S2E. |
| 42 | +- `to_mpp`, `to_mpa`, `to_mpu` — handle the multiparty wrappers. |
| 43 | + |
| 44 | +These methods delegate to `mpc.mhe.ciphervector_to_additive_share_vector` and `mpc.mhe.additive_share_vector_to_ciphervector` — the core MHE conversion routines in [stdlib/sequre/mpc/mhe.codon](../../stdlib/sequre/mpc/mhe.codon), documented in [Core MHE Module](../deep-dive-shechi/core-mhe.md). |
| 45 | + |
| 46 | +### The deepest layer: Lattiseq protocols |
| 47 | + |
| 48 | +Those MHE conversion methods ultimately call into the Lattiseq distributed CKKS protocols — `E2SProtocol`, `S2EProtocol`, `RefreshProtocol` — which operate on ring polynomials, NTT transforms, and secret key shards. For details, see [Lattiseq Overview](../deep-dive-lattiseq/overview.md) and [CKKS Operations](../deep-dive-lattiseq/ckks-operations.md). |
| 49 | + |
| 50 | +This layer rarely needs to be touched directly. But when implementing a new collective protocol or debugging bootstrap failures, this is where things end up. |
| 51 | + |
| 52 | +## Example B: how `@` picks a matmul strategy |
| 53 | + |
| 54 | +### Starting point: linear regression |
| 55 | + |
| 56 | +In [stdlib/sequre/stdlib/learn/lin_reg.codon](../../stdlib/sequre/stdlib/learn/lin_reg.codon), the gradient descent loop does: |
| 57 | + |
| 58 | +```python |
| 59 | +cov = X_tilde.T @ X_tilde # n x n |
| 60 | +ref = X_tilde.T @ y # n x 1 |
| 61 | +``` |
| 62 | + |
| 63 | +When `X_tilde` is a `Sharetensor`, this is a standard Beaver-triple matmul — one implementation, done. But when `X_tilde` is backed by a `Ciphertensor` (HE), the `@` operator has to make a choice. |
| 64 | + |
| 65 | +### One layer down: the cost selector |
| 66 | + |
| 67 | +The key function is `_switch_matmul_by_cost` in [stdlib/sequre/types/ciphertensor.codon](../../stdlib/sequre/types/ciphertensor.codon). When a `Ciphertensor` is multiplied by a plaintext `ndarray`, it estimates the cost of four strategies: |
| 68 | + |
| 69 | +```python |
| 70 | +costs = (Ciphertensor._get_matmul_via_mpc_cost(self, other), # decrypt, MPC matmul, re-encrypt |
| 71 | + Ciphertensor._get_matmul_v1_cost(self, other), # M1: column-packed HE |
| 72 | + Ciphertensor._get_matmul_v2_cost(self, other), # M2: row-packed HE |
| 73 | + Ciphertensor._get_matmul_v3_cost(self, other)) # M3: diagonal-packed HE |
| 74 | + |
| 75 | +if not mpc.default_allow_mpc_switch: |
| 76 | + costs = (inf, *costs[1:]) # disable MPC path unless opted in |
| 77 | +``` |
| 78 | + |
| 79 | +Then it picks the cheapest: |
| 80 | + |
| 81 | +```python |
| 82 | +match argmin(costs): |
| 83 | + case 0: return self.via_mpc(mpc, lambda stensor: secure_operator.matmul(mpc, stensor, other), ...) |
| 84 | + case 1: return self._matmul_v1(mpc, other_cipher, debug) |
| 85 | + case 2: return self._matmul_v2(mpc, ..., debug) |
| 86 | + case 3: return self._matmul_v3(mpc, ..., debug) |
| 87 | +``` |
| 88 | + |
| 89 | +So the same `@` in the algorithm code can end up as a completely different computation path depending on tensor shapes and whether `mpc.allow_mpc_switch()` is active. `DEBUG` mode can be enabled to see the cost breakdown printed at runtime. |
| 90 | + |
| 91 | +The three pure-HE strategies (M1, M2, M3) differ in how they pack matrix elements into CKKS ciphertext slots — column-wise, row-wise, or diagonal-wise. Each has different rotation and multiplication costs depending on the matrix dimensions. The "Via MPC" path does the full E2S → Beaver matmul → S2E round-trip. |
| 92 | + |
| 93 | +### Going deeper |
| 94 | + |
| 95 | +The M1/M2/M3 implementations call `mpc.mhe.iadd`, `mpc.mhe.imul`, `mpc.mhe.irotate` — working directly with ciphertext-level operations. These are the Layer 2 (MPCEnv/MHE) primitives documented in [Core MHE Module](../deep-dive-shechi/core-mhe.md). Below that, each of those methods calls into Lattiseq's `Evaluator` for the actual CKKS polynomial arithmetic. |
| 96 | + |
| 97 | +## When to drop a layer |
| 98 | + |
| 99 | +Most protocol code should stay at the top level. Here's a rough guide for when it makes sense to go deeper: |
| 100 | + |
| 101 | +**Stay at `@sequre` / tensor level** for algorithm logic — this is where all the built-in secure functions (`inv`, `sqrt`, `maximum`, etc.) and type-generic patterns live. See [One Algorithm, Many Secure Types](one-algorithm-many-secure-types.md). |
| 102 | + |
| 103 | +**Use `via_mpc` explicitly** when you have a non-linear or comparison-heavy step inside an otherwise HE-based pipeline. PCA's eigendecomposition is the canonical example. See [MPC ↔ MHE Switching](../user-guide/switching.md). |
| 104 | + |
| 105 | +**Drop into Ciphertensor / MHE** when encoding strategy needs to be controlled, ciphertext levels manually managed, or the matmul cost model tuned. See [Ciphertensor Internals](../deep-dive-shechi/ciphertensor-internals.md) and [Core MHE Module](../deep-dive-shechi/core-mhe.md). |
| 106 | + |
| 107 | +**Use Lattiseq directly** only when extending the cryptographic protocols themselves — new collective operations, custom refresh logic, or low-level debugging of noise/precision issues. See [Lattiseq Overview](../deep-dive-lattiseq/overview.md) and [Lattiseq API](../api/lattiseq.md). |
| 108 | + |
| 109 | +## Next steps |
| 110 | + |
| 111 | +- [One Algorithm, Many Secure Types](one-algorithm-many-secure-types.md) — How the same code runs on ndarray, Sharetensor, and MPU. |
| 112 | +- [Transitioning to MHE](transition-mhe.md) — When to use Shechi's encrypted types. |
| 113 | +- [Distributed Tensors (MPU)](../user-guide/distributed-tensors.md) — MPU/MPP/MPA reference. |
0 commit comments