NOUVEAU : Essayez l'API Parsed Events Scan
confidentialité sur Solana avec Elusiv et Light Protocol
Blog/Développement

Confidentialité sur Solana avec Elusiv et Light : tout savoir sur la confidentialité de SOL

Developer Experience Engineer0xIchigo sur X0xIchigo sur LinkedIn0xIchigo sur GitHub
16 min de lecture

Prérequis

  • Node.js installé (pour installer les dépendances requises, exécuter les scripts et envoyer des $SOL de manière confidentielle)
  • Connaissances de base en TypeScript
  • Connaissances de base de @solana/web3.js

Quel est le sujet de cet article ?

La confidentialité est au cœur de la technologie blockchain moderne. Dans les systèmes de registres publics, les transactions, les soldes des comptes et, souvent, le code source des programmes sont accessibles à tous. Bien que ce niveau de transparence sans précédent soit considéré comme l’une des caractéristiques fondamentales de la blockchain, la confidentialité peut être indispensable à de nombreuses applications ou à un usage quotidien.

Elusiv et Light Protocol proposent des solutions de pointe pour la confidentialité sur Solana en exploitant les zk-SNARKS pour les transactions privées, les programmes privés et les solutions de conformité décentralisées. Dans cet article, nous examinons l’importance de la confidentialité, expliquons pourquoi les blockchains comme Solana ont besoin de fonctionnalités de confidentialité et fournissons un guide détaillé pour envoyer des SOL de manière confidentielle avec le SDK TypeScript d’Elusiv et le module zk.js de Light.

La confidentialité est importante

La confidentialité est un droit humain fondamental, essentiel à l’autonomie et à la préservation de la dignité humaine. Elle est nécessaire à une société libre et ouverte, car elle sous-tend l’exercice d’autres droits, tels que la liberté d’expression et la liberté d’association. Elle permet de créer un espace distinct de la vie politique, où chacun peut penser, parler, exprimer des idées ou agir sans être constamment surveillé par un tiers. De récentes controverses publiques, telles que les révélations d’Edward Snowden, les informations publiées par WikiLeaks et le scandale des données Facebook-Cambridge Analytica, ont suscité d’intenses débats sur l’importance et les limites de la confidentialité.

Tout le monde devrait se soucier de la confidentialité

À première vue, il peut sembler contradictoire de défendre l’ajout de fonctionnalités de confidentialité aux registres publics, construits sur des technologies décentralisées qui font de la transparence l’un de leurs principes fondamentaux. Que vous soyez un cypherpunk convaincu ou un utilisateur ordinaire, tout le monde devrait se soucier de la confidentialité. Vous pensez peut-être que, puisque vous ne faites rien de nuisible ou d’illégal en ligne, peu importe que quelqu’un voie ce que vous faites sur la blockchain. Considérez cette expérience de pensée :

Nous avons deux personnages : Alice, une utilisatrice ordinaire qui tient à sa vie privée et souhaite effectuer des achats sans être surveillée, et un acteur malveillant, que nous appellerons AM. Un acteur malveillant peut être, par exemple, un gouvernement oppressif, un employeur autoritaire ou un cybercriminel. Imaginons qu’Alice vive dans un pays où le gouvernement surveille constamment chaque citoyen, ou qu’elle travaille pour une entreprise appliquant des politiques strictes contre certains achats légaux (par exemple, les dons politiques ou l’achat de livres sur certains sujets). Un jour, Alice effectue un achat sur une blockchain traditionnelle telle que Solana, pensant qu’il s’agit d’un moyen de paiement sûr et confidentiel. Voici la suite des événements :

  • La transaction d’Alice est enregistrée dans le registre public. Elle comprend sa clé publique, celle du destinataire et le montant envoyé
  • L’AM, qui souhaite surveiller ou contrôler le comportement d’Alice, commence à retracer ses transactions à l’aide de sa clé publique
  • L’AM découvre l’achat d’Alice, qu’il juge « répréhensible » selon ses propres règles
  • L’AM prend des mesures contre Alice qui, selon les circonstances, peuvent aller de poursuites judiciaires à l’emprisonnement, en passant par des sanctions professionnelles, l’humiliation publique ou le chantage

