NEU: Teste die Parsed Events Scan API
Datenschutz auf Solana mit Elusiv und Light Protocol
Blog/Entwicklung

Datenschutz auf Solana mit Elusiv und Light: Alles, was du über Datenschutz für SOL wissen musst

Developer Experience Engineer0xIchigo auf X0xIchigo auf LinkedIn0xIchigo auf GitHub
16 Min. Lesezeit

Voraussetzungen

  • Node.js ist installiert (um die erforderlichen Abhängigkeiten zu installieren, die Skripte auszuführen und $SOL privat zu senden)
  • Grundkenntnisse in TypeScript
  • Grundkenntnisse in @solana/web3.js

Worum geht es in diesem Artikel?

Datenschutz steht im Zentrum moderner Blockchain-Technologie. Öffentliche Ledger-Systeme, Transaktionen, Kontostände und häufig auch der Quellcode eines Programms sind für alle einsehbar. Diese beispiellose Transparenz gilt zwar als Markenzeichen der Blockchain-Technologie, doch für viele Anwendungen und den alltäglichen Gebrauch ist Datenschutz unverzichtbar.

Sowohl Elusiv als auch Light Protocol bieten fortschrittliche Datenschutzlösungen für Solana. Sie nutzen zk-SNARKS für private Transaktionen, private Programme und dezentrale Compliance-Lösungen. In diesem Artikel befassen wir uns mit der Bedeutung von Datenschutz, erklären, warum Blockchains wie Solana Datenschutzfunktionen benötigen, und zeigen dir Schritt für Schritt, wie du SOL mit dem TypeScript SDK von Elusiv und dem zk.js-Modul von Light privat sendest.

Datenschutz ist wichtig

Datenschutz ist ein grundlegendes Menschenrecht und entscheidend für Autonomie und den Schutz der Menschenwürde. Eine freie und offene Gesellschaft braucht Datenschutz, denn er bildet die Grundlage für weitere Rechte wie Meinungs- und Vereinigungsfreiheit. So entsteht ein Raum außerhalb des politischen Lebens, in dem jeder denken, sprechen, Ideen äußern oder handeln kann, ohne ständig von Dritten überwacht zu werden. Jüngste öffentliche Kontroversen wie Edward Snowdens Enthüllungen, die von WikiLeaks veröffentlichten Informationen und der Datenskandal um Facebook und Cambridge Analytica haben intensive Debatten über die Bedeutung und Grenzen des Datenschutzes ausgelöst.

Datenschutz geht alle etwas an

Auf den ersten Blick mag es widersprüchlich erscheinen, öffentliche Ledger, die auf dezentralen Technologien basieren und Transparenz als einen ihrer Grundsätze verstehen, mit Datenschutzfunktionen auszustatten. Ob überzeugter Cypherpunk oder durchschnittlicher Nutzer: Datenschutz sollte allen wichtig sein. Vielleicht denkst du, dass du online nichts Schädliches oder Illegales tust und es daher egal ist, wer deine Aktivitäten auf der Blockchain sieht. Betrachte dazu dieses Gedankenexperiment:

Es gibt zwei Figuren: Alice, eine durchschnittliche Nutzerin, die ihre Privatsphäre schätzt und Einkäufe unbeobachtet tätigen möchte, und einen böswilligen Akteur, den ich mit MA abkürze. Ein solcher Akteur könnte beispielsweise eine repressive Regierung, ein kontrollierender Arbeitgeber oder ein Cyberkrimineller sein. Angenommen, Alice lebt in einem Land, dessen Regierung alle Bürger ständig überwacht, oder sie arbeitet für ein Unternehmen mit strengen Richtlinien gegen bestimmte legale Käufe, etwa politische Spenden oder Bücher zu bestimmten Themen. Eines Tages bezahlt Alice über eine herkömmliche Blockchain wie Solana, weil sie diese für eine sichere und private Zahlungsmethode hält. Dann geschieht Folgendes:

  • Alices Transaktion wird im öffentlichen Ledger erfasst. Sie enthält ihren öffentlichen Schlüssel, den öffentlichen Schlüssel des Empfängers und den gesendeten Betrag
  • MA möchte Alices Verhalten überwachen oder kontrollieren und beginnt, ihre Transaktionen anhand ihres öffentlichen Schlüssels nachzuverfolgen
  • MA entdeckt Alices Kauf und stuft ihn nach den eigenen Regeln als „anstößig“ ein
  • MA geht gegen Alice vor. Je nach Umständen kann das rechtliche Schritte, eine Inhaftierung, Sanktionen am Arbeitsplatz, öffentliche Bloßstellung oder Erpressung bedeuten

