MỚI: Dùng thử Parsed Events Scan API
quyền riêng tư trên Solana với Elusiv và Light Protocol
Blog/Phát triển

Quyền riêng tư trên Solana với Elusiv và Light: Mọi điều bạn cần biết về quyền riêng tư trên SOL

Developer Experience Engineer0xIchigo trên X0xIchigo trên LinkedIn0xIchigo trên GitHub
Đọc trong 16 phút

Điều kiện tiên quyết

  • Đã cài đặt Node.js (để cài đặt các phần phụ thuộc bắt buộc, chạy tập lệnh và gửi $SOL riêng tư)
  • Hiểu biết cơ bản về TypeScript
  • Hiểu biết cơ bản về @solana/web3.js

Bài viết này nói về điều gì?

Quyền riêng tư là cốt lõi của công nghệ blockchain hiện đại. Trong các hệ thống sổ cái công khai, giao dịch, số dư tài khoản và thường cả mã nguồn của chương trình đều được công khai để bất kỳ ai cũng có thể xem. Mặc dù mức độ minh bạch chưa từng có này được xem là một đặc trưng tiêu biểu của công nghệ blockchain, quyền riêng tư vẫn có thể là yêu cầu thiết yếu đối với nhiều ứng dụng hoặc hoạt động sử dụng hằng ngày.

Cả Elusiv và Light Protocol đều cung cấp các giải pháp tiên tiến về quyền riêng tư trên Solana bằng cách tận dụng zk-SNARKS cho giao dịch riêng tư, chương trình riêng tư và các giải pháp tuân thủ phi tập trung. Trong bài viết này, chúng ta sẽ đi sâu vào tầm quan trọng của quyền riêng tư, tìm hiểu lý do các blockchain như Solana cần tính năng bảo vệ quyền riêng tư, đồng thời cung cấp hướng dẫn từng bước để gửi SOL riêng tư bằng TypeScript SDK của Elusiv và mô-đun zk.js của Light.

Quyền riêng tư rất quan trọng

Quyền riêng tư là một quyền cơ bản của con người, thiết yếu đối với quyền tự chủ và việc bảo toàn phẩm giá con người. Quyền riêng tư là điều cần thiết cho một xã hội tự do và cởi mở vì nó tạo nền tảng để thực thi các quyền khác, chẳng hạn như tự do ngôn luận và tự do lập hội. Điều này tạo ra một không gian tách biệt với đời sống chính trị, bảo đảm mọi người có thể suy nghĩ, lên tiếng, bày tỏ ý tưởng hoặc hành xử mà không bị bên thứ ba giám sát liên tục. Những tranh cãi công khai gần đây như các tiết lộ của Edward Snowden, thông tin do WikiLeaks công bố và vụ bê bối dữ liệu Facebook-Cambridge Analytica đã làm bùng lên những cuộc tranh luận gay gắt về tầm quan trọng và ranh giới của quyền riêng tư.

Mọi người đều nên quan tâm đến quyền riêng tư

Thoạt nhìn, việc ủng hộ các tính năng bảo vệ quyền riêng tư cho sổ cái công khai—được xây dựng trên các công nghệ phi tập trung vốn đề cao tính minh bạch như một trong những nguyên tắc nền tảng—có vẻ mâu thuẫn. Dù bạn là một cypherpunk kiên định hay người dùng bình thường, mọi người đều nên quan tâm đến quyền riêng tư. Bạn có thể nghĩ rằng mình không làm điều gì có hại hoặc bất hợp pháp trên mạng, vậy ai quan tâm đến việc nhìn thấy những gì mình làm trên blockchain. Hãy xem xét thí nghiệm tư duy sau:

Chúng ta có hai nhân vật: Alice, một người dùng bình thường coi trọng quyền riêng tư và muốn mua hàng mà không bị soi xét; và một tác nhân độc hại, được viết tắt là MA. Ví dụ, tác nhân độc hại có thể là một chính phủ áp bức, một chủ lao động thích kiểm soát hoặc một tội phạm mạng. Giả sử Alice sống tại một quốc gia nơi chính phủ liên tục giám sát từng công dân, hoặc Alice làm việc cho một công ty có chính sách nghiêm ngặt chống lại một số giao dịch mua hợp pháp nhất định (ví dụ: quyên góp chính trị, mua sách về một số chủ đề). Một ngày nọ, Alice mua hàng bằng một blockchain truyền thống như Solana vì tin rằng đây là phương thức thanh toán an toàn và riêng tư. Trình tự sự kiện diễn ra như sau:

  • Giao dịch của Alice được ghi lại trên sổ cái công khai. Giao dịch bao gồm khóa công khai của cô ấy, khóa công khai của người nhận và số tiền đã gửi
  • MA muốn giám sát hoặc kiểm soát hành vi của Alice nên bắt đầu truy vết các giao dịch bằng khóa công khai của cô ấy
  • MA phát hiện giao dịch mua của Alice và cho rằng giao dịch đó "không thể chấp nhận" theo quy tắc của mình
  • MA có hành động chống lại Alice; tùy hoàn cảnh, hành động này có thể bao gồm khởi kiện, bỏ tù, kỷ luật tại nơi làm việc, bêu riếu công khai hoặc tống tiền

Thí nghiệm tư duy này minh họa rõ thực tế khắc nghiệt về cách tình trạng thiếu quyền riêng tư trên blockchain có thể ảnh hưởng đến người dùng bình thường. Với sự gia tăng của các nhà điều tra on-chain và công nghệ thu thập dữ liệu độc hại, việc truy vết giao dịch của người dùng bình thường đang trở nên khá dễ dàng và có thể dẫn đến những hậu quả đáng kể trong đời thực. Trong một thế giới ngày càng số hóa, nếu không có quyền riêng tư, chúng ta có nguy cơ đánh mất chủ quyền cá nhân, mọi quyết định đều bị giám sát và mọi khía cạnh trong sự tồn tại của mình đều bị thương mại hóa. Blockchain sẽ chỉ đẩy nhanh quá trình này nếu chúng ta không triển khai các công nghệ bảo vệ quyền riêng tư. Đây là lúc Elusiv và Light Protocol phát huy vai trò.

Elusiv là gì?

Elusiv tự mô tả là "một giao thức quyền riêng tư dựa trên blockchain, sử dụng công nghệ không tiết lộ" và "cho phép người dùng cũng như ứng dụng tiếp cận mã hóa phổ quát". Elusiv hướng đến việc bảo đảm quyền riêng tư mà không phải đánh đổi tính bảo mật, an toàn và phi tập trung bằng cách áp dụng mật mã học không tiết lộ và tính toán đa bên cho người dùng và mạng. Mục tiêu mã hóa phổ quát này cho phép Elusiv cung cấp các công cụ giúp người dùng kiểm soát dữ liệu của mình. Giờ đây, người dùng có thể chọn nội dung nào được chia sẻ trên blockchain và nội dung nào không.

Elusiv hoạt động như thế nào?

Elusiv hoạt động bằng cách để người dùng gửi tiền vào một pool dùng chung do chương trình Elusiv quản lý, qua đó nạp thêm vào số dư riêng tư của họ. Từ pool này, người dùng có thể gửi và nhận các token như SOL, cũng như rút tiền. Cần lưu ý rằng với mô hình pool dùng chung này, mọi người vẫn có thể quan sát các khoản tiền gửi vào và thanh toán từ Elusiv. Tuy nhiên, khi số lượng người dùng và giao dịch tăng lên, việc liên kết các khoản tiền gửi và thanh toán với những tài khoản cụ thể theo thời gian sẽ ngày càng khó hơn—việc liên kết một người dùng duy nhất gửi vào 10 SOL rồi rút ra 10 SOL dễ hơn so với xác định một người giữa hàng triệu người thực hiện các khoản gửi và thanh toán tăng dần. Càng đông càng dễ ẩn danh.

Tiền trong pool không chứa bất kỳ thông tin nào về người gửi, nhưng bằng cách sử dụng bằng chứng không tiết lộ, các giá trị bí mật được liên kết với tiền của người dùng để có thể tạo bằng chứng về quyền sở hữu đối với số tiền chưa chi tiêu. Các bằng chứng không tiết lộ được dùng trong Elusiv gọi là zk-SNARKS (Lập luận tri thức ngắn gọn, không tương tác và không tiết lộ). Không cần đi sâu vào sự phức tạp của bằng chứng zk-SNARK, hãy hình dung đây là một phương pháp đã được chứng minh bằng toán học để cho bạn bè thấy rằng bạn biết một mật khẩu bí mật mà không bao giờ phải nói mật khẩu đó là gì. Trong trường hợp này, người bạn có thể nhanh chóng kiểm tra rằng bạn đang nói thật mà không cần hỏi thêm bất kỳ điều gì. Nhờ đó, Elusiv có thể theo dõi số tiền chưa chi tiêu của bạn mà không tiết lộ cho toàn bộ Solana biết bạn có bao nhiêu tiền trong pool. Nếu muốn tìm hiểu thêm về bằng chứng không tiết lộ, tôi khuyên bạn nên xem khóa học về bằng chứng không tiết lộ của Porter.