Cette expérience de pensée illustre clairement les conséquences que l’absence de confidentialité sur la blockchain peut avoir pour un utilisateur ordinaire. Avec l’essor des enquêteurs on-chain et des technologies malveillantes de collecte de données, il devient très facile de retracer les transactions d’un utilisateur moyen, ce qui peut entraîner de lourdes conséquences dans le monde réel. Dans un monde toujours plus numérique, l’absence de confidentialité menace la souveraineté individuelle : toutes nos décisions risquent d’être surveillées et chaque aspect de notre existence, marchandisé. Les blockchains ne feront qu’accélérer ce phénomène si nous ne mettons pas en œuvre des technologies favorisant la confidentialité. C’est là qu’interviennent Elusiv et Light Protocol.

Qu’est-ce qu’Elusiv ?

Elusiv se décrit comme « un protocole de confidentialité blockchain fondé sur la preuve à divulgation nulle de connaissance » qui « permet aux utilisateurs et aux applications d’accéder au chiffrement universel ». Elusiv cherche à garantir la confidentialité sans sacrifier la sécurité, la sûreté ni la décentralisation, en appliquant la cryptographie à divulgation nulle de connaissance et le calcul multipartite à l’utilisateur et au réseau. Cet objectif de chiffrement universel permet à Elusiv de fournir des outils grâce auxquels les utilisateurs contrôlent leurs données. Ils peuvent désormais choisir ce qui est partagé sur la blockchain et ce qui ne l’est pas.

Comment fonctionne Elusiv ?

Avec Elusiv, les utilisateurs envoient des fonds dans un pool partagé régi par le programme Elusiv, ce qui alimente leur solde privé. Depuis ce pool, ils peuvent envoyer et recevoir des tokens comme SOL, ainsi que retirer des fonds. Il est important de noter qu’avec ce modèle de pool partagé, les dépôts vers Elusiv et les paiements qui en proviennent restent visibles. Toutefois, à mesure que le nombre d’utilisateurs et de transactions augmente, il devient de plus en plus difficile de relier les dépôts et les paiements à des comptes précis au fil du temps. Il est plus facile d’établir un lien avec un utilisateur isolé qui dépose 10 SOL puis retire 10 SOL qu’avec un utilisateur parmi des millions qui effectue progressivement plusieurs dépôts et paiements. L’anonymat aime la compagnie.

Les fonds du pool ne contiennent aucune information sur leurs déposants. Toutefois, grâce aux preuves à divulgation nulle de connaissance, des valeurs secrètes sont associées aux fonds d’un utilisateur afin de produire des preuves d’autorité sur les fonds non dépensés. Les preuves à divulgation nulle de connaissance utilisées dans Elusiv sont appelées zk-SNARKS (Zero-Knowledge Succinct Non-Interactive Argument of Knowledge). Sans entrer dans la complexité des preuves zk-SNARK, imaginez-les comme un moyen mathématiquement démontré de prouver à un ami que vous connaissez un mot de passe secret sans jamais le lui révéler. Votre ami peut alors vérifier rapidement que vous dites la vérité sans avoir à vous poser d’autres questions. Elusiv peut ainsi suivre vos fonds non dépensés sans révéler à l’ensemble de Solana combien vous détenez dans le pool. Si vous souhaitez en savoir plus sur les preuves à divulgation nulle de connaissance, je vous recommande de consulter le cours de Porter sur les preuves à divulgation nulle de connaissance.

Qu’est-ce que Light Protocol ?

Light Protocol se décrit comme une « couche zk de nouvelle génération sur Solana » permettant d’exécuter des programmes privés directement sur Solana. Trois concepts fondamentaux de Light rendent possible la confidentialité sur Solana :

  • État privé
  • Modèle UTXO
  • Vérification on-chain des transitions d’état privées

Comment fonctionne Light Protocol ?

Light Protocol permet de chiffrer l’état on-chain. Les utilisateurs « possèdent » cet état et disposent de droits de déchiffrement exclusifs. Contrairement à l’approche d’Elusiv fondée sur un pool de fonds, Light Protocol introduit dans Solana un modèle UTXO (Unspent Transaction Outputs). Un UTXO est lié à une paire de clés protégée et peut contenir deux soldes : l’un en SOL, puisqu’il s’agit du token utilisé pour les frais dans Light Protocol, et l’autre dans un autre token SPL. Avec cette approche, chaque transaction génère un nouvel UTXO ajouté à une liste linéaire, ce qui évite les mises à jour sur place. L’état reste ainsi masqué au lieu de divulguer des informations sur l’état mis à jour et l’identité de la personne qui le modifie.