Dieses Gedankenexperiment zeigt deutlich, wie sich fehlender Datenschutz auf der Blockchain auf durchschnittliche Nutzer auswirken kann. Durch die Zunahme von On-Chain-Ermittlern und böswilligen Technologien zur Datensammlung wird es immer einfacher, die Transaktionen durchschnittlicher Nutzer nachzuverfolgen. Das kann erhebliche Folgen in der realen Welt haben. In einer zunehmend digitalen Welt riskieren wir ohne Datenschutz den Verlust individueller Souveränität: Alle unsere Entscheidungen werden überwacht und jeder Aspekt unseres Lebens wird zur Ware. Blockchains werden diese Entwicklung nur beschleunigen, wenn wir keine datenschutzfreundlichen Technologien implementieren. Hier kommen Elusiv und Light Protocol ins Spiel.

Was ist Elusiv?

Elusiv beschreibt sich selbst als „Blockchain-basiertes Zero-Knowledge-Datenschutzprotokoll“, das „Nutzern und Anwendungen Zugriff auf universelle Verschlüsselung ermöglicht“. Durch Zero-Knowledge-Kryptografie und Multi-Party Computation auf Nutzer- und Netzwerkebene möchte Elusiv Datenschutz bieten, ohne Sicherheit und Dezentralisierung zu beeinträchtigen. Das Ziel einer universellen Verschlüsselung ermöglicht Elusiv, Werkzeuge bereitzustellen, mit denen Nutzer ihre Daten kontrollieren. Sie können selbst entscheiden, was auf der Blockchain geteilt wird und was nicht.

Wie funktioniert Elusiv?

Bei Elusiv senden Nutzer Geld an einen gemeinsamen Pool, den das Elusiv-Programm verwaltet. Dadurch laden sie ihr privates Guthaben auf. Aus dem Pool können sie Token wie SOL senden und empfangen sowie Geld abheben. Wichtig ist: Bei diesem gemeinsamen Pool lassen sich Einzahlungen an Elusiv und Zahlungen aus Elusiv weiterhin beobachten. Mit steigender Nutzer- und Transaktionszahl wird es jedoch immer schwieriger, Einzahlungen und Zahlungen bestimmten Konten zuzuordnen. Einen einzelnen Nutzer, der 10 SOL einzahlt und 10 SOL abhebt, kann man leichter identifizieren als einen Nutzer unter Millionen, der schrittweise Einzahlungen und Zahlungen vornimmt. Anonymität braucht Gesellschaft.

Das Geld im Pool enthält keine Informationen über die Einzahler. Mithilfe von Zero-Knowledge-Beweisen werden dem Geld eines Nutzers jedoch geheime Werte zugeordnet. So lassen sich Berechtigungsnachweise für noch nicht ausgegebenes Geld erstellen. Die in Elusiv verwendeten Zero-Knowledge-Beweise heißen zk-SNARKS (Zero-Knowledge Succinct Non-Interactive Argument of Knowledge). Ohne auf die Komplexität von zk-SNARK-Beweisen einzugehen, kannst du sie dir als mathematisch bewiesene Methode vorstellen, mit der du einem Freund zeigst, dass du ein geheimes Passwort kennst, ohne es ihm jemals zu verraten. Dein Freund kann schnell prüfen, ob du die Wahrheit sagst, ohne weitere Fragen stellen zu müssen. So kann Elusiv dein noch nicht ausgegebenes Geld erfassen, ohne ganz Solana mitzuteilen, wie viel du im Pool hast. Wenn du mehr über Zero-Knowledge-Beweise erfahren möchtest, empfehle ich dir Porters Kurs über Zero-Knowledge-Beweise.

Was ist Light Protocol?

