The full story behind building Onyx with FHE

Écrit par @Scabanel_ , CMO de zKorp

Onyx, streaming de paiements confidentiel

C’est quoi, Onyx ?

Onyx est un protocole de streaming de paiements confidentiel : les soldes et les débits restent chiffrés onchain.

Nous l’avons construit pendant la Zama Builder Villa (du 30 mars au 2 avril 2026), une retraite sur invitation consacrée au chiffrement homomorphe complet (FHE) dans des systèmes EVM de niveau production.

Notre thèse était simple : les streams de paiement comptent parmi les primitives les plus utiles de la DeFi, mais aujourd’hui chaque salaire streamé, chaque vesting et chaque versement de grant est public par défaut.

Onyx conserve la composabilité et la vérifiabilité, tout en apportant une vraie confidentialité aux institutions, aux équipes et aux particuliers.

Pourquoi la FHE ? Le manque de confidentialité des protocoles de streaming

La plupart des approches existantes en DeFi se rangent dans deux familles :

  • Les systèmes à base de ZK : solides pour les preuves de validité et l’unlinkability, mais l’état privé partagé et mutable est plus dur à modéliser pour des applications qui vivent longtemps.
  • Le calcul confidentiel offchain : utile, mais la confiance repose souvent sur les opérateurs d’enclaves et des hypothèses d’attestation.

La FHE offre un modèle différent : les smart contracts calculent directement sur des valeurs chiffrées, sans jamais exposer de clair pendant l’exécution.

Pour un protocole de streaming, cela signifie qu’Onyx peut garder les dépôts et les retraits chiffrés, tout en dérivant l’état du stream à partir de données temporelles publiques.

Vue d’ensemble de l’architecture

Vue d'ensemble de l'architecture d'Onyx

Onyx repose sur trois couches : le transfert de tokens confidentiel, la logique de comptabilité des streams, et le flux de chiffrement côté frontend.

Le clair n’est jamais exposé onchain. Les utilisateurs autorisés déchiffrent les valeurs qui les concernent localement, dans le navigateur.

Le smart contract central : le stream

Chaque stream stocke ses champs temporels en clair, tandis que l’état financier reste chiffré sous forme de valeurs euint.

Plutôt que de stocker un débit visible, le contrat calcule les montants streamés dans le temps à partir du dépôt chiffré et des timestamps publics.

pragma solidity ^0.8.27; import {FHE, euint64, euint128, externalEuint64} from "@fhevm/solidity/lib/FHE.sol"; import {IERC7984} from "@openzeppelin/confidential-contracts/interfaces/IERC7984.sol"; contract OnyxStream { struct Stream { address sender; address recipient; address token; uint64 startTime; uint64 endTime; euint64 eDeposit; euint64 eWithdrawn; } mapping(uint256 => Stream) private streams; uint256 public nextStreamId; }

Le frontend envoie un dépôt chiffré et une preuve d’input. Le contrat de stream stocke les valeurs comptables chiffrées, pendant que le token ERC-7984 gère le transfert confidentiel.

Calculer le montant retirable

Le calcul clé reste chiffré de bout en bout : montant streamé moins montant déjà retiré.

function _computeStreamed(Stream storage s) internal returns (euint64) { if (block.timestamp < s.startTime) return FHE.asEuint64(0); if (block.timestamp >= s.endTime) return s.eDeposit; euint128 deposit128 = FHE.asEuint128(s.eDeposit); uint128 elapsed = uint128(block.timestamp - s.startTime); uint128 duration = uint128(s.endTime - s.startTime); return FHE.asEuint64(FHE.div(FHE.mul(deposit128, elapsed), duration)); } function _computeWithdrawable(Stream storage s) internal returns (euint64) { euint64 streamed = _computeStreamed(s); return FHE.sub(streamed, s.eWithdrawn); }

Note de design : le temps reste public, les montants restent chiffrés. Cette contrainte garde le calcul moins coûteux et compatible avec les règles actuelles de division du FHEVM.

Le flux de chiffrement côté frontend

Onyx utilise le wrapper React SDK de Zama pour chiffrer les montants déposés dans le navigateur, avant l’envoi de toute transaction.

import { useEncrypt } from "@zama-fhe/react-sdk"; const encrypt = useEncrypt(); const encrypted = await encrypt.mutateAsync({ values: [{ value: depositAmount, type: "euint64" }], contractAddress: STREAM_ADDRESS, userAddress, }); const eDeposit = toHex(encrypted.handles[0]); const inputProof = toHex(encrypted.inputProof);

La preuve lie le ciphertext au wallet de l’utilisateur et au contrat cible, ce qui empêche tout rejeu dans le contexte d’un autre contrat.

Ce qui peut ralentir ce genre de projet

1. Les contraintes arithmétiques peuvent dicter l’architecture

Toutes les opérations FHE n’ont pas le même profil de coût. Les opérations ciphertext contre ciphertext sont en général plus chères, et la division a des contraintes plus strictes.

2. La complexité des ACL sur les handles chiffrés

Les permissions vivent sur les handles de ciphertext, et chaque opération FHE produit de nouveaux handles. Les permissions ne se propagent pas automatiquement.

D’où un mode de défaillance subtil : le calcul peut être juste alors que le handle produit reste inutilisable, faute de grants ACL.

3. Les tests de bout en bout restent plus durs qu’en Solidity classique

L’outillage local a progressé, mais un vrai E2E impose encore de valider toute la chaîne chiffrée : chiffrement côté frontend, comportement du relayer, flux de rechiffrement, signature et conditions de testnet.

Pourquoi c’est important pour la DeFi

Implications du streaming confidentiel

Confidentialité et composabilité ne s’excluent pas dans le modèle FHEVM. Les contrats peuvent continuer à composer autour de handles chiffrés, sans jamais déchiffrer les valeurs.

Vue technique d’ensemble

Vue technique d'ensemble d'Onyx

Pour aller plus loin sur Onyx

Si vous voulez creuser, lisez le thread original du build et jetez un œil au récap visuel ci-dessous.

Récap visuel d'Onyx

Nous continuons d’itérer sur les paiements confidentiels. Si vous avez des idées, des cas d’usage ou des intégrations à discuter, contactez-nous.

Message de l'équipe zKorp

Lire le thread X original →