NOVO: Experimente a API Parsed Events Scan
privacidade na Solana com Elusiv e Light Protocol
Blog/Desenvolvimento

Privacidade na Solana com Elusiv e Light: tudo o que você precisa saber sobre privacidade em SOL

Developer Experience Engineer0xIchigo no X0xIchigo no LinkedIn0xIchigo no GitHub
16 min de leitura

Pré-requisitos

  • Node.js instalado (para instalar as dependências necessárias, executar os scripts e enviar $SOL de forma privada)
  • Conhecimento básico de TypeScript
  • Conhecimento básico de @solana/web3.js

Sobre o que é este artigo?

A privacidade está no centro da tecnologia blockchain moderna. Em sistemas de registros públicos, transações, saldos de contas e, muitas vezes, o código-fonte de um programa ficam disponíveis para qualquer pessoa consultar. Embora esse nível sem precedentes de transparência seja considerado uma característica essencial da tecnologia blockchain, a privacidade pode ser um requisito vital para muitas aplicações ou para o uso cotidiano.

Tanto a Elusiv quanto o Light Protocol oferecem soluções de ponta para privacidade na Solana, aproveitando zk-SNARKS para transações privadas, programas privados e soluções descentralizadas de conformidade. Neste artigo, analisamos a importância da privacidade, exploramos por que blockchains como a Solana precisam de recursos de privacidade e apresentamos um guia passo a passo para enviar SOL de forma privada usando o SDK TypeScript da Elusiv e o módulo zk.js do Light.

A privacidade é importante

A privacidade é um direito humano fundamental, essencial para a autonomia e a preservação da dignidade humana. Ela é necessária para uma sociedade livre e aberta, pois sustenta o exercício de outros direitos, como a liberdade de expressão e de associação. Isso permite criar um espaço separado da vida política, garantindo que qualquer pessoa possa pensar, falar, expressar ideias ou agir sem ser constantemente monitorada por terceiros. Controvérsias públicas recentes, como as revelações de Edward Snowden, as informações expostas pelo WikiLeaks e o escândalo de dados do Facebook-Cambridge Analytica, provocaram debates intensos sobre a importância e os limites da privacidade.

Todos deveriam se importar com a privacidade

À primeira vista, pode parecer contraditório defender que registros públicos, construídos sobre tecnologias descentralizadas que promovem a transparência como um de seus princípios fundamentais, tenham recursos de privacidade. Seja você um cypherpunk convicto ou um usuário comum, todos deveriam se importar com a privacidade. Talvez você pense que, como não faz nada prejudicial ou ilegal online, não importa que vejam o que você faz na blockchain. Considere este experimento mental:

Temos dois personagens: Alice, uma usuária comum que valoriza sua privacidade e quer fazer compras sem ser investigada; e um agente mal-intencionado, que abreviarei como MA. Um agente mal-intencionado pode ser, por exemplo, um governo opressor, um empregador controlador ou um cibercriminoso. Digamos que Alice viva em um país cujo governo vigia constantemente cada cidadão, ou trabalhe para uma empresa com políticas rígidas contra determinadas compras legais (por exemplo, doações políticas ou a compra de livros sobre certos temas). Um dia, Alice faz uma compra usando uma blockchain tradicional, como a Solana, acreditando que seja uma forma de pagamento segura e privada. A sequência de eventos é a seguinte:

  • A transação de Alice é registrada no livro-razão público. A transação inclui a chave pública dela, a chave pública do destinatário e o valor enviado
  • MA, querendo monitorar ou controlar o comportamento de Alice, começa a rastrear as transações dela por meio de sua chave pública
  • MA descobre a compra de Alice, que considera "censurável" segundo suas regras
  • MA age contra Alice, o que, dependendo das circunstâncias, pode envolver ação judicial, prisão, sanções no trabalho, constrangimento público ou chantagem