Light Protocol là gì?

Light Protocol tự mô tả là một "zkLayer thế hệ mới trên Solana", cho phép thực thi chương trình riêng tư trực tiếp trên Solana. Có ba khái niệm nền tảng giúp Light bảo vệ quyền riêng tư trên Solana:

  • Trạng thái riêng tư
  • Mô hình UTXO
  • Xác minh quá trình chuyển đổi trạng thái riêng tư on-chain

Light Protocol hoạt động như thế nào?

Light Protocol cho phép mã hóa trạng thái on-chain. Trong trường hợp này, người dùng "sở hữu" trạng thái và có quyền giải mã độc quyền. Không giống cách tiếp cận pool tiền của Elusiv, Light Protocol đưa mô hình UTXO (Đầu ra giao dịch chưa chi tiêu) vào Solana. Một UTXO được liên kết với một cặp khóa được che chắn và có thể lưu hai số dư: một bằng SOL (vì đây là token dùng để trả phí trong Light Protocol) và một bằng token SPL khác. Theo cách tiếp cận này, mỗi giao dịch tạo ra một UTXO mới được thêm vào danh sách tuyến tính, nhờ đó tránh cập nhật tại chỗ; trạng thái được che giấu thay vì làm rò rỉ thông tin về trạng thái nào đang được cập nhật và ai đang cập nhật.

Logic chương trình trên Solana hầu như luôn là một cơ chế ký quỹ với một số ràng buộc riêng. Light Protocol cải tiến thiết kế này bằng cách mã hóa logic chương trình thành các mạch không tiết lộ (zk-SNARKS) ở phía máy khách, nơi phép tính và bằng chứng được tạo ra. Bằng chứng này được chia sẻ on-chain, tại đó một chương trình xác minh chuyên dụng được xây dựng riêng cho mạch sẽ kiểm tra tính hợp lệ. Sau khi xác minh thành công, Program xác minh tùy chỉnh gọi một trình xác minh gốc khác để hoàn tất nhiều bước kiểm tra bảo mật và thêm các cam kết mới vào chương trình cây Merkle của Light.

Dù nằm ngoài phạm vi bài viết này, Light vẫn cho phép bạn tự tạo Private Solana Program (PSP) tùy chỉnh từ đầu. Bạn nên đọc phần còn lại của bài viết này và xem tài liệu của họ trước khi thử tự tạo. Tài liệu bao gồm hướng dẫn tạo PSP tùy chỉnh, có thể xem tại đây.

Tất cả đều rất thú vị, nhưng làm thế nào để áp dụng trực tiếp vào việc lập trình trên Solana?

Gửi SOL riêng tư bằng Elusiv

Toàn bộ mã

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

Phân tích mã Elusiv

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

Trước tiên, chúng ta nhập thư viện noble-ed25519 vì chưa thiết lập kết nối với ví. Trong môi trường production, bạn nên sử dụng Solana Wallet Adapter để ký giao dịch. Ở đây, chúng ta chỉ cần chức năng sign, tuy nhiên việc sử dụng import { sign } from "@noble/ed25519"; đôi khi có thể trả về lỗi cho biết etc.sha512Sync chưa được thiết lập. Để tránh lỗi này, chúng ta sẽ tự thiết lập—không cần quá bận tâm về phần này.

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

Sau đó, chúng ta nhập web3.js của Solana và TypeScript SDK của Elusiv cùng các mô-đun cần thiết.

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

Tại đây, chúng ta xác định CLUSTER là "devnet" và thiết lập RPC thành RPC devnet của Helius. Nếu muốn dùng tập lệnh này trên mainnet, bạn sẽ gán CLUSTER là "mainnet" và lấy khóa Helius API từ một trong các gói sau 😎

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

	// Rest of the code
}

Chúng ta đánh dấu hàm main là bất đồng bộ vì await một số hàm bên trong khối của hàm này. Sau đó, chúng ta thiết lập kết nối đến devnet bằng RPC và tạo một đối tượng Keypair từ hàm tiện ích generateKeypair. Chúng ta cũng tạo một recipientPublicKey bằng hàm generateKeypair nói trên để có thể nhận SOL được che chắn tại một địa chỉ ngẫu nhiên. Để tham khảo, hàm generateKeypair được định nghĩa như sau:

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

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

	return keyPair;
};