Light Protocol beschreibt sich selbst als „[n]ächste Generation des zkLayer auf Solana“, die private Programmausführung direkt auf Solana ermöglicht. Drei grundlegende Konzepte von Light ermöglichen Datenschutz auf Solana:

  • Privater Zustand
  • UTXO-Modell
  • Verifizierung privater Zustandsübergänge on-chain

Wie funktioniert Light Protocol?

Light Protocol ermöglicht die Verschlüsselung des On-Chain-Zustands. Nutzer „besitzen“ diesen Zustand und verfügen über exklusive Entschlüsselungsrechte. Anders als beim Pool-Ansatz von Elusiv führt Light Protocol ein UTXO-Modell (Unspent Transaction Outputs) auf Solana ein. Ein UTXO ist mit einem geschützten Schlüsselpaar verknüpft und kann zwei Guthaben enthalten: eines in SOL, da dies der Gebührentoken in Light Protocol ist, und eines in einem anderen SPL-Token. Bei diesem Ansatz erzeugt jede Transaktion einen neuen UTXO, der an eine lineare Liste angehängt wird. Dadurch werden direkte Aktualisierungen vermieden. Der Zustand bleibt verborgen, statt Informationen darüber offenzulegen, welcher Zustand von wem aktualisiert wird.

Programmlogik auf Solana besteht fast immer aus einem Escrow mit bestimmten Einschränkungen. Light Protocol entwickelt dieses Design weiter, indem es die Programmlogik clientseitig in Zero-Knowledge-Schaltkreisen (zk-SNARKS) codiert. Dort werden eine Berechnung und ein Beweis erzeugt. Dieser Beweis wird on-chain geteilt und von einem speziell für den Schaltkreis entwickelten Verifizierungsprogramm geprüft. Nach erfolgreicher Verifizierung ruft das benutzerdefinierte Verifizierungsprogramm einen weiteren nativen Verifizierer auf. Dieser führt verschiedene Sicherheitsprüfungen durch und fügt die neuen Commitments dem Merkle-Tree-Programm von Light hinzu.

Obwohl dies den Rahmen dieses Artikels sprengt, kannst du mit Light dein eigenes benutzerdefiniertes Private Solana Program (PSP) von Grund auf erstellen. Lies zuerst den restlichen Artikel und sieh dir die Dokumentation an, bevor du dein eigenes Programm erstellst. Die Dokumentation enthält eine Anleitung zum Erstellen eines benutzerdefinierten PSP. Du findest sie hier.

Das klingt alles gut, aber wie kann ich es direkt bei der Programmierung auf Solana einsetzen?

SOL mit Elusiv privat senden

Der vollständige Code

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();

Den Elusiv-Code Schritt für Schritt verstehen

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

Zuerst importieren wir die Bibliothek noble-ed25519, da wir keine Wallet-Verbindung eingerichtet haben. In einer Produktionsumgebung würdest du stattdessen den Solana Wallet Adapter verwenden, um Transaktionen zu signieren. Hier benötigen wir nur die Funktion sign. Bei der Verwendung von import { sign } from "@noble/ed25519"; kann jedoch gelegentlich der Fehler auftreten, dass etc.sha512Sync nicht festgelegt ist. Um diesen Fehler zu vermeiden, legen wir den Wert selbst fest. Mach dir darüber nicht zu viele Gedanken.

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

Anschließend importieren wir Solanas web3.js und das TypeScript SDK von Elusiv mit den erforderlichen Modulen.

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

Hier definieren wir CLUSTER als "devnet" und legen als RPC das Helius devnet RPC fest. Wenn du dieses Skript auf mainnet verwenden möchtest, weist du CLUSTER den Wert "mainnet" zu und holst dir einen Helius API-Schlüssel über einen der folgenden Pläne 😎

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
}

Wir markieren unsere Funktion main als asynchron, da wir innerhalb ihres Blocks mehrere Funktionen mit await aufrufen. Anschließend stellen wir mit RPC eine Verbindung zu devnet her und erstellen über die Hilfsfunktion generateKeypair ein Keypair-Objekt. Mit derselben Funktion generateKeypair erstellen wir außerdem ein recipientPublicKey, damit wir die geschützten SOL an einer zufälligen Adresse empfangen können. Die Funktion generateKeypair ist wie folgt definiert:

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

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

	return keyPair;
};