Esse experimento mental ilustra a dura realidade de como a falta de privacidade na blockchain pode afetar o usuário comum. Com o crescimento de investigadores on-chain e de tecnologias mal-intencionadas de coleta de dados, está se tornando bastante fácil rastrear as transações de um usuário comum, o que pode gerar consequências significativas no mundo real. Em um mundo cada vez mais digital, sem privacidade corremos o risco de perder a soberania individual, ter todas as nossas decisões monitoradas e ver cada aspecto da nossa existência transformado em mercadoria. As blockchains apenas acelerarão esse processo se não implementarmos tecnologias que viabilizem a privacidade. É aqui que entram a Elusiv e o Light Protocol.

O que é a Elusiv?

A Elusiv se define como "um protocolo de privacidade baseado em blockchain e em conhecimento zero" que "permite que usuários e aplicações acessem criptografia universal". A Elusiv busca garantir privacidade sem sacrificar proteção, segurança e descentralização, aplicando criptografia de conhecimento zero e computação multipartidária ao usuário e à rede. Esse objetivo de criptografia universal permite que a Elusiv forneça ferramentas para que os usuários controlem seus dados. Agora, os usuários podem escolher o que é compartilhado na blockchain e o que não é.

Como a Elusiv funciona?

A Elusiv funciona permitindo que os usuários enviem fundos para um pool compartilhado, administrado pelo programa da Elusiv, que recarrega seus saldos privados. A partir do pool, os usuários podem enviar e receber tokens como SOL, além de sacar fundos. É importante observar que, nesse modelo de pool compartilhado, ainda é possível ver depósitos e pagamentos feitos pela Elusiv. No entanto, à medida que aumentam o número de usuários e de transações, fica cada vez mais difícil vincular depósitos e pagamentos a contas específicas ao longo do tempo — é mais fácil relacionar um único usuário que deposita 10 SOL e retira 10 SOL do que identificar, entre milhões, um usuário que faz depósitos e pagamentos incrementais. O anonimato funciona melhor em grupo.

Os fundos no pool não contêm informações sobre seus depositantes, mas, usando provas de conhecimento zero, valores secretos são associados aos fundos de um usuário para que seja possível gerar provas de autoridade sobre fundos não gastos. As provas de conhecimento zero usadas na Elusiv são conhecidas como zk-SNARKS (Argumento de Conhecimento Sucinto, Não Interativo e de Conhecimento Zero). Sem entrar nas complexidades das provas zk-SNARK, pense nelas como uma maneira matematicamente comprovada de mostrar a um amigo que você conhece uma senha secreta sem jamais revelar qual é. Nesse caso, seu amigo consegue verificar rapidamente se você está dizendo a verdade sem precisar perguntar mais nada. Assim, a Elusiv consegue acompanhar seus fundos não gastos sem informar a toda a Solana quanto você tem no pool. Se quiser saber mais sobre provas de conhecimento zero, recomendo a aula de Porter sobre provas de conhecimento zero.

O que é o Light Protocol?

O Light Protocol se define como uma "camada zk de última geração na Solana" que permite a execução privada de programas diretamente na Solana. Há três conceitos fundamentais no Light que viabilizam a privacidade na Solana:

  • Estado privado
  • Modelo UTXO
  • Verificação on-chain de transições de estado privado

Como o Light Protocol funciona?

O Light Protocol permite criptografar o estado on-chain. Nesse modelo, os usuários são "proprietários" desse estado e têm direitos exclusivos de descriptografia. Diferentemente da abordagem de fundos agrupados da Elusiv, o Light Protocol introduz na Solana um modelo UTXO (saídas de transações não gastas). Um UTXO está vinculado a um par de chaves protegido e pode armazenar dois saldos: um em SOL (por ser o token usado para taxas no Light Protocol) e outro em um token SPL diferente. Nessa abordagem, cada transação gera um novo UTXO, que é adicionado a uma lista linear, evitando atualizações no local; o estado é ocultado, em vez de revelar informações sobre qual estado está sendo atualizado e por quem.

A lógica de programas na Solana quase sempre consiste em um escrow com algumas restrições específicas. O Light Protocol inova esse design ao codificar a lógica do programa em circuitos de conhecimento zero (zk-SNARKS) no lado do cliente, onde uma computação e uma prova são geradas. Essa prova é compartilhada on-chain, onde um programa verificador dedicado, criado especificamente para o circuito, confere sua validade. Após uma verificação bem-sucedida, o programa verificador personalizado chama outro verificador nativo, que conclui várias verificações de segurança e adiciona os novos compromissos ao programa de árvore de Merkle do Light.