Hàm này tạo một cặp khóa Solana thử nghiệm mà chúng ta sẽ dùng cho tập lệnh. Nếu muốn sử dụng trên mainnet, bạn sẽ thay cặp khóa này bằng cặp khóa ví của mình. Hãy nhớ không bao giờ lưu khóa riêng tư dưới dạng văn bản thuần và phải nhập khóa đúng cách bằng đường dẫn tệp chính xác.

Mã
await airdropSol(keyPair);

Chúng ta cần một ít SOL trên devnet vì đang dùng các cặp khóa thử nghiệm mới tạo. Chúng ta dùng hàm tiện ích aidropSol để airdrop SOL vào cặp khóa mới. Hàm này được định nghĩa như sau:

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

Tại đây, chúng ta airdrop 1 SOL (1 * LAMPORTS_PER_SOL) cho chính mình trên devnet. Chúng ta truy xuất latestBlockHash để có thể truyền blockhash và chiều cao block hợp lệ cuối cùng vào connection.confirmTransaction(), qua đó xác nhận rằng 1 SOL đã được airdrop đến đúng địa chỉ.

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

Elusiv yêu cầu một seed đầu vào để bảo đảm một số thao tác có tính xác định, đó là lý do chúng ta nhập và sử dụng SEED_MESSAGE. SEED_MESSAGE là thông điệp bắt buộc phải ký vì được dùng để tạo seed Elusiv; nó cho phép ứng dụng giải mã các tài sản riêng tư của bạn cùng những giá trị bí mật liên quan để bạn có thể theo dõi tiền của mình. Chúng ta sử dụng Buffer.from() để tạo một buffer mới (cấu trúc dữ liệu nhị phân thô) từ SEED_MESSAGE với mã hóa utf-8. Chúng ta cần sử dụng slice vì kiểu cặp khóa của Solana có 32 byte đầu tiên là khóa riêng tư và 32 byte cuối cùng là khóa công khai. Do đó, chúng ta ký thông điệp seed bắt buộc bằng khóa riêng tư.

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

Tại đây, chúng ta xác định elusiv, một phiên bản Elusiv, bằng seed, keyPair.publicKey, connection và CLUSTER. Như vậy, chúng ta đang tạo một phiên bản Elusiv trên devnet bằng RPC devnet, khóa công khai và thông điệp dài vừa ký.

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

Sau đó, chúng ta tìm nạp số dư riêng tư—lượng Lamport đang có trong pool Elusiv—được trả về dưới dạng BigInt. Phần còn lại của hàm main được đóng gói trong một try-catch-finally block, được định nghĩa như sau:

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

Nếu có số dư riêng tư, balance sẽ lớn hơn 0 và khối đầu tiên trong if-else sẽ được thực thi. Lý do chúng ta ép kiểu 0 thành BigInt trong điều kiện if là vì kiểu trả về của getLatestPrivateBalance là BigInt. Sau đó, chúng ta tạo signature, một biến được gán bằng giá trị trả về của send(). Hàm send() được định nghĩa như sau:

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

Hàm này gửi giao dịch qua Elusiv và trả về một Promise có kiểu ConfirmedSignatureInfo (một chữ ký đã xác nhận cùng trạng thái). Các đối số gồm một phiên bản Elusiv, khóa công khai của người nhận, số tiền cần gửi và loại token (không nhất thiết phải là Lamport mà có thể là nhiều token khác như USDC, USDT, mSOL, BONK và SAMO). Sau đó, chúng ta thiết lập txt bằng elusiv.buildSendTx(). buildSendTx() là một phương thức trên phiên bản Elusiv có chức năng tạo giao dịch gửi token đến người nhận được chỉ định bằng số dư riêng tư. Sau đó, chúng ta gửi và trả về giao dịch bằng câu lệnh return elusiv.sendElusivTx(txt), đồng thời truyền vào giao dịch đã tạo là txt.

Nếu không có số dư riêng tư, khối else sẽ được thực thi:

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