Diese Funktion erstellt ein Solana-Testschlüsselpaar, das wir für dieses Skript verwenden. Wenn du das Skript auf mainnet einsetzen möchtest, ersetzt du das Schlüsselpaar durch das deiner eigenen Wallet. Speichere deinen privaten Schlüssel niemals als Klartext und importiere ihn korrekt über den richtigen Dateipfad.

Code
await airdropSol(keyPair);

Da wir neu erzeugte Testschlüsselpaare verwenden, benötigen wir etwas devnet SOL. Mit der Hilfsfunktion aidropSol führen wir einen Airdrop von SOL an unser neues Schlüsselpaar durch. Sie ist wie folgt definiert:

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}`);
  }
};

Hier senden wir uns per Airdrop 1 SOL (1 * LAMPORTS_PER_SOL) auf devnet. Wir rufen latestBlockHash ab, damit wir den Blockhash und die letzte gültige Blockhöhe an connection.confirmTransaction() übergeben können. So bestätigen wir, dass 1 SOL per Airdrop an die richtige Adresse gesendet wurde.

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

Elusiv benötigt einen Eingabe-Seed, damit bestimmte Vorgänge deterministisch ablaufen. Deshalb importieren und verwenden wir SEED_MESSAGE. SEED_MESSAGE ist eine erforderliche zu signierende Nachricht, aus der der Elusiv-Seed erzeugt wird. Damit kann die Anwendung deine privaten Assets samt den zugehörigen geheimen Werten entschlüsseln, sodass du dein Geld nachverfolgen kannst. Mit Buffer.from() erstellen wir aus SEED_MESSAGE einen neuen Buffer, also eine rohe binäre Datenstruktur, mit UTF-8-Codierung. Wir müssen slice verwenden, da beim Schlüsselpaar-Typ von Solana die ersten 32 Byte den privaten Schlüssel und die letzten 32 Byte den öffentlichen Schlüssel enthalten. So signieren wir die erforderliche Seed-Nachricht mit unserem privaten Schlüssel.

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

Hier definieren wir elusiv als Elusiv-Instanz. Dafür verwenden wir seed, keyPair.publicKey, connection und CLUSTER. So erstellen wir auf devnet eine Elusiv-Instanz mit dem devnet RPC, unserem öffentlichen Schlüssel und der langen Nachricht, die wir gerade signiert haben.

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

Anschließend rufen wir unser privates Guthaben ab, also die Anzahl der Lamports im Elusiv-Pool. Der Wert wird als BigInt zurückgegeben. Der restliche Teil der Funktion main ist in try-catch-finally block gekapselt. Dieser Block ist wie folgt definiert:

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
	}
}

Wenn wir ein privates Guthaben haben, ist balance größer als 0 und der erste Abschnitt des Blocks if-else wird ausgeführt. In der Bedingung if wandeln wir 0 in BigInt um, weil der Rückgabetyp von getLatestPrivateBalance BigInt ist. Danach erstellen wir signature und setzen die Variable auf den Rückgabewert von send(). Die Funktion send() ist wie folgt definiert:

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);
};

Diese Funktion sendet unsere Transaktion über Elusiv und gibt ein Promise vom Typ ConfirmedSignatureInfo zurück, also eine bestätigte Signatur mit ihrem Status. Als Argumente verwenden wir eine Elusiv-Instanz, den öffentlichen Schlüssel des Empfängers, den zu sendenden Betrag und den Token-Typ. Dieser muss nicht Lamports sein. Auch andere Token wie USDC, USDT, mSOL, BONK und SAMO sind möglich. Anschließend setzen wir txt mit elusiv.buildSendTx(). buildSendTx() ist eine Methode der Elusiv-Instanz, die eine Transaktion zum Senden von Token aus dem privaten Guthaben an einen bestimmten Empfänger erstellt. Mit unserer Anweisung return elusiv.sendElusivTx(txt) senden wir die erstellte Transaktion txt und geben sie zurück.

Wenn wir kein privates Guthaben haben, wird der Block else ausgeführt:

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}`);