Embora esteja fora do escopo deste artigo, o Light permite criar do zero seu próprio Programa Privado da Solana (PSP). Recomendamos que você leia o restante deste artigo e consulte a documentação antes de tentar criar o seu. A documentação inclui um tutorial sobre como criar seu próprio PSP personalizado, disponível aqui.

Tudo isso é muito interessante, mas como posso aplicar esses conceitos diretamente à programação na Solana?

Como enviar SOL de forma privada usando a Elusiv

O código completo

Código
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();

Análise detalhada do código da Elusiv

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

Primeiro, importamos a biblioteca noble-ed25519, pois não configuramos nenhuma conexão com uma carteira. Em um ambiente de produção, você usaria o Solana Wallet Adapter para assinar transações. Aqui, precisamos apenas da funcionalidade sign. No entanto, usar import { sign } from "@noble/ed25519"; às vezes pode retornar um erro informando que etc.sha512Sync não está definido. Para evitar esse erro, nós mesmos o definimos — não se preocupe muito com isso.

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

Em seguida, importamos o web3.js da Solana e o SDK TypeScript da Elusiv com os módulos necessários.

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

Aqui, definimos CLUSTER como "devnet" e configuramos nosso RPC como o RPC da devnet da Helius. Se você quisesse usar esse script na mainnet, atribuiria "mainnet" a CLUSTER e obteria uma chave de API da Helius em um dos seguintes planos 😎

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

	// Rest of the code
}

Marcamos nossa função main como assíncrona porque aplicamos await a várias funções dentro de seu bloco. Em seguida, estabelecemos nossa conexão com a devnet usando RPC e criamos um objeto Keypair a partir da função utilitária generateKeypair. Também criamos um recipientPublicKey com a função generateKeypair mencionada anteriormente, para recebermos o SOL protegido em um endereço aleatório. Como referência, a função generateKeypair é definida assim:

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

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

	return keyPair;
};

Essa função cria um par de chaves de teste da Solana que usaremos neste script. Se você quisesse usar isso na mainnet, substituiria o par de chaves pelo par de chaves da sua própria carteira. Lembre-se de nunca armazenar sua chave privada em texto simples e de importá-la corretamente usando o caminho de arquivo adequado.

Código
await airdropSol(keyPair);

Precisamos de um pouco de SOL da devnet porque estamos usando pares de chaves de teste recém-gerados. Usamos a função utilitária aidropSol para fazer um airdrop de SOL para nosso novo par de chaves. Ela é definida assim:

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

Aqui, fazemos um airdrop de 1 SOL (1 * LAMPORTS_PER_SOL) para nós mesmos na devnet. Recuperamos latestBlockHash para passar o blockhash e a última altura de bloco válida para connection.confirmTransaction(), confirmando assim que fizemos o airdrop de 1 SOL para o endereço correto.

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

A Elusiv exige uma seed de entrada para garantir que certas operações sejam determinísticas. Por isso, importamos e usamos SEED_MESSAGE. SEED_MESSAGE é uma mensagem obrigatória para assinatura, pois é usada para gerar a seed da Elusiv. Ela permite que a aplicação descriptografe seus ativos privados com os valores secretos associados, para que você possa acompanhar seus fundos. Usamos Buffer.from() para criar um novo buffer (uma estrutura de dados binários brutos) a partir de SEED_MESSAGE com codificação utf-8. Precisamos usar slice porque o tipo de par de chaves da Solana usa os primeiros 32 bytes como chave privada e os últimos 32 bytes como chave pública. Assim, assinamos a mensagem de seed obrigatória usando nossa chave privada.

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

Aqui, definimos elusiv, que é uma instância da Elusiv, usando nossos seed, keyPair.publicKey, connection e CLUSTER. Assim, criamos uma instância da Elusiv na devnet usando o RPC da devnet, nossa chave pública e a longa mensagem que acabamos de assinar.

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

Em seguida, buscamos nosso saldo privado — a quantidade de Lamports que temos no pool da Elusiv — que é retornado como um BigInt. O restante da função main fica encapsulado em um try-catch-finally block, definido assim:

Código
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
	}
}

Se tivermos saldo privado, balance será maior que 0 e o primeiro bloco do if-else será executado. Convertemos 0 para BigInt na condição if porque o tipo de retorno de getLatestPrivateBalance é BigInt. Depois, criamos signature, uma variável definida com o valor retornado por send(). A função send() é definida assim:

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

Essa função envia nossa transação pela Elusiv e retorna uma Promise do tipo ConfirmedSignatureInfo (uma assinatura confirmada com seu status). Como argumentos, recebemos uma instância da Elusiv, a chave pública de um destinatário, um valor a ser enviado e o tipo de token (que não precisa ser Lamports e pode ser qualquer um de vários outros tokens, como USDC, USDT, mSOL, BONK e SAMO). Em seguida, definimos txt usando elusiv.buildSendTx(). buildSendTx() é um método da instância da Elusiv que cria uma transação para enviar tokens a um destinatário específico usando o saldo privado. Depois, enviamos e retornamos a transação usando nossa instrução return elusiv.sendElusivTx(txt), passando a transação criada txt.

Se não tivermos saldo privado, o bloco else será executado:

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

Informamos ao usuário no log que ele não tem saldo privado e que faremos uma recarga. Em seguida, criamos a transação de recarga usando o método .buildTopUpTx() disponível na instância da Elusiv. Passamos o valor de 1 SOL e o tipo de token Lamports, pois estamos enviando SOL. Depois, assinamos a recarga com um .partialSign() no par de chaves da transação, pois estamos fazendo a recarga a partir da nossa chave pública. Por fim, enviamos a transação criada usando elusiv.sendElusivTx(topUpTxData) e registramos a assinatura da transação no log.

O restante do bloco try-catch-finally é o seguinte:

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

Se ocorrer algum erro ao enviar SOL pela Elusiv, nós o registramos no console usando console.error(). Em seguida, encerramos o programa usando process.exit(0).

Para iniciar todo esse processo, chamamos a função main() no final do arquivo.

Como enviar SOL de forma privada usando o Light Protocol

O código completo

Código
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();

Análise detalhada do código

Código
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";

Primeiro, importamos os módulos necessários do Anchor, Solana/web3.js e Light para interagir com a Solana usando o Light Protocol. Em seguida, definimos várias funções utilitárias antes de chegar à função main — o Light tem um processo de inicialização mais complexo em comparação com nossa implementação da Elusiv.

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

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

Aqui, criamos a função initializeSolanaWallet, que gera uma nova carteira Solana usando o método Keypair.generate().

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

Ao contrário da Elusiv, o Light fornece em sua biblioteca uma função airdropSol para fazer airdrop de Lamports para uma chave pública específica. Criamos a função utilitária requestAirdrop para enviar SOL ao publicKey fornecido.

Código
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;
};

Para enviar SOL protegido pelo Light, precisamos configurar um retransmissor de teste usando o módulo TestRelayer. Os retransmissores desempenham um papel importante no ecossistema do Light, pois são servidores web que encaminham do cliente para o respectivo programa on-chain as provas ZK que comprovam as transições de estado. Eles também pagam as taxas de transação e recebem uma taxa em troca quando a verificação da prova é bem-sucedida. As taxas são de 100 mil Lamports, valor que definimos com new BN(100_000).

Código
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;
};

Em seguida, inicializamos o Light Provider com nossa função apropriadamente chamada initializeLightProvider(). Essa função configura um provedor e o vincula à nossa carteira Solana e ao retransmissor de teste usando a configuração fornecida pelo Light, confirmConfig. Ela é do tipo ConfirmOptions e lida com a etapa de verificação da transação, os níveis de compromisso e outros aspectos.

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

A função initializeLightUser é bastante autoexplicativa: ela inicializa um usuário no Light Protocol usando o lightProvider fornecido.

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

A função performShieldOperation permite que o usuário proteja SOL, tornando-o privado. Aqui, protegemos mais de 1 SOL, embora enviemos apenas 1 SOL protegido, para cobrir as taxas.

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

Esta é provavelmente a função utilitária mais importante, pois é ela que realmente executa uma transferência privada. Aqui, transferimos de forma privada 1 SOL de user para o destinatário especificado testRecipientPublicKey.