Chúng ta ghi log để thông báo cho người dùng rằng họ không có số dư riêng tư và chúng ta sẽ nạp thêm. Sau đó, chúng ta tạo giao dịch nạp thêm bằng phương thức .buildTopUpTx() trên phiên bản Elusiv. Chúng ta truyền vào giá trị 1 SOL và loại token là Lamport vì đang gửi SOL. Tiếp theo, chúng ta ký giao dịch nạp thêm bằng .partialSign() trên cặp khóa của giao dịch vì đang nạp từ khóa công khai của mình. Cuối cùng, chúng ta gửi giao dịch đã tạo bằng elusiv.sendElusivTx(topUpTxData) và ghi log chữ ký giao dịch.

Phần còn lại của khối try-catch-finally như sau:

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

Nếu gặp bất kỳ lỗi nào khi gửi SOL qua Elusiv, chúng ta ghi chúng vào console bằng console.error(). Sau đó, chúng ta thoát chương trình bằng process.exit(0).

Để bắt đầu toàn bộ quy trình này, chúng ta gọi hàm main() ở cuối tệp.

Gửi SOL riêng tư bằng Light Protocol

Toàn bộ mã

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

Phân tích mã

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

Trước tiên, chúng ta nhập các mô-đun cần thiết từ Anchor, Solana/web3.js và Light để tương tác với Solana bằng Light Protocol. Sau đó, chúng ta định nghĩa một số hàm tiện ích trước khi đến hàm main—Light có quy trình khởi tạo phức tạp hơn so với cách triển khai Elusiv.

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

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

Tại đây, chúng ta tạo hàm initializeSolanaWallet để tạo một ví Solana mới bằng phương thức Keypair.generate().

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

Không giống Elusiv, Light cung cấp hàm airdropSol trong thư viện để airdrop Lamport đến một khóa công khai được chỉ định. Chúng ta tạo hàm tiện ích requestAirdrop để gửi SOL đến publicKey được truyền vào.

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

Để gửi SOL được che chắn qua Light, chúng ta cần thiết lập một relayer thử nghiệm bằng mô-đun TestRelayer. Relayer đóng vai trò quan trọng trong hệ sinh thái Light vì đây là các máy chủ web chuyển tiếp bằng chứng ZK xác nhận quá trình chuyển đổi trạng thái từ máy khách đến chương trình on-chain tương ứng. Chúng cũng thanh toán phí giao dịch để đổi lấy một khoản phí khi bằng chứng được xác minh thành công. Mức phí là 100 nghìn Lamport, được chúng ta thiết lập bằng new BN(100_000).

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

Sau đó, chúng ta khởi tạo Light Provider bằng hàm initializeLightProvider() có tên gọi rất phù hợp. Hàm này thiết lập một provider rồi liên kết nó với ví Solana và relayer thử nghiệm bằng cấu hình do Light cung cấp là confirmConfig—cấu hình này có kiểu ConfirmOptions và xử lý bước xác minh giao dịch, các cấp độ commitment, v.v.

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

Hàm initializeLightUser khá dễ hiểu—hàm này khởi tạo một người dùng cho Light Protocol bằng lightProvider được truyền vào.

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

Hàm performShieldOperation cho phép người dùng che chắn SOL để biến nó thành riêng tư. Ở đây, chúng ta che chắn hơn 1 SOL dù chỉ gửi 1 SOL được che chắn, nhằm tính cả phí.

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

Có thể nói đây là hàm tiện ích quan trọng nhất vì nó thực sự thực thi một giao dịch chuyển tiền riêng tư. Tại đây, chúng ta chuyển riêng tư 1 SOL từ user đến người nhận được chỉ định là testRecipientPublicKey.

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

Hàm main đóng vai trò là điểm vào cho tập lệnh. Chúng ta đánh dấu hàm này là bất đồng bộ vì có nhiều hàm async được gọi trong phạm vi của nó. Hàm chạy tuần tự tất cả các bước, bắt đầu bằng việc khởi tạo ví và kết thúc bằng việc thực thi một giao dịch chuyển tiền riêng tư. Trước tiên, chúng ta thiết lập ví Solana và kết nối. Sau đó, chúng ta airdrop SOL vào ví rồi khởi tạo relayer và Light Provider. Tiếp theo, chúng ta khởi tạo ví dưới dạng Light User và che chắn SOL. Sau đó, chúng ta tạo một người nhận thử nghiệm, airdrop một ít SOL cho họ, khởi tạo họ dưới dạng Light User và gửi riêng tư 1 SOL cho họ. Tương tự mã Elusiv, chúng ta đặt logic vào một khối try-catch-finally để bắt mọi lỗi và tự động thoát chương trình sau khi hoàn tất.