La logique d’un programme sur Solana est presque toujours un séquestre assorti de contraintes particulières. Light Protocol fait évoluer cette conception en encodant la logique du programme dans des circuits à divulgation nulle de connaissance (zk-SNARKS) côté client, où un calcul et une preuve sont générés. Cette preuve est partagée on-chain, où un programme de vérification dédié et conçu spécialement pour le circuit en contrôle la validité. Une fois la vérification réussie, le programme de vérification personnalisé appelle un autre vérificateur natif, qui effectue différents contrôles de sécurité et ajoute les nouveaux engagements au programme d’arbre de Merkle de Light.

Bien que cela dépasse le cadre de cet article, Light vous permet de créer de toutes pièces votre propre programme Solana privé (PSP). Nous vous recommandons de lire la suite de cet article et de consulter sa documentation avant d’essayer de créer le vôtre. Cette documentation comprend un tutoriel expliquant comment créer votre propre PSP personnalisé, disponible ici.

Tout cela est intéressant, mais comment l’appliquer directement à la programmation sur Solana ?

Envoyer des SOL de manière confidentielle avec Elusiv

Le code complet

Code
import * as ed from "@noble/ed25519";
import { sha512 } from "@noble/hashes/sha512";
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));

import {
  Connection,
  PublicKey,
  Keypair,
  ConfirmedSignatureInfo,
  Cluster,
  LAMPORTS_PER_SOL,
  clusterApiUrl,
} from "@solana/web3.js";
import { Elusiv, TokenType, SEED_MESSAGE } from "@elusiv/sdk";

const CLUSTER: Cluster = "devnet";
const RPC = "https://rpc-devnet.helius.xyz";

const main = async () => {
  const connection: Connection = new Connection(RPC);
  const keyPair: Keypair = generateKeypair();
  const recipientPublicKey: PublicKey = new PublicKey(generateKeypair().publicKey);

  await airdropSol(keyPair);

  const seed: Uint8Array = ed.sign(Buffer.from(SEED_MESSAGE, "utf-8"), keyPair.secretKey.slice(0, 32));

  const elusiv: Elusiv = await Elusiv.getElusivInstance(seed, keyPair.publicKey, connection, CLUSTER);

  const balance: bigint = await elusiv.getLatestPrivateBalance("LAMPORTS");

  try {
    if (balance > BigInt(0)) {
      const signature = await send(elusiv, recipientPublicKey, 1 * LAMPORTS_PER_SOL, "LAMPORTS");

      console.log(`Sent with signature ${signature.signature}`);
    } else {
      console.log("Private balance is empty. Topping up...");

      const amount = 1 * LAMPORTS_PER_SOL;
      const tokenType = "LAMPORTS";

      const topUpTxData = await elusiv.buildTopUpTx(amount, tokenType);

      topUpTxData.tx.partialSign(keyPair);

      const topUpSig: ConfirmedSignatureInfo = await elusiv.sendElusivTx(topUpTxData);
      console.log(`Top-up complete with signature ${topUpSig.signature}`);

      const signature: ConfirmedSignatureInfo = await send(elusiv, recipientPublicKey, 1 * LAMPORTS_PER_SOL, "LAMPORTS");

      console.log(`Sent with signature ${signature.signature}`);
    }
  } catch (e) {
    console.error(`Error sending SOL via Elusiv: ${e}`);
  } finally {
    console.log("Exiting program...");
    process.exit(0);
  }
};

const send = async (
  elusiv: Elusiv,
  recipient: PublicKey,
  amount: number,
  tokenType: TokenType
): Promise => {
  const txt = await elusiv.buildSendTx(amount, recipient, tokenType);
  return elusiv.sendElusivTx(txt);
};

const generateKeypair = (): Keypair => {
  let keyPair = Keypair.generate();

  console.log(`Public key: ${keyPair.publicKey.toBase58()}`);
  console.log(`Private key: ${keyPair.secretKey}`);

  return keyPair;
};

const airdropSol = async (wallet: Keypair) => {
  try {
    const connection = new Connection(clusterApiUrl("devnet"));
    const airdropSignature = await connection.requestAirdrop(new PublicKey(wallet.publicKey), 1 * LAMPORTS_PER_SOL);
    const latestBlockHash = await connection.getLatestBlockhash();

    await connection.confirmTransaction({
      blockhash: latestBlockHash.blockhash,
      lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
      signature: airdropSignature,
    });
    console.log(`Airdropped 1 SOL to ${wallet.publicKey.toBase58()}`);
  } catch (e) {
    console.error(`Error sending SOL via Elusiv: ${e}`);
  }
};

main();

Analyse du code Elusiv

Code
import * as ed from "@noble/ed25519";
import { sha512 } from "@noble/hashes/sha512";
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));