Wir informieren den Nutzer im Log darüber, dass kein privates Guthaben vorhanden ist und wir es aufladen werden. Anschließend erstellen wir die Aufladetransaktion mit der Methode .buildTopUpTx() der Elusiv-Instanz. Wir übergeben den Wert 1 SOL und den Token-Typ Lamports, da wir SOL senden. Danach signieren wir die Aufladung mit .partialSign() auf dem Schlüsselpaar der Transaktion, weil wir von unserem öffentlichen Schlüssel aufladen. Mit elusiv.sendElusivTx(topUpTxData) senden wir die erstellte Transaktion und protokollieren ihre Signatur.

Der restliche Teil des Blocks try-catch-finally sieht wie folgt aus:

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

Wenn beim Senden der SOL über Elusiv Fehler auftreten, geben wir sie mit console.error() in der Konsole aus. Anschließend beenden wir das Programm mit process.exit(0).

Um den gesamten Prozess zu starten, rufen wir am Ende der Datei die Funktion main() auf.

SOL mit Light Protocol privat senden

Der vollständige 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";

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();

Den Code Schritt für Schritt verstehen

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";

Zuerst importieren wir die erforderlichen Module aus Anchor, Solana/web3.js und Light, um über Light Protocol mit Solana zu interagieren. Danach definieren wir mehrere Hilfsfunktionen, bevor wir zu unserer Funktion main kommen. Light hat einen komplexeren Initialisierungsprozess als unsere Elusiv-Implementierung.

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

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

Hier erstellen wir die Funktion initializeSolanaWallet. Sie erzeugt mit der Methode Keypair.generate() eine neue Solana-Wallet.

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

Anders als Elusiv stellt Light in seiner Bibliothek die Funktion airdropSol bereit, um Lamports per Airdrop an einen bestimmten öffentlichen Schlüssel zu senden. Wir erstellen die Hilfsfunktion requestAirdrop, um SOL an das übergebene publicKey zu senden.

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;
};

Um geschützte SOL über Light zu senden, müssen wir mit dem Modul TestRelayer einen Test-Relayer einrichten. Relayer spielen im Light-Ökosystem eine wichtige Rolle. Diese Webserver leiten ZK-Beweise, die Zustandsübergänge nachweisen, vom Client an das jeweilige On-Chain-Programm weiter. Außerdem bezahlen sie die Transaktionsgebühren und erhalten dafür bei erfolgreicher Verifizierung des Beweises eine Gebühr. Die Gebühren betragen 100.000 Lamports. Diesen Wert legen wir mit new BN(100_000) fest.

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;
};

Anschließend initialisieren wir den Light Provider mit unserer passend benannten Funktion initializeLightProvider(). Diese Funktion richtet einen Provider ein und verknüpft ihn mit unserer Solana-Wallet und dem Test-Relayer. Dafür verwendet sie die von Light bereitgestellte Konfiguration confirmConfig. Sie hat den Typ ConfirmOptions und behandelt unter anderem die Transaktionsverifizierung und Commitment-Level.

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

Die Funktion initializeLightUser erklärt sich weitgehend selbst: Sie initialisiert mit dem übergebenen lightProvider einen Nutzer für Light Protocol.

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