Bạn có thể nhận thấy rằng không giống Elusiv dùng devnet, ở đây chúng ta đang thiết lập kết nối đến "http://127.0.0.1:8899". Tại thời điểm viết bài, cần lưu ý rằng Light Protocol chưa được triển khai trên devnet/mainnet, vì vậy khóa Helius chưa hoạt động (👀). Với localhost, bạn cần chạy một test-validator có các tài khoản và chương trình cần thiết được tải sẵn để chạy mã. Bạn có thể cài đặt Light Protocol CLI cho mục đích này rồi chạy lệnh sau:

Mã
light test-validator

Thao tác này sẽ khởi động một validator trong nền. Hiện tại, bạn cũng cần thêm thủ công chai (một thư viện assertion giúp việc kiểm thử dễ dàng hơn) vì có một số unit test bên trong lớp UTXO. Tuy nhiên, vấn đề này sẽ được giải quyết trong bản phát hành tiếp theo. Tệp package.json của bạn cho tập lệnh này sẽ có dạng như sau:

Mã
{
  "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"
  }
}

Kết luận

Xin chúc mừng! Trong hướng dẫn này, chúng ta đã tìm hiểu sâu về sự phức tạp của quyền riêng tư, tầm quan trọng của nó và cách giao dịch SOL riêng tư bằng Elusiv và Light Protocol. Với Elusiv, chúng ta đã xem cách truy xuất số dư riêng tư, gửi SOL riêng tư và thậm chí tự động nạp thêm khi số dư riêng tư cạn kiệt hoặc không tồn tại. Với Light Protocol, chúng ta đã xem cách thiết lập quy trình khởi tạo phức tạp, có tính mô-đun cao, che chắn SOL và thực thi một giao dịch chuyển tiền riêng tư.

Vậy bạn có thể đang tự hỏi nên sử dụng giải pháp nào? Câu trả lời là còn tùy! Với Elusiv, mã của chúng ta tập trung vào việc nạp số dư riêng tư từ một pool dùng chung và gửi giao dịch riêng tư. Để thực hiện điều này, tập lệnh tương tác với một smart contract được định nghĩa sẵn và quản lý pool Elusiv. Với Light Protocol, chúng ta thiết lập một quy trình khởi tạo linh hoạt hơn nhưng cũng phức tạp hơn để tạo workflow quản lý nhiều người dùng tương tác với các chương trình Solana bảo vệ quyền riêng tư. Với Light, chúng ta cũng có thể tự tạo Private Solana Program (PSP) tùy chỉnh nếu cần. Cả hai đều có những điểm đánh đổi ở các phương diện như tài liệu và cách triển khai cụ thể, nhưng cả hai giao thức đều hướng đến quyền riêng tư và giao dịch an toàn. Nếu ứng dụng cần khả năng kiểm soát chi tiết hơn đối với các tính năng quyền riêng tư và bạn muốn mã hóa những phần cụ thể trong trạng thái ứng dụng, hãy cân nhắc sử dụng Light Protocol. Hoặc nếu đang xây dựng một giải pháp đơn giản hơn và muốn thêm một lớp riêng tư cho hoạt động chuyển token trên devnet/mainnet, Elusiv có thể là lựa chọn phù hợp hơn.

Các kỹ thuật, mã và thông tin chuyên sâu trong bài viết này cung cấp một điểm khởi đầu vững chắc để xây dựng các ứng dụng tập trung vào quyền riêng tư trên Solana. Không thể xem nhẹ tầm quan trọng của quyền riêng tư trong công nghệ blockchain. Khi nhu cầu đối với các hệ thống phi tập trung tăng lên, nhu cầu về những tính năng bảo vệ quyền riêng tư nhằm trao quyền cho người dùng, bảo vệ quyền của họ và hiện thực hóa mục tiêu đích thực của công nghệ blockchain thời kỳ đầu cũng tăng theo. Quyền riêng tư rất quan trọng, và giờ đây với cả Elusiv lẫn Light Protocol, bạn có thể giúp Solana trở thành một blockchain an toàn hơn!

Nếu anon đã đọc đến đây, xin cảm ơn!

Tài nguyên bổ sung / Đọc thêm

‍

Đăng ký nhận tin từ Helius

Luôn cập nhật những thông tin mới nhất về phát triển Solana và nhận thông báo khi chúng tôi đăng bài