Tout d’abord, nous importons la bibliothèque noble-ed25519, car aucune connexion à un wallet n’est configurée. Dans un environnement de production, vous utiliseriez plutôt Solana Wallet Adapter pour signer les transactions. Ici, nous avons uniquement besoin de la fonctionnalité sign. Toutefois, l’utilisation de import { sign } from "@noble/ed25519"; peut parfois renvoyer une erreur indiquant que etc.sha512Sync n’est pas défini. Pour éviter cette erreur, nous le définissons nous-mêmes. Il n’est pas nécessaire de trop vous en préoccuper.

Code
import {
  Connection,
  PublicKey,
  Keypair,
  ConfirmedSignatureInfo,
  Cluster,
  LAMPORTS_PER_SOL,
  clusterApiUrl,
} from "@solana/web3.js";
import { Elusiv, TokenType, SEED_MESSAGE } from "@elusiv/sdk";

Nous importons ensuite web3.js de Solana et le SDK TypeScript d’Elusiv avec les modules nécessaires.

Code
const CLUSTER: Cluster = "devnet";
const RPC = "https://rpc-devnet.helius.xyz";

Ici, nous définissons CLUSTER sur "devnet" et configurons notre RPC sur le RPC devnet de Helius. Pour utiliser ce script sur mainnet, vous définiriez CLUSTER sur "mainnet" et obtiendriez une clé API Helius avec l’une des offres suivantes 😎

Code
const main = async () => {
	const connection: Connection = new Connection(RPC);
	const keyPair: Keypair = generateKeypair();
	const recipientPublicKey: PublicKey = new PublicKey(generateKeypair().publicKey);

	// Rest of the code
}

Nous déclarons notre fonction main comme asynchrone, car nous appliquons await à plusieurs fonctions dans son bloc. Nous établissons ensuite notre connexion à devnet avec RPC, puis créons un objet Keypair à partir de la fonction utilitaire generateKeypair. Nous créons également un recipientPublicKey avec la fonction generateKeypair mentionnée précédemment, afin de recevoir les SOL protégés à une adresse aléatoire. Pour référence, la fonction generateKeypair est définie comme suit :

Code
const generateKeypair = (): Keypair => {
	let keyPair = Keypair.generate();

	console.log(`Public key: ${keyPair.publicKey.toBase58()}`);
	console.log(`Private key: ${keyPair.secretKey}`);

	return keyPair;
};

Cette fonction crée une paire de clés Solana de test que nous utiliserons pour ce script. Pour utiliser celui-ci sur mainnet, vous remplaceriez cette paire de clés par celle de votre propre wallet. N’oubliez pas de ne jamais stocker votre clé privée en texte brut et de l’importer correctement en indiquant le bon chemin de fichier.

Code
await airdropSol(keyPair);

Nous avons besoin de SOL sur devnet, car nous utilisons des paires de clés de test nouvellement générées. Nous utilisons la fonction utilitaire aidropSol pour distribuer des SOL par airdrop à notre nouvelle paire de clés. Elle est définie comme suit :

Code
const airdropSol = async (wallet: Keypair) => {
  try {
    const connection = new Connection(clusterApiUrl("devnet"));
    const airdropSignature = await connection.requestAirdrop(new PublicKey(wallet.publicKey), 1 * LAMPORTS_PER_SOL);
    const latestBlockHash = await connection.getLatestBlockhash();

    await connection.confirmTransaction({
      blockhash: latestBlockHash.blockhash,
      lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
      signature: airdropSignature,
    });
    console.log(`Airdropped 1 SOL to ${wallet.publicKey.toBase58()}`);
  } catch (e) {
    console.error(`Error sending SOL via Elusiv: ${e}`);
  }
};

Ici, nous nous envoyons 1 SOL (1 * LAMPORTS_PER_SOL) par airdrop sur devnet. Nous récupérons latestBlockHash afin de transmettre le hash du bloc et la dernière hauteur de bloc valide à connection.confirmTransaction(), ce qui confirme que nous avons envoyé 1 SOL par airdrop à la bonne adresse.

Code
const seed: Uint8Array = ed.sign(Buffer.from(SEED_MESSAGE, "utf-8"), keyPair.secretKey.slice(0, 32));