Código
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);
  }
};

A função main serve como ponto de entrada do nosso script. Nós a marcamos como assíncrona porque várias funções async são chamadas dentro de seu escopo. Ela executa todas as etapas em sequência, começando pela inicialização da carteira e terminando com a execução de uma transferência privada. Primeiro, configuramos nossa carteira Solana e a conexão. Depois, fazemos um airdrop de SOL para nossa carteira e inicializamos o retransmissor e o Light Provider. Em seguida, inicializamos nossa carteira como um usuário do Light e protegemos nosso SOL. Depois, criamos um destinatário de teste, fazemos um airdrop de SOL para ele, inicializamos esse destinatário como um usuário do Light e enviamos 1 SOL de forma privada. Assim como no código da Elusiv, colocamos a lógica em um bloco try-catch-finally para capturar possíveis erros e encerrar automaticamente o programa após a conclusão.

Você pode notar que, ao contrário da Elusiv, em que usávamos a devnet, aqui configuramos a conexão como "http://127.0.0.1:8899". No momento em que este artigo foi escrito, é importante observar que o Light Protocol não está implantado na devnet/mainnet. Portanto, as chaves da Helius não funcionarão (ainda 👀). Para localhost, você deverá executar um test-validator com as contas e os programas necessários pré-carregados para rodar seu código. Para isso, você pode instalar a CLI do Light Protocol e executar o seguinte comando:

Código
light test-validator

Isso iniciará um validador em segundo plano. No momento, você também precisará adicionar manualmente o chai (uma biblioteca de asserções usada para facilitar testes), pois há alguns testes unitários na classe UTXO. No entanto, isso deve ser resolvido na próxima versão. Seu package.json para este script deve ser semelhante a este:

Código
{
  "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"
  }
}

Conclusão

Parabéns! Neste tutorial, exploramos em detalhes a privacidade, sua importância e como transacionar SOL de forma privada usando Elusiv e Light Protocol. Com a Elusiv, vimos como consultar saldos privados, enviar SOL de forma privada e até automatizar recargas quando o saldo privado está esgotado ou não existe. Com o Light Protocol, vimos como configurar seu processo de inicialização complexo e altamente modular, proteger nosso SOL e executar uma transferência privada.

Então, talvez você esteja se perguntando qual deles deveria usar. Bem, depende! Com a Elusiv, nosso código se concentrou em recarregar um saldo privado a partir de um pool compartilhado e enviar transações de forma privada. Para isso, o script interage com um contrato inteligente predefinido que administra o pool da Elusiv. Com o Light Protocol, estabelecemos um processo de inicialização mais flexível, embora complexo, para criar um fluxo de trabalho que gerencia vários usuários interagindo com programas da Solana que preservam a privacidade. Com o Light, também podemos criar nossos próprios Programas Privados da Solana (PSPs) personalizados, se necessário. Ambos têm vantagens e desvantagens em áreas como documentação e implementação específica, mas os dois protocolos buscam oferecer privacidade e transações seguras. Se sua aplicação exige um controle mais granular sobre os recursos de privacidade e você quer criptografar partes específicas do estado da aplicação, considere usar o Light Protocol. Se você está criando algo mais simples e quer adicionar uma camada de privacidade às transferências de tokens na devnet/mainnet, a Elusiv pode ser a melhor opção.

As técnicas, o código e os insights apresentados neste artigo oferecem um ponto de partida sólido para criar aplicações voltadas à privacidade na Solana. A importância da privacidade na tecnologia blockchain não pode ser subestimada. À medida que cresce a demanda por sistemas descentralizados, também aumenta a necessidade de recursos de privacidade que deem autonomia aos usuários, protejam seus direitos e concretizem os verdadeiros objetivos da tecnologia blockchain em seus primórdios. A privacidade importa e, agora, com a Elusiv e o Light Protocol, você pode tornar a Solana uma blockchain mais segura!

Se você leu até aqui, anon, agradeço!

Recursos adicionais / Leituras complementares

‍

Assine a Helius

Acompanhe as novidades mais recentes do desenvolvimento Solana e receba atualizações quando publicarmos