Mit der Funktion performShieldOperation kann der Nutzer SOL schützen und damit privat machen. Hier schützen wir mehr als 1 SOL, obwohl wir nur 1 geschütztes SOL senden, um die Gebühren abzudecken.

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}`);
};

Dies ist wohl die wichtigste Hilfsfunktion, da sie tatsächlich eine private Übertragung ausführt. Hier übertragen wir privat 1 SOL von user an den angegebenen Empfänger 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);
  }
};

Die Funktion main dient als Einstiegspunkt für unser Skript. Wir markieren sie als asynchron, da in ihrem Gültigkeitsbereich mehrere asynchrone Funktionen aufgerufen werden. Sie führt alle Schritte nacheinander aus: von der Initialisierung der Wallet bis zur privaten Übertragung. Zuerst richten wir unsere Solana-Wallet und die Verbindung ein. Dann senden wir per Airdrop SOL an unsere Wallet und initialisieren den Relayer und Light Provider. Anschließend initialisieren wir unsere Wallet als Light User und schützen unsere SOL. Danach erstellen wir einen Testempfänger, senden ihm per Airdrop etwas SOL, initialisieren ihn als Light User und senden ihm privat 1 SOL. Wie bei unserem Elusiv-Code setzen wir die Logik in einen Block try-catch-finally, um Fehler abzufangen und das Programm nach Abschluss automatisch zu beenden.

Dir fällt vielleicht auf, dass wir hier anders als bei Elusiv nicht devnet verwenden, sondern die Verbindung auf „http://127.0.0.1:8899“ setzen. Zum Zeitpunkt der Erstellung dieses Artikels ist Light Protocol nicht auf devnet/mainnet bereitgestellt. Helius-Schlüssel funktionieren daher noch nicht (👀). Für localhost musst du einen Test-Validator mit den erforderlichen vorab geladenen Konten und Programmen ausführen, damit dein Code funktioniert. Installiere dafür die Light Protocol CLI und führe den folgenden Befehl aus:

Code
light test-validator

Dadurch wird im Hintergrund ein Validator gestartet. Derzeit musst du außerdem chai , eine Assertion-Bibliothek für einfachere Tests, manuell hinzufügen, da die UTXO-Klasse einige Unit-Tests enthält. Das sollte jedoch mit der nächsten Version behoben sein. Deine package.json für dieses Skript sollte ungefähr so aussehen:

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"
  }
}

Fazit

Glückwunsch! In diesem Tutorial haben wir uns ausführlich mit Datenschutz, seiner Bedeutung und dem privaten Senden von SOL über Elusiv und Light Protocol beschäftigt. Bei Elusiv haben wir gesehen, wie du private Guthaben abrufst, SOL privat sendest und automatische Aufladungen ausführst, wenn das private Guthaben aufgebraucht oder nicht vorhanden ist. Bei Light Protocol haben wir den komplexen, stark modularen Initialisierungsprozess eingerichtet, unsere SOL geschützt und eine private Übertragung ausgeführt.

Du fragst dich vielleicht, welche Lösung du verwenden solltest. Das kommt darauf an! Bei Elusiv konzentriert sich unser Code darauf, ein privates Guthaben aus einem gemeinsamen Pool aufzuladen und Transaktionen privat zu senden. Dazu interagiert das Skript mit einem vordefinierten Smart Contract, der den Elusiv-Pool verwaltet. Bei Light Protocol haben wir einen flexibleren, aber komplexeren Initialisierungsprozess eingerichtet. So entsteht ein Workflow, der mehrere Nutzer verwaltet, die mit datenschutzfreundlichen Solana-Programmen interagieren. Mit Light können wir bei Bedarf auch eigene benutzerdefinierte Private Solana Programs (PSPs) erstellen. Beide Lösungen haben Vor- und Nachteile, etwa bei der Dokumentation und der konkreten Implementierung. Beide Protokolle zielen jedoch auf Datenschutz und sichere Transaktionen ab. Wenn deine Anwendung eine präzisere Kontrolle über Datenschutzfunktionen erfordert und du bestimmte Teile des Anwendungszustands verschlüsseln möchtest, solltest du Light Protocol in Betracht ziehen. Entwickelst du etwas Einfacheres und möchtest Token-Übertragungen auf devnet/mainnet um eine Datenschutzebene ergänzen, ist Elusiv möglicherweise die bessere Wahl.

Die Techniken, der Code und die Erkenntnisse aus diesem Artikel bilden einen soliden Ausgangspunkt für datenschutzorientierte Anwendungen auf Solana. Die Bedeutung von Datenschutz in der Blockchain-Technologie lässt sich kaum überschätzen. Mit der steigenden Nachfrage nach dezentralen Systemen wächst auch der Bedarf an Datenschutzfunktionen, die Nutzer stärken, ihre Rechte schützen und die ursprünglichen Ziele der Blockchain-Technologie verwirklichen. Datenschutz ist wichtig. Mit Elusiv und Light Protocol kannst du Solana jetzt zu einer sichereren Blockchain machen!

Wenn du bis hierher gelesen hast, Anon: Danke!

Zusätzliche Ressourcen und weiterführende Literatur

‍

Helius abonnieren

Bleib bei der Solana-Entwicklung auf dem Laufenden und erhalte Updates, wenn wir neue Beiträge veröffentlichen