Elusiv nécessite une seed d’entrée pour garantir le caractère déterministe de certaines opérations. C’est pourquoi nous importons et utilisons SEED_MESSAGE. SEED_MESSAGE est un message obligatoire à signer, car il sert à générer la seed Elusiv. Il permet à l’application de déchiffrer vos actifs privés avec les valeurs secrètes qui leur sont associées afin de suivre vos fonds. Nous utilisons Buffer.from() pour créer un nouveau buffer, une structure de données binaires brutes, à partir de SEED_MESSAGE avec l’encodage utf-8. Nous devons utiliser slice, car le type de paire de clés de Solana place la clé privée dans les 32 premiers octets et la clé publique dans les 32 derniers. Nous signons donc le message de seed requis avec notre clé privée.

Code
const elusiv: Elusiv = await Elusiv.getElusivInstance(seed, keyPair.publicKey, connection, CLUSTER);

Ici, nous définissons elusiv, une instance d’Elusiv, à l’aide de nos seed, keyPair.publicKey, connection et CLUSTER. Nous créons ainsi une instance d’Elusiv sur devnet avec le RPC devnet, notre clé publique et le long message que nous venons de signer.

Code
const balance: bigint = await elusiv.getLatestPrivateBalance("LAMPORTS");

Nous récupérons ensuite notre solde privé, c’est-à-dire le nombre de lamports que nous détenons dans le pool Elusiv, renvoyé sous forme de BigInt. Le reste de la fonction main est encapsulé dans un try-catch-finally block, défini comme suit :

Code
try {
	if (balance > BigInt(0)) {
      const signature = await send(elusiv, recipientPublicKey, 1 * LAMPORTS_PER_SOL, "LAMPORTS");

      console.log(`Sent with signature ${signature.signature}`);
    } else {
      // Rest of code
	}
}

Si nous disposons d’un solde privé, balance sera supérieur à 0 et le premier bloc du bloc if-else s’exécutera. Nous convertissons 0 en BigInt dans la condition if, car le type de retour de getLatestPrivateBalance est BigInt. Nous créons ensuite signature, une variable définie sur la valeur renvoyée par send(). La fonction send() est définie comme suit :

Code
const send = async (
  elusiv: Elusiv,
  recipient: PublicKey,
  amount: number,
  tokenType: TokenType
): Promise => {
  const txt = await elusiv.buildSendTx(amount, recipient, tokenType);
  return elusiv.sendElusivTx(txt);
};

Cette fonction envoie notre transaction via Elusiv et renvoie une Promise de type ConfirmedSignatureInfo, c’est-à-dire une signature confirmée accompagnée de son statut. Ses arguments sont une instance d’Elusiv, la clé publique d’un destinataire, le montant à envoyer et le type de token. Celui-ci n’est pas nécessairement des lamports et peut correspondre à d’autres tokens, comme USDC, USDT, mSOL, BONK et SAMO. Nous définissons ensuite txt avec elusiv.buildSendTx(). buildSendTx() est une méthode de l’instance d’Elusiv qui construit une transaction pour envoyer des tokens à un destinataire donné à partir du solde privé. Enfin, nous envoyons et renvoyons la transaction avec notre instruction return elusiv.sendElusivTx(txt), en lui transmettant notre transaction construite txt.

Si nous n’avons aucun solde privé, le bloc else s’exécute :

Code
console.log("Private balance is empty. Topping up...");

const amount = 1 * LAMPORTS_PER_SOL;
const tokenType = "LAMPORTS";

const topUpTxData = await elusiv.buildTopUpTx(amount, tokenType);

topUpTxData.tx.partialSign(keyPair);

const topUpSig: ConfirmedSignatureInfo = await elusiv.sendElusivTx(topUpTxData);
console.log(`Top-up complete with signature ${topUpSig.signature}`);

const signature: ConfirmedSignatureInfo = await send(elusiv, recipientPublicKey, 1 * LAMPORTS_PER_SOL, "LAMPORTS");
console.log(`Sent with signature ${signature.signature}`);

Nous indiquons à l’utilisateur qu’il n’a aucun solde privé et que nous allons l’alimenter. Nous construisons ensuite la transaction d’approvisionnement avec la méthode .buildTopUpTx() de l’instance d’Elusiv. Nous transmettons une valeur de 1 SOL et un type de token en lamports, puisque nous envoyons des SOL. Nous signons ensuite l’approvisionnement avec un .partialSign() sur la paire de clés de la transaction, puisque les fonds proviennent de notre clé publique. Enfin, nous envoyons la transaction construite avec elusiv.sendElusivTx(topUpTxData) et journalisons sa signature.

Le reste du bloc try-catch-finally se présente comme suit :

Code
} catch (e) {
	console.error(`Error sending SOL via Elusiv: ${e}`);
} finally {
	console.log("Exiting program...");
	process.exit(0);
}

