← Demos Index
FHE in Action
Interactive Demo · Skincare Under Encryption
Factual Demonstration

The Mirror That Doesn't See.

Follow, in nine steps, how a consumer can receive fully personalized skincare recommendations — without the brand ever seeing her selfie, skin profile, or the resulting scores.

The Scenario

A consumer generates 8 skin features locally from a selfie. Encrypts on her own phone. Sends to the server.

The Server

Computes affinity scores for 5 catalog products by applying a linear model — over the ciphertext, without decrypting.

The Guarantee

At no point does the server see the profile in cleartext. The guarantee is mathematical (RLWE), not political.

Everything in this demo is real Numbers, timings, sizes and scores were captured from a real execution of demo_fhe_skincare/main.go using the Lattigo v6 library with the CKKS scheme. Nothing is simulated.
Step 01 · Setup

Define the parameters.

CKKS operates over vectors of real numbers with approximate arithmetic — the standard scheme for machine learning under FHE. The parameters define security, computational depth and cost.

Chosen Parameters

// Lattigo v6 — CKKS
params := ckks.NewParametersFromLiteral({
  LogN: 14,  // N = 16384
  LogQ: [55, 45, 45, 45],
  LogP: [61],
  LogScale: 45,
})

What This Means

16 384Ring degree N
8 192Slots per ciphertext
~45 bitPrecision per op.
~128 bitRLWE security

Mathematical base: RLWE in Zq[x]/(xN+1) — the same problem behind post-quantum cryptography standardized by NIST (ML-KEM, ML-DSA).

Step 02 · Client

Generate the key pair.

The private key is born on the consumer's phone and never leaves it. The public key is what travels to the brand's server.

Key Flow

📱
Phone
sk (private)
only pk travels
Server
pk + rlk

The relinearization key rlk also goes to the server — it enables multiplications under encryption but does not reveal sk.

What Was Generated

23 msTotal time
6.8 KBPublic key
43.9 KBRelinearization
128 bitSecurity
// on the consumer's phone
sk := kgen.GenSecretKey()
pk := kgen.GenPublicKey(sk)
rlk := kgen.GenRelinKey(sk)
Step 03 · Client

Generate the profile and encrypt.

In production, the 8 features would come from a vision model running locally on the selfie. Here we simulate the vector directly — and encryption happens before any byte leaves the phone.

Skin Profile · Cleartext (only the consumer sees)

oiliness0.78
hydration0.32
sensitivity0.65
spots0.42
wrinkles0.18
pores0.71
texture0.55
redness0.48

CKKS Encryption

8.3 msEncryption time
64 BPlaintext (8 floats)
1.00 MBCiphertext
~16 000×Overhead

This overhead is the price of mathematical privacy. In exchange, the server can compute over the ciphertext without ever knowing the content.

Step 04 · Transit

What the server receives.

These are the exact bytes that leave the phone for the brand's server. To any observer without the sk key, it is indistinguishable noise.

Real Ciphertext Sample (first 32 bytes)

01 7b 22 50 6c 61 69 6e
74 65 78 74 4d 65 74 61
44 61 74 61 22 3a 7b 22
53 63 61 6c 65 22 3a 7b
...

The full ciphertext has ~1 MB of pseudo-random bytes. Without the secret key, recovering any individual feature would require solving Ring-LWE at dimension 16 384 — ~2128 operations.

What Travels

6.8 KBpk · once per session
43.9 KBrlk · once per session
1.00 MBct_features · per query
Step 05 · Server

Compute scores under encryption.

For each of the 5 catalog products, the server computes score = bias + Σ(weighti × featurei) — entirely over the ciphertext, without ever decrypting.

The Algorithm (per product)

// server: weights in CLEARTEXT, profile ENCRYPTED
ptW := encoder.Encode(product.Weights)
// (1) element-wise multiplication under encryption
ctMult := evaluator.Mul(ctFeatures, ptW)
evaluator.Rescale(ctMult)
// (2) sum the 8 slots → inner product
ctSum := evaluator.InnerSum(ctMult, 1, 8)
// (3) add scalar bias
evaluator.Add(ctSum, product.Bias)