Si des erreurs surviennent lors de l’envoi des SOL via Elusiv, nous les journalisons dans la console avec console.error(). Nous quittons ensuite le programme avec process.exit(0).

Pour lancer l’ensemble du processus, nous appelons la fonction main() au bas du fichier.

Envoyer des SOL de manière confidentielle avec Light Protocol

Le code complet

Code
import { BN } from "@coral-xyz/anchor";
import { PublicKey, Keypair, Connection } from "@solana/web3.js";
import { User, Provider as LightProvider, TestRelayer, confirmConfig, airdropSol } from "@lightprotocol/zk.js";

const initializeSolanaWallet = async (): Promise => {
  const wallet = Keypair.generate();

  console.log("Wallet initialized");
  return wallet;
};

const requestAirdrop = async (connection: Connection, publicKey: PublicKey): Promise => {
  await airdropSol({
    connection,
    lamports: 2e9,
    recipientPublicKey: publicKey,
  });
  console.log("Airdrop requested...");
};

const setupTestRelayer = async (solanaWallet: Keypair): Promise => {
  const testRelayer = new TestRelayer({
    relayerPubkey: solanaWallet.publicKey,
    relayerRecipientSol: solanaWallet.publicKey,
    relayerFee: new BN(100_000),
    payer: solanaWallet,
  });
  console.log("Test relayer initialized");
  return testRelayer;
};

const initializeLightProvider = async (solanaWallet: Keypair, testRelayer: TestRelayer): Promise => {
  const lightProvider = await LightProvider.init({
    wallet: solanaWallet,
    relayer: testRelayer,
    confirmConfig,
  });

  console.log("Light provider initialized");
  return lightProvider;
};

const initializeLightUser = async (lightProvider: LightProvider): Promise => {
  const user = await User.init({ provider: lightProvider });
  console.log("Light user initialized");
  return user;
};

const performShieldOperation = async (user: User) => {
  await user.shield({
    publicAmountSol: "1.1",
    token: "SOL",
  });
  console.log("Performed shield operation");
};

const executePrivateTransfer = async (user: User, testRecipientPublicKey: string) => {
  const response = await user.transfer({
    amountSol: "1",
    token: "SOL",
    recipient: testRecipientPublicKey,
  });
  console.log(`Executed private transfer! Txt hash: ${response.txHash}`);
};

const main = async () => {
  try {
    const solanaWallet = await initializeSolanaWallet();
    const connection = new Connection("http://127.0.0.1:8899");

    await requestAirdrop(connection, solanaWallet.publicKey);

    const testRelayer = await setupTestRelayer(solanaWallet);
    const lightProvider = await initializeLightProvider(solanaWallet, testRelayer);
    const user = await initializeLightUser(lightProvider);

    await performShieldOperation(user);

    const testRecipientKeypair = Keypair.generate();

    await requestAirdrop(connection, testRecipientKeypair.publicKey);

    const lightProviderRecipient = await initializeLightProvider(testRecipientKeypair, testRelayer);
    const testRecipient = await initializeLightUser(lightProviderRecipient);

    await executePrivateTransfer(user, testRecipient.account.getPublicKey());
    console.log(`Successfully sent 1 $SOL to ${testRecipient.account.getPublicKey()} privately!`);
  } catch (e) {
    console.error(`Error sending SOL via Light: ${e}`);
  } finally {
    console.log("Exiting program...");
    process.exit(0);
  }
};

main();

Analyse du code

Code
import { BN } from "@coral-xyz/anchor";
import { PublicKey, Keypair, Connection } from "@solana/web3.js";
import { User, Provider as LightProvider, TestRelayer, confirmConfig, airdropSol } from "@lightprotocol/zk.js";

Tout d’abord, nous importons les modules nécessaires depuis Anchor, Solana/web3.js et Light afin d’interagir avec Solana à l’aide de Light Protocol. Nous définissons ensuite plusieurs fonctions utilitaires avant de passer à notre fonction main. Light possède un processus d’initialisation plus complexe que notre implémentation d’Elusiv.

Code
const initializeSolanaWallet = async (): Promise => {
  const wallet = Keypair.generate();

  console.log("Wallet initialized");
  return wallet;
};

Ici, nous créons la fonction initializeSolanaWallet, qui génère un nouveau wallet Solana avec la méthode Keypair.generate().

Code
const requestAirdrop = async (connection: Connection, publicKey: PublicKey): Promise => {
  await airdropSol({
    connection,
    lamports: 2e9,
    recipientPublicKey: publicKey,
  });
  console.log("Airdrop requested...");
};

Contrairement à Elusiv, Light fournit dans sa bibliothèque une fonction airdropSol permettant d’envoyer des lamports par airdrop à une clé publique donnée. Nous créons la fonction utilitaire requestAirdrop pour envoyer des SOL au publicKey transmis.

Code
const setupTestRelayer = async (solanaWallet: Keypair): Promise => {
  const testRelayer = new TestRelayer({
    relayerPubkey: solanaWallet.publicKey,
    relayerRecipientSol: solanaWallet.publicKey,
    relayerFee: new BN(100_000),
    payer: solanaWallet,
  });
  console.log("Test relayer initialized");
  return testRelayer;
};

Pour envoyer des SOL protégés via Light, nous devons configurer un relais de test à l’aide du module TestRelayer. Les relais jouent un rôle important dans l’écosystème Light : ce sont des serveurs web qui transmettent du client au programme on-chain concerné les preuves ZK attestant les transitions d’état. Ils paient également les frais de transaction en échange d’une commission lorsque la preuve est vérifiée avec succès. Ces frais s’élèvent à 100 000 lamports, valeur que nous définissons avec new BN(100_000).

Code
const initializeLightProvider = async (solanaWallet: Keypair, testRelayer: TestRelayer): Promise => {
  const lightProvider = await LightProvider.init({
    wallet: solanaWallet,
    relayer: testRelayer,
    confirmConfig,
  });

  console.log("Light provider initialized");
  return lightProvider;
};

Nous initialisons ensuite le Light Provider avec notre fonction judicieusement nommée initializeLightProvider(). Cette fonction configure un provider et le relie à notre wallet Solana ainsi qu’au relais de test à l’aide de la configuration fournie par Light, confirmConfig. Celle-ci est de type ConfirmOptions et gère notamment l’étape de vérification des transactions et les niveaux d’engagement.

Code
const initializeLightUser = async (lightProvider: LightProvider): Promise => {
  const user = await User.init({ provider: lightProvider });
  console.log("Light user initialized");
  return user;
};

La fonction initializeLightUser est assez explicite : elle initialise un utilisateur pour Light Protocol à l’aide du lightProvider transmis.

Code
const performShieldOperation = async (user: User) => {
  await user.shield({
    publicAmountSol: "1.1",
    token: "SOL",
  });
  console.log("Performed shield operation");
};

La fonction performShieldOperation permet à l’utilisateur de protéger des SOL afin de les rendre confidentiels. Ici, nous protégeons plus de 1 SOL, même si nous n’envoyons qu’1 SOL protégé, afin de couvrir les frais.

Code
const executePrivateTransfer = async (user: User, testRecipientPublicKey: string) => {
  const response = await user.transfer({
    amountSol: "1",
    token: "SOL",
    recipient: testRecipientPublicKey,
  });
  console.log(`Executed private transfer! Txt hash: ${response.txHash}`);
};

Il s’agit probablement de la fonction utilitaire la plus importante, car c’est elle qui exécute réellement un transfert privé. Ici, nous transférons de manière confidentielle 1 SOL depuis user vers le destinataire indiqué, testRecipientPublicKey.

Code
const main = async () => {
  try {
    const solanaWallet = await initializeSolanaWallet();
    const connection = new Connection("http://127.0.0.1:8899");

    await requestAirdrop(connection, solanaWallet.publicKey);

    const testRelayer = await setupTestRelayer(solanaWallet);
    const lightProvider = await initializeLightProvider(solanaWallet, testRelayer);
    const user = await initializeLightUser(lightProvider);

    await performShieldOperation(user);

    const testRecipientKeypair = Keypair.generate();

    await requestAirdrop(connection, testRecipientKeypair.publicKey);

    const lightProviderRecipient = await initializeLightProvider(testRecipientKeypair, testRelayer);
    const testRecipient = await initializeLightUser(lightProviderRecipient);

    await executePrivateTransfer(user, testRecipient.account.getPublicKey());
    console.log(`Successfully sent 1 $SOL to ${testRecipient.account.getPublicKey()} privately!`);
  } catch (e) {
    console.error(`Error sending SOL via Light: ${e}`);
  } finally {
    console.log("Exiting program...");
    process.exit(0);
  }
};

La fonction main sert de point d’entrée à notre script. Nous la déclarons comme asynchrone, car plusieurs fonctions async sont appelées dans sa portée. Elle exécute toutes les étapes dans l’ordre, de l’initialisation du wallet à l’exécution d’un transfert privé. Nous configurons d’abord notre wallet Solana et notre connexion. Nous envoyons ensuite des SOL par airdrop à notre wallet, puis initialisons le relais et le Light Provider. Nous initialisons notre wallet en tant que Light User et protégeons nos SOL. Nous créons ensuite un destinataire de test, lui envoyons des SOL par airdrop, l’initialisons en tant que Light User et lui envoyons 1 SOL de manière confidentielle. Comme dans notre code Elusiv, nous plaçons la logique dans un bloc try-catch-finally afin d’intercepter les erreurs et de quitter automatiquement le programme une fois l’opération terminée.

Vous remarquerez peut-être que, contrairement à Elusiv où nous utilisions devnet, nous définissons ici la connexion sur « http://127.0.0.1:8899 ». Au moment de la rédaction de cet article, Light Protocol n’est pas déployé sur devnet/mainnet. Les clés Helius ne fonctionnent donc pas encore (👀). Pour localhost, vous devrez exécuter un validateur de test avec les comptes et programmes nécessaires préchargés afin d’exécuter votre code. Pour cela, vous pouvez installer la CLI de Light Protocol et exécuter la commande suivante :

Code
light test-validator

Cette commande démarrera un validateur en arrière-plan. Pour le moment, vous devrez également ajouter manuellement chai , une bibliothèque d’assertions facilitant les tests, car la classe UTXO contient des tests unitaires. Ce problème devrait toutefois être résolu dans la prochaine version. Votre package.json pour ce script devrait ressembler à ceci :

Code
{
  "name": "helius-tutorial",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "@lightprotocol/cli": "^0.1.1-alpha.21",
    "@types/node": "^20.6.0",
    "chai": "^4.3.8",
    "typescript": "^5.2.2"
  },
  "dependencies": {
    "@coral-xyz/anchor": "^0.28.1-beta.2",
    "@lightprotocol/zk.js": "^0.3.2-alpha.14",
    "@solana/web3.js": "^1.78.4"
  }
}

Conclusion

Félicitations ! Dans ce tutoriel, nous avons étudié les subtilités de la confidentialité, son importance et la manière d’effectuer des transactions SOL confidentielles avec Elusiv et Light Protocol. Avec Elusiv, nous avons vu comment récupérer des soldes privés, envoyer des SOL de manière confidentielle et même automatiser l’approvisionnement lorsque le solde privé est épuisé ou inexistant. Avec Light Protocol, nous avons découvert comment configurer son processus d’initialisation complexe et hautement modulaire, protéger nos SOL et exécuter un transfert privé.

Vous vous demandez peut-être lequel utiliser. Cela dépend ! Avec Elusiv, notre code se concentrait sur l’approvisionnement d’un solde privé depuis un pool partagé et sur l’envoi confidentiel de transactions. Pour cela, le script interagit avec un smart contract prédéfini qui régit le pool Elusiv. Avec Light Protocol, nous avons mis en place un processus d’initialisation plus flexible, quoique complexe, afin de créer un workflow gérant plusieurs utilisateurs qui interagissent avec des programmes Solana préservant la confidentialité. Avec Light, nous pouvons également créer nos propres programmes Solana privés (PSP) personnalisés si nécessaire. Les deux solutions impliquent des compromis dans des domaines tels que la documentation et la mise en œuvre précise, mais ces deux protocoles visent à assurer la confidentialité et la sécurité des transactions. Si votre application nécessite un contrôle plus fin des fonctionnalités de confidentialité et que vous souhaitez chiffrer certaines parties de son état, envisagez d’utiliser Light Protocol. Si vous développez une solution plus simple et souhaitez ajouter une couche de confidentialité aux transferts de tokens sur devnet/mainnet, Elusiv peut constituer un meilleur choix.

Les techniques, le code et les enseignements présentés dans cet article offrent un point de départ solide pour développer des applications axées sur la confidentialité sur Solana. L’importance de la confidentialité dans la technologie blockchain ne doit pas être sous-estimée. À mesure que la demande de systèmes décentralisés augmente, le besoin de fonctionnalités de confidentialité capables de donner davantage de contrôle aux utilisateurs, de protéger leurs droits et de concrétiser les véritables ambitions des premières technologies blockchain se renforce également. La confidentialité compte. Avec Elusiv et Light Protocol, vous pouvez désormais rendre Solana plus sûre !

Si vous avez lu jusqu’ici, anon, merci !

Ressources supplémentaires / Pour aller plus loin

‍

Abonnez-vous à Helius

Suivez les dernières actualités du développement sur Solana et recevez une notification à chaque publication