Everything happens on ct — the server never has access to the real numbers.

Real Measured Performance

131 msTotal · 5 products
26 msPer product
5Ciphertexts generated
0Bytes decrypted
What the server knows now 5 ciphertexts holding the consumer's scores. It has no idea what they are worth. It doesn't know if she has high oiliness, spots, wrinkles, sensitivity. It only fulfilled its function.
Step 06 · Client

Decrypt and recommend.

The 5 ciphertexts travel back to the phone. Only there, with the secret key sk, do they become readable numbers. The consumer finally sees her recommendation.

Recommendation Ranking

  1. iSensitive Soothing Lotion+1.4470
  2. iiPureté Mattifying Toner+1.2980
  3. iiiLumière Spot Brightener+0.7250
  4. ivAbsolu Anti-Aging Cream+0.6350
  5. vQuotidien Hydrating Serum+0.2990

Why It Makes Sense

The consumer has high sensitivity (0.65) and moderate redness (0.48) → the algorithm correctly picked the Soothing Lotion.

She also has high oiliness (0.78) and visible pores (0.71) → the Mattifying Toner shows up second.

The crucial point The model worked exactly as it would in cleartext. But the server never saw any of the 8 values.
Step 07 · Proof

Mathematical validation.

To prove that the encrypted computation is equivalent to the cleartext one, we recompute the scores directly in plaintext (for checking only) and compare.

FHE vs Plaintext

ProductFHECleartextError
Sensitive Soothing Lotion+1.447000+1.4470002.3e-10
Pureté Mattifying Toner+1.298000+1.2980002.7e-10
Lumière Spot Brightener+0.725000+0.7250002.6e-10
Absolu Anti-Aging Cream+0.635000+0.6350001.3e-10
Quotidien Hydrating Serum+0.299000+0.2990004.0e-10

Result

3.96 × 10⁻¹⁰Max absolute error

CKKS is approximate by design — the error comes from the controlled noise added during encryption. For recommendation scoring, 9 decimal places of precision is more than enough.

For exact counts (database, fraud scoring), we would use BFV/BGV which have zero error.

Step 08 · Adversarial

What an attacker can do.

Imagine a dishonest brand employee, or an intruder, captures the ct_features ciphertext. What can they extract without the secret key?

Attack Attempts

  • Attempt 1 — Read bytes directly Result: pseudo-random bytes. Nothing extracted.
  • Attempt 2 — Recover sk from pk Requires solving Ring-LWE at dimension 16 384 with a ~280-bit modulus. Best known attack (BKZ): ~2128 operations. Infeasible on classical or foreseeable quantum hardware.
  • Attempt 3 — Brute force the features Even guessing them, verification would require decryption — which collides with the barrier in Attempt 2.

Why this is different

In traditional architectures (TLS + AES + server processing), the data exists in cleartext at some point — during processing. That moment is the Achilles heel of the entire history of cryptography so far.

FHE eliminates that moment. The data never exists in cleartext outside the consumer's device.

The regulatory point For LGPD/GDPR, the difference between "we handle it carefully" and "mathematically impossible to view" is the difference between residual risk and zero risk.
Step 09 · Summary

What just happened.

In under half a second total, we ran a complete personalized recommendation pipeline with verifiable mathematical privacy. All factual, all measured.

The Complete Flow

  1. The consumer generated 8 skin features locally
  2. Encrypted on her own device with her private key
  3. Sent only the ciphertext to the server
  4. The server computed 5 scores under encryption (131 ms)
  5. Returned 5 score ciphertexts
  6. The consumer decrypted on her phone and saw the recommendation

Real Numbers From This Run

8.3 msEncryption
131 msScoring 5 products
1 MBCiphertext
10⁻¹⁰Precision error
The Next Step Take this code to the CTO and ask them to reproduce it, replacing the simulated vector with the brand's real vision model.
PT EN