diff --git a/public/content/translations/vi/developers/tutorials/set-up-web3js-to-use-ethereum-in-javascript/index.md b/public/content/translations/vi/developers/tutorials/set-up-web3js-to-use-ethereum-in-javascript/index.md new file mode 100644 index 00000000000..a54be4e1957 --- /dev/null +++ b/public/content/translations/vi/developers/tutorials/set-up-web3js-to-use-ethereum-in-javascript/index.md @@ -0,0 +1,92 @@ +--- +title: "Thiết lập web3.js để sử dụng chuỗi khối Ethereum trong JavaScript" +description: "Tìm hiểu cách thiết lập và cấu hình thư viện web3.js để tương tác với chuỗi khối Ethereum từ các ứng dụng JavaScript." +author: "jdourlens" +tags: [ "web3.js", "javascript" ] +skill: beginner +lang: vi +published: 2020-04-11 +source: EthereumDev +sourceUrl: https://ethereumdev.io/setup-web3js-to-use-the-ethereum-blockchain-in-javascript/ +address: "0x19dE91Af973F404EDF5B4c093983a7c6E3EC8ccE" +--- + +Trong hướng dẫn này, chúng ta sẽ xem cách bắt đầu với [web3.js](https://web3js.readthedocs.io/) để tương tác với chuỗi khối Ethereum. Web3.js có thể được sử dụng trong cả frontend và backend để đọc dữ liệu từ chuỗi khối hoặc thực hiện giao dịch và thậm chí triển khai các hợp đồng thông minh. + +Bước đầu tiên là thêm web3.js vào dự án của bạn. Để sử dụng trong một trang web, bạn có thể nhập thư viện trực tiếp bằng cách sử dụng một CDN như JSDeliver. + +```html + +``` + +Nếu bạn muốn cài đặt thư viện để sử dụng trong backend hoặc một dự án frontend có sử dụng bản dựng, bạn có thể cài đặt nó bằng npm: + +```bash +npm install web3 --save +``` + +Sau đó, để nhập Web3.js vào một kịch bản Node.js hoặc một dự án frontend Browserify, bạn có thể sử dụng dòng JavaScript sau: + +```js +const Web3 = require("web3") +``` + +Bây giờ chúng ta đã thêm thư viện vào dự án, chúng ta cần khởi tạo nó. Dự án của bạn cần có khả năng giao tiếp với chuỗi khối. Hầu hết các thư viện Ethereum giao tiếp với một [nút](/developers/docs/nodes-and-clients/) thông qua các lệnh gọi RPC. Để khởi tạo nhà cung cấp Web3 của chúng ta, chúng ta sẽ khởi tạo một thực thể Web3 bằng cách chuyển URL của nhà cung cấp làm hàm tạo. Nếu bạn có một nút hoặc [một thực thể ganache đang chạy trên máy tính của bạn](https://ethereumdev.io/testing-your-smart-contract-with-existing-protocols-ganache-fork/), nó sẽ trông như thế này: + +```js +const web3 = new Web3("http://localhost:8545") +``` + +Nếu bạn muốn truy cập trực tiếp vào một nút được lưu trữ, bạn có thể tìm các tùy chọn trên [các nút dưới dạng dịch vụ](/developers/docs/nodes-and-clients/nodes-as-a-service). + +```js +const web3 = new Web3("https://cloudflare-eth.com") +``` + +Để kiểm tra xem chúng ta đã cấu hình chính xác thực thể Web3 của mình chưa, chúng ta sẽ cố gắng truy xuất số khối mới nhất bằng cách sử dụng hàm `getBlockNumber`. Hàm này chấp nhận một lệnh gọi lại làm tham số và trả về số khối dưới dạng một số nguyên. + +```js +var Web3 = require("web3") +const web3 = new Web3("https://cloudflare-eth.com") + +web3.eth.getBlockNumber(function (error, result) { + console.log(result) +}) +``` + +Nếu bạn thực thi chương trình này, nó sẽ chỉ in ra số khối mới nhất: đỉnh của chuỗi khối. Bạn cũng có thể sử dụng các lệnh gọi hàm `await/async` để tránh lồng các lệnh gọi lại trong mã của bạn: + +```js +async function getBlockNumber() { + const latestBlockNumber = await web3.eth.getBlockNumber() + console.log(latestBlockNumber) + return latestBlockNumber +} + +getBlockNumber() +``` + +Bạn có thể xem tất cả các hàm có sẵn trên thực thể Web3 trong [tài liệu tham khảo chính thức của web3.js](https://docs.web3js.org/). + +Hầu hết các thư viện Web3 đều không đồng bộ vì trong nền, thư viện thực hiện các lệnh gọi JSON-RPC đến nút, nơi sẽ gửi lại kết quả. + + + +Nếu bạn đang làm việc trong trình duyệt, một số ví trực tiếp chèn một thực thể Web3 và bạn nên cố gắng sử dụng nó bất cứ khi nào có thể, đặc biệt nếu bạn có kế hoạch tương tác với địa chỉ Ethereum của người dùng để thực hiện giao dịch. + +Đây là đoạn mã để phát hiện xem có ví MetaMask hay không và cố gắng kích hoạt nó nếu có. Sau đó, nó sẽ cho phép bạn đọc số dư của người dùng và cho phép họ xác thực các giao dịch bạn muốn họ thực hiện trên chuỗi khối Ethereum: + +```js +if (window.ethereum != null) { + state.web3 = new Web3(window.ethereum) + try { + // Yêu cầu quyền truy cập tài khoản nếu cần + await window.ethereum.enable() + // Tài khoản hiện đã được hiển thị + } catch (error) { + // Người dùng từ chối quyền truy cập tài khoản... + } +} +``` + +Các lựa chọn thay thế cho web3.js như [Ethers.js](https://docs.ethers.io/) có tồn tại và cũng được sử dụng phổ biến. Trong hướng dẫn tiếp theo, chúng ta sẽ xem [cách dễ dàng lắng nghe các khối mới đến trên chuỗi khối và xem chúng chứa gì](https://ethereumdev.io/listening-to-new-transactions-happening-on-the-blockchain/). diff --git a/public/content/translations/vi/developers/tutorials/short-abi/index.md b/public/content/translations/vi/developers/tutorials/short-abi/index.md new file mode 100644 index 00000000000..5c906e08f97 --- /dev/null +++ b/public/content/translations/vi/developers/tutorials/short-abi/index.md @@ -0,0 +1,585 @@ +--- +title: "Các ABI ngắn để tối ưu hóa Calldata" +description: "Tối ưu hóa hợp đồng thông minh cho các gộp giao dịch lạc quan" +author: Ori Pomerantz +lang: vi +tags: [ "lớp 2" ] +skill: intermediate +published: 2022-04-01 +--- + +## Giới thiệu {#introduction} + +Trong bài viết này, bạn sẽ tìm hiểu về [các gộp giao dịch lạc quan](/developers/docs/scaling/optimistic-rollups), chi phí giao dịch trên chúng và cách cấu trúc chi phí khác biệt đó đòi hỏi chúng ta phải tối ưu hóa cho những thứ khác so với trên Mạng chính Ethereum. +Bạn cũng sẽ học cách thực hiện tối ưu hóa này. + +### Tiết lộ đầy đủ {#full-disclosure} + +Tôi là nhân viên toàn thời gian của [Optimism](https://www.optimism.io/), vì vậy các ví dụ trong bài viết này sẽ chạy trên Optimism. +Tuy nhiên, kỹ thuật được giải thích ở đây cũng sẽ hoạt động tốt cho các rollup khác. + +### Thuật ngữ {#terminology} + +Khi thảo luận về các rollup, thuật ngữ 'lớp 1' (L1) được sử dụng cho Mạng chính, mạng Ethereum sản phẩm. +Thuật ngữ 'lớp 2' (L2) được sử dụng cho rollup hoặc bất kỳ hệ thống nào khác dựa vào L1 để bảo mật nhưng thực hiện hầu hết quá trình xử lý của nó ngoài chuỗi. + +## Làm cách nào chúng ta có thể giảm thêm chi phí giao dịch L2? {#how-can-we-further-reduce-the-cost-of-L2-transactions} + +[Các gộp giao dịch lạc quan](/developers/docs/scaling/optimistic-rollups) phải lưu giữ bản ghi của mọi giao dịch trong lịch sử để bất kỳ ai cũng có thể xem qua chúng và xác minh rằng trạng thái hiện tại là chính xác. +Cách rẻ nhất để đưa dữ liệu vào Mạng chính Ethereum là ghi dữ liệu đó dưới dạng calldata. +Giải pháp này đã được cả [Optimism](https://help.optimism.io/hc/en-us/articles/4413163242779-What-is-a-rollup-) và [Arbitrum](https://developer.offchainlabs.com/docs/rollup_basics#intro-to-rollups) lựa chọn. + +### Chi phí giao dịch L2 {#cost-of-l2-transactions} + +Chi phí giao dịch L2 bao gồm hai thành phần: + +1. Xử lý L2, thường cực kỳ rẻ +2. Lưu trữ L1, gắn liền với chi phí gas của Mạng chính + +Khi tôi đang viết bài này, trên Optimism, chi phí gas L2 là 0,001 [Gwei](/developers/docs/gas/#pre-london). +Mặt khác, chi phí gas L1 là khoảng 40 gwei. +[Bạn có thể xem giá hiện tại tại đây](https://public-grafana.optimism.io/d/9hkhMxn7z/public-dashboard?orgId=1&refresh=5m). + +Một byte calldata có giá 4 gas (nếu là số không) hoặc 16 gas (nếu là bất kỳ giá trị nào khác). +Một trong những hoạt động tốn kém nhất trên máy ảo ethereum (EVM) là ghi vào bộ nhớ lưu trữ. +Chi phí tối đa để ghi một từ 32 byte vào bộ nhớ lưu trữ trên L2 là 22100 gas. Hiện tại, chi phí này là 22,1 gwei. +Vì vậy, nếu chúng ta có thể tiết kiệm một byte calldata bằng không, chúng ta sẽ có thể ghi khoảng 200 byte vào bộ nhớ lưu trữ mà vẫn có lợi. + +### Giao diện nhị phân ứng dụng (ABI) {#the-abi} + +Phần lớn các giao dịch truy cập vào một hợp đồng từ một tài khoản sở hữu ngoại biên. +Hầu hết các hợp đồng được viết bằng Solidity và diễn giải trường dữ liệu của chúng theo [giao diện nhị phân ứng dụng (ABI)](https://docs.soliditylang.org/en/latest/abi-spec.html#formal-specification-of-the-encoding). + +Tuy nhiên, ABI được thiết kế cho L1, nơi một byte calldata có giá xấp xỉ bốn phép toán số học, chứ không phải L2, nơi một byte calldata có giá hơn một nghìn phép toán số học. +Calldata được chia như sau: + +| Phần | Độ dài | Byte | Byte lãng phí | Gas lãng phí | Byte cần thiết | Gas cần thiết | +| ------------ | -----: | ----: | ------------: | -----------: | -------------: | ------------: | +| Bộ chọn hàm | 4 | 0-3 | 3 | 48 | 1 | 16 | +| Các số không | 12 | 4-15 | 12 | 48 | 0 | 0 | +| Địa chỉ đích | 20 | 16-35 | 0 | 0 | 20 | 320 | +| Số lượng | 32 | 36-67 | 17 | 64 | 15 | 240 | +| Tổng | 68 | | | 160 | | 576 | + +Giải thích: + +- **Bộ chọn hàm**: Hợp đồng có ít hơn 256 hàm, vì vậy chúng ta có thể phân biệt chúng bằng một byte duy nhất. + Các byte này thường khác không và do đó [có giá mười sáu gas](https://eips.ethereum.org/EIPS/eip-2028). +- **Các số không**: Các byte này luôn bằng không vì một địa chỉ hai mươi byte không yêu cầu một từ ba mươi hai byte để chứa nó. + Các byte chứa giá trị không có giá bốn gas ([xem sách vàng](https://ethereum.github.io/yellowpaper/paper.pdf), Phụ lục G, + tr. 27, giá trị cho `G``txdatazero`). +- **Số lượng**: Nếu chúng ta giả định rằng trong hợp đồng này, `decimals` là mười tám (giá trị bình thường) và số lượng token tối đa chúng ta chuyển sẽ là 1018, chúng ta sẽ có số lượng tối đa là 1036. + 25615 > 1036, vì vậy mười lăm byte là đủ. + +Lãng phí 160 gas trên L1 thường không đáng kể. Một giao dịch có giá ít nhất [21.000 gas](https://yakkomajuri.medium.com/blockchain-definition-of-the-week-ethereum-gas-2f976af774ed), vì vậy thêm 0,8% không thành vấn đề. +Tuy nhiên, trên L2, mọi thứ lại khác. Hầu như toàn bộ chi phí của giao dịch là ghi nó vào L1. +Ngoài calldata giao dịch, có 109 byte tiêu đề giao dịch (địa chỉ đích, chữ ký, v.v.). +Do đó, tổng chi phí là `109*16+576+160=2480`, và chúng ta đang lãng phí khoảng 6,5% trong số đó. + +## Giảm chi phí khi bạn không kiểm soát đích đến {#reducing-costs-when-you-dont-control-the-destination} + +Giả sử rằng bạn không có quyền kiểm soát hợp đồng đích, bạn vẫn có thể sử dụng một giải pháp tương tự như [giải pháp này](https://github.com/qbzzt/ethereum.org-20220330-shortABI). +Hãy xem qua các tệp có liên quan. + +### Token.sol {#token-sol} + +[Đây là hợp đồng đích](https://github.com/qbzzt/ethereum.org-20220330-shortABI/blob/master/contracts/Token.sol). +Đó là một hợp đồng ERC-20 tiêu chuẩn, với một tính năng bổ sung. +Hàm `faucet` này cho phép bất kỳ người dùng nào nhận được một số token để sử dụng. +Điều này sẽ làm cho một hợp đồng ERC-20 sản phẩm trở nên vô dụng, nhưng nó giúp cuộc sống dễ dàng hơn khi một ERC-20 chỉ tồn tại để tạo điều kiện thử nghiệm. + +```solidity + /** + * @dev Cung cấp cho người gọi 1000 token để sử dụng + */ + function faucet() external { + _mint(msg.sender, 1000); + } // function faucet +``` + +### CalldataInterpreter.sol {#calldatainterpreter-sol} + +[Đây là hợp đồng mà các giao dịch sẽ gọi với calldata ngắn hơn](https://github.com/qbzzt/ethereum.org-20220330-shortABI/blob/master/contracts/CalldataInterpreter.sol). +Hãy xem xét từng dòng một. + +```solidity +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + + +import { OrisUselessToken } from "./Token.sol"; +``` + +Chúng ta cần hàm token để biết cách gọi nó. + +```solidity +contract CalldataInterpreter { + + OrisUselessToken public immutable token; +``` + +Địa chỉ của token mà chúng tôi là một proxy. + +```solidity + + /** + * @dev Chỉ định địa chỉ token + * @param tokenAddr_ địa chỉ hợp đồng ERC-20 + */ + constructor( + address tokenAddr_ + ) { + token = OrisUselessToken(tokenAddr_); + } // constructor +``` + +Địa chỉ token là tham số duy nhất chúng ta cần chỉ định. + +```solidity + function calldataVal(uint startByte, uint length) + private pure returns (uint) { +``` + +Đọc một giá trị từ calldata. + +```solidity + uint _retVal; + + require(length < 0x21, + "calldataVal length limit is 32 bytes"); + + require(length + startByte <= msg.data.length, + "calldataVal trying to read beyond calldatasize"); +``` + +Chúng ta sẽ tải một từ 32 byte (256-bit) vào bộ nhớ và loại bỏ các byte không phải là một phần của trường chúng ta muốn. +Thuật toán này không hoạt động đối với các giá trị dài hơn 32 byte, và tất nhiên chúng ta không thể đọc vượt quá cuối calldata. +Trên L1, có thể cần bỏ qua các bài kiểm tra này để tiết kiệm gas, nhưng trên L2, gas cực kỳ rẻ, cho phép chúng ta thực hiện bất kỳ kiểm tra tính hợp lệ nào có thể nghĩ đến. + +```solidity + assembly { + _retVal := calldataload(startByte) + } +``` + +Chúng ta có thể sao chép dữ liệu từ lệnh gọi đến `fallback()` (xem bên dưới), nhưng việc sử dụng [Yul](https://docs.soliditylang.org/en/v0.8.12/yul.html), ngôn ngữ hợp ngữ của máy ảo ethereum (EVM), sẽ dễ dàng hơn. + +Ở đây chúng ta sử dụng [opcode CALLDATALOAD](https://www.evm.codes/#35) để đọc các byte từ `startByte` đến `startByte+31` vào ngăn xếp. +Nói chung, cú pháp của một opcode trong Yul là `(,...). + +```solidity + + _retVal = _retVal >> (256-length*8); +``` + +Chỉ các byte `length` quan trọng nhất là một phần của trường, vì vậy chúng ta [dịch phải](https://en.wikipedia.org/wiki/Logical_shift) để loại bỏ các giá trị khác. +Điều này có thêm lợi thế là di chuyển giá trị sang bên phải của trường, vì vậy nó là chính giá trị đó thay vì giá trị nhân với 256lần nào đó. + +```solidity + + return _retVal; + } + + + fallback() external { +``` + +Khi một lệnh gọi đến hợp đồng Solidity không khớp với bất kỳ chữ ký hàm nào, nó sẽ gọi [hàm `fallback()`](https://docs.soliditylang.org/en/v0.8.12/contracts.html#fallback-function) (giả sử có một hàm). +Trong trường hợp của `CalldataInterpreter`, _bất kỳ_ lệnh gọi nào cũng sẽ đến đây vì không có hàm `external` hoặc `public` nào khác. + +```solidity + uint _func; + + _func = calldataVal(0, 1); +``` + +Đọc byte đầu tiên của calldata, cho chúng ta biết hàm. +Có hai lý do tại sao một hàm sẽ không có sẵn ở đây: + +1. Các hàm `pure` hoặc `view` không thay đổi trạng thái và không tốn gas (khi được gọi ngoài chuỗi). + Không có ý nghĩa gì khi cố gắng giảm chi phí gas của chúng. +2. Các hàm dựa trên [`msg.sender`](https://docs.soliditylang.org/en/v0.8.12/units-and-global-variables.html#block-and-transaction-properties). + Giá trị của `msg.sender` sẽ là địa chỉ của `CalldataInterpreter`, không phải là người gọi. + +Thật không may, [nhìn vào các thông số kỹ thuật ERC-20](https://eips.ethereum.org/EIPS/eip-20), điều này chỉ còn lại một hàm, `transfer`. +Điều này chỉ còn lại hai hàm: `transfer` (bởi vì chúng ta có thể gọi `transferFrom`) và `faucet` (bởi vì chúng ta có thể chuyển token trở lại cho bất kỳ ai đã gọi chúng ta). + +```solidity + + // Gọi các phương thức thay đổi trạng thái của token bằng + // thông tin từ calldata + + // faucet + if (_func == 1) { +``` + +Một lệnh gọi đến `faucet()`, không có tham số. + +```solidity + token.faucet(); + token.transfer(msg.sender, + token.balanceOf(address(this))); + } +``` + +Sau khi chúng ta gọi `token.faucet()`, chúng ta nhận được token. Tuy nhiên, với tư cách là hợp đồng proxy, chúng tôi không **cần** token. +EOA (tài khoản sở hữu bên ngoài) hoặc hợp đồng đã gọi chúng tôi thì cần. +Vì vậy, chúng tôi chuyển tất cả token của mình cho bất kỳ ai đã gọi chúng tôi. + +```solidity + // chuyển khoản (giả sử chúng ta có khoản cho phép cho nó) + if (_func == 2) { +``` + +Chuyển token yêu cầu hai tham số: địa chỉ đích và số lượng. + +```solidity + token.transferFrom( + msg.sender, +``` + +Chúng tôi chỉ cho phép người gọi chuyển token mà họ sở hữu + +```solidity + address(uint160(calldataVal(1, 20))), +``` + +Địa chỉ đích bắt đầu ở byte #1 (byte #0 là hàm). +Là một địa chỉ, nó dài 20 byte. + +```solidity + calldataVal(21, 2) +``` + +Đối với hợp đồng cụ thể này, chúng tôi giả định rằng số lượng token tối đa mà bất kỳ ai muốn chuyển sẽ vừa trong hai byte (ít hơn 65536). + +```solidity + ); + } +``` + +Nhìn chung, một lần chuyển khoản mất 35 byte calldata: + +| Phần | Độ dài | Byte | +| ------------ | -----: | ----: | +| Bộ chọn hàm | 1 | 0 | +| Địa chỉ đích | 32 | 1-32 | +| Số lượng | 2 | 33-34 | + +```solidity + } // fallback + +} // contract CalldataInterpreter +``` + +### test.js {#test-js} + +[Thử nghiệm đơn vị JavaScript này](https://github.com/qbzzt/ethereum.org-20220330-shortABI/blob/master/test/test.js) cho chúng ta thấy cách sử dụng cơ chế này (và cách xác minh nó hoạt động chính xác). +Tôi sẽ giả định bạn hiểu [chai](https://www.chaijs.com/) và [ethers](https://docs.ethers.io/v5/) và chỉ giải thích các phần áp dụng cụ thể cho hợp đồng. + +```js +const { expect } = require("chai"); + +describe("CalldataInterpreter", function () { + it("Should let us use tokens", async function () { + const Token = await ethers.getContractFactory("OrisUselessToken") + const token = await Token.deploy() + await token.deployed() + console.log("Token addr:", token.address) + + const Cdi = await ethers.getContractFactory("CalldataInterpreter") + const cdi = await Cdi.deploy(token.address) + await cdi.deployed() + console.log("CalldataInterpreter addr:", cdi.address) + + const signer = await ethers.getSigner() +``` + +Chúng tôi bắt đầu bằng cách triển khai cả hai hợp đồng. + +```javascript + // Nhận token để sử dụng + const faucetTx = { +``` + +Chúng ta không thể sử dụng các hàm cấp cao mà chúng ta thường sử dụng (chẳng hạn như `token.faucet()`) để tạo giao dịch, vì chúng ta không tuân theo ABI. +Thay vào đó, chúng ta phải tự xây dựng giao dịch và sau đó gửi nó. + +```javascript + to: cdi.address, + data: "0x01" +``` + +Có hai tham số chúng ta cần cung cấp cho giao dịch: + +1. `to`, địa chỉ đích. + Đây là hợp đồng thông dịch calldata. +2. `data`, calldata cần gửi. + Trong trường hợp gọi faucet, dữ liệu là một byte duy nhất, `0x01`. + +```javascript + + } + await (await signer.sendTransaction(faucetTx)).wait() +``` + +Chúng tôi gọi [phương thức `sendTransaction` của người ký](https://docs.ethers.io/v5/api/signer/#Signer-sendTransaction) vì chúng tôi đã chỉ định đích (`faucetTx.to`) và chúng tôi cần giao dịch được ký. + +```javascript +// Kiểm tra faucet cung cấp token chính xác +expect(await token.balanceOf(signer.address)).to.equal(1000) +``` + +Ở đây chúng tôi xác minh số dư. +Không cần tiết kiệm gas cho các hàm `view`, vì vậy chúng tôi chỉ chạy chúng bình thường. + +```javascript +// Cấp cho CDI một khoản cho phép (không thể ủy quyền phê duyệt) +const approveTX = await token.approve(cdi.address, 10000) +await approveTX.wait() +expect(await token.allowance(signer.address, cdi.address)).to.equal(10000) +``` + +Cấp cho trình thông dịch calldata một khoản trợ cấp để có thể thực hiện chuyển khoản. + +```javascript +// Chuyển token +const destAddr = "0xf5a6ead936fb47f342bb63e676479bddf26ebe1d" +const transferTx = { + to: cdi.address, + data: "0x02" + destAddr.slice(2, 42) + "0100", +} +``` + +Tạo một giao dịch chuyển khoản. Byte đầu tiên là "0x02", theo sau là địa chỉ đích và cuối cùng là số tiền (0x0100, tức là 256 trong hệ thập phân). + +```javascript + await (await signer.sendTransaction(transferTx)).wait() + + // Kiểm tra xem chúng ta có ít hơn 256 token + expect (await token.balanceOf(signer.address)).to.equal(1000-256) + + // Và đích đến của chúng ta đã nhận được chúng + expect (await token.balanceOf(destAddr)).to.equal(256) + }) // it +}) // describe +``` + +## Giảm chi phí khi bạn kiểm soát hợp đồng đích {#reducing-the-cost-when-you-do-control-the-destination-contract} + +Nếu bạn có quyền kiểm soát hợp đồng đích, bạn có thể tạo các hàm bỏ qua kiểm tra `msg.sender` vì chúng tin tưởng vào trình thông dịch calldata. +[Bạn có thể xem một ví dụ về cách hoạt động của nó tại đây, trong nhánh `control-contract`](https://github.com/qbzzt/ethereum.org-20220330-shortABI/tree/control-contract). + +Nếu hợp đồng chỉ phản hồi các giao dịch bên ngoài, chúng ta có thể chỉ cần một hợp đồng duy nhất. +Tuy nhiên, điều đó sẽ phá vỡ [tính kết hợp](/developers/docs/smart-contracts/composability/). +Tốt hơn nhiều là có một hợp đồng phản hồi các lệnh gọi ERC-20 bình thường và một hợp đồng khác phản hồi các giao dịch với dữ liệu cuộc gọi ngắn. + +### Token.sol {#token-sol-2} + +Trong ví dụ này, chúng ta có thể sửa đổi `Token.sol`. +Điều này cho phép chúng ta có một số hàm mà chỉ proxy mới có thể gọi. +Dưới đây là các phần mới: + +```solidity + // Địa chỉ duy nhất được phép chỉ định địa chỉ CalldataInterpreter + address owner; + + // Địa chỉ CalldataInterpreter + address proxy = address(0); +``` + +Hợp đồng ERC-20 cần biết danh tính của proxy được ủy quyền. +Tuy nhiên, chúng ta không thể đặt biến này trong hàm khởi tạo, vì chúng ta chưa biết giá trị của nó. +Hợp đồng này được khởi tạo trước vì proxy mong đợi địa chỉ của token trong hàm tạo của nó. + +```solidity + /** + * @dev Gọi hàm khởi tạo ERC20. + */ + constructor( + ) ERC20("Oris useless token-2", "OUT-2") { + owner = msg.sender; + } +``` + +Địa chỉ của người tạo (được gọi là `owner`) được lưu trữ ở đây vì đó là địa chỉ duy nhất được phép đặt proxy. + +```solidity + /** + * @dev đặt địa chỉ cho proxy (CalldataInterpreter). + * Chỉ có thể được gọi một lần bởi chủ sở hữu + */ + function setProxy(address _proxy) external { + require(msg.sender == owner, "Can only be called by owner"); + require(proxy == address(0), "Proxy is already set"); + + proxy = _proxy; + } // function setProxy +``` + +Proxy có quyền truy cập đặc quyền, vì nó có thể bỏ qua các kiểm tra bảo mật. +Để đảm bảo rằng chúng ta có thể tin tưởng vào proxy, chúng tôi chỉ cho phép `owner` gọi hàm này và chỉ một lần. +Khi `proxy` có giá trị thực (khác không), giá trị đó không thể thay đổi, vì vậy ngay cả khi chủ sở hữu quyết định trở thành kẻ xấu, hoặc cụm từ ghi nhớ của nó bị tiết lộ, chúng ta vẫn an toàn. + +```solidity + /** + * @dev Một số hàm chỉ có thể được gọi bởi proxy. + */ + modifier onlyProxy { +``` + +Đây là một [`hàm modifier`](https://www.tutorialspoint.com/solidity/solidity_function_modifiers.htm), nó sửa đổi cách hoạt động của các hàm khác. + +```solidity + require(msg.sender == proxy); +``` + +Đầu tiên, xác minh chúng tôi được gọi bởi proxy chứ không phải ai khác. +Nếu không, hãy `revert`. + +```solidity + _; + } +``` + +Nếu vậy, hãy chạy hàm mà chúng tôi sửa đổi. + +```solidity + /* Các hàm cho phép proxy thực sự làm proxy cho các tài khoản */ + + function transferProxy(address from, address to, uint256 amount) + public virtual onlyProxy() returns (bool) + { + _transfer(from, to, amount); + return true; + } + + function approveProxy(address from, address spender, uint256 amount) + public virtual onlyProxy() returns (bool) + { + _approve(from, spender, amount); + return true; + } + + function transferFromProxy( + address spender, + address from, + address to, + uint256 amount + ) public virtual onlyProxy() returns (bool) + { + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } +``` + +Đây là ba hoạt động thường yêu cầu thông điệp phải đến trực tiếp từ thực thể chuyển token hoặc phê duyệt một khoản trợ cấp. +Ở đây chúng tôi có một phiên bản proxy của các hoạt động này: + +1. Được sửa đổi bởi `onlyProxy()` để không ai khác được phép kiểm soát chúng. +2. Nhận địa chỉ thường là `msg.sender` làm tham số bổ sung. + +### CalldataInterpreter.sol {#calldatainterpreter-sol-2} + +Trình thông dịch calldata gần như giống hệt với trình thông dịch ở trên, ngoại trừ việc các hàm được ủy quyền nhận tham số `msg.sender` và không cần có khoản cho phép `transfer`. + +```solidity + // chuyển khoản (không cần khoản cho phép) + if (_func == 2) { + token.transferProxy( + msg.sender, + address(uint160(calldataVal(1, 20))), + calldataVal(21, 2) + ); + } + + // phê duyệt + if (_func == 3) { + token.approveProxy( + msg.sender, + address(uint160(calldataVal(1, 20))), + calldataVal(21, 2) + ); + } + + // transferFrom + if (_func == 4) { + token.transferFromProxy( + msg.sender, + address(uint160(calldataVal( 1, 20))), + address(uint160(calldataVal(21, 20))), + calldataVal(41, 2) + ); + } +``` + +### Test.js {#test-js-2} + +Có một vài thay đổi giữa mã kiểm tra trước đó và mã này. + +```js +const Cdi = await ethers.getContractFactory("CalldataInterpreter") +const cdi = await Cdi.deploy(token.address) +await cdi.deployed() +await token.setProxy(cdi.address) +``` + +Chúng ta cần cho hợp đồng ERC-20 biết proxy nào cần tin tưởng + +```js +console.log("CalldataInterpreter addr:", cdi.address) + +// Cần hai người ký để xác minh các khoản cho phép +const signers = await ethers.getSigners() +const signer = signers[0] +const poorSigner = signers[1] +``` + +Để kiểm tra `approve()` và `transferFrom()`, chúng ta cần một người ký thứ hai. +Chúng tôi gọi nó là `poorSigner` vì nó không nhận được bất kỳ token nào của chúng tôi (tất nhiên nó cần phải có ETH). + +```js +// Chuyển token +const destAddr = "0xf5a6ead936fb47f342bb63e676479bddf26ebe1d" +const transferTx = { + to: cdi.address, + data: "0x02" + destAddr.slice(2, 42) + "0100", +} +await (await signer.sendTransaction(transferTx)).wait() +``` + +Bởi vì hợp đồng ERC-20 tin tưởng vào proxy (`cdi`), chúng ta không cần một khoản trợ cấp để chuyển tiếp các giao dịch chuyển khoản. + +```js +// phê duyệt và transferFrom +const approveTx = { + to: cdi.address, + data: "0x03" + poorSigner.address.slice(2, 42) + "00FF", +} +await (await signer.sendTransaction(approveTx)).wait() + +const destAddr2 = "0xE1165C689C0c3e9642cA7606F5287e708d846206" + +const transferFromTx = { + to: cdi.address, + data: "0x04" + signer.address.slice(2, 42) + destAddr2.slice(2, 42) + "00FF", +} +await (await poorSigner.sendTransaction(transferFromTx)).wait() + +// Check the approve / transferFrom combo was done correctly +expect(await token.balanceOf(destAddr2)).to.equal(255) +``` + +Kiểm tra hai chức năng mới. +Lưu ý rằng `transferFromTx` yêu cầu hai tham số địa chỉ: người cấp khoản cho phép và người nhận. + +## Kết luận {#conclusion} + +Cả [Optimism](https://medium.com/ethereum-optimism/the-road-to-sub-dollar-transactions-part-2-compression-edition-6bb2890e3e92) và [Arbitrum](https://developer.offchainlabs.com/docs/special_features) đều đang tìm cách giảm kích thước của calldata được ghi vào L1 và do đó giảm chi phí giao dịch. +Tuy nhiên, với tư cách là các nhà cung cấp cơ sở hạ tầng đang tìm kiếm các giải pháp chung, khả năng của chúng tôi bị hạn chế. +Với tư cách là nhà phát triển ứng dụng phi tập trung, bạn có kiến thức cụ thể về ứng dụng, cho phép bạn tối ưu hóa calldata của mình tốt hơn nhiều so với chúng tôi trong một giải pháp chung. +Hy vọng rằng, bài viết này sẽ giúp bạn tìm ra giải pháp lý tưởng cho nhu cầu của mình. + +[Xem thêm công việc của tôi tại đây](https://cryptodocguy.pro/). + diff --git a/public/content/translations/vi/developers/tutorials/smart-contract-security-guidelines/index.md b/public/content/translations/vi/developers/tutorials/smart-contract-security-guidelines/index.md new file mode 100644 index 00000000000..db6c8a4c466 --- /dev/null +++ b/public/content/translations/vi/developers/tutorials/smart-contract-security-guidelines/index.md @@ -0,0 +1,91 @@ +--- +title: "Các nguyên tắc bảo mật hợp đồng thông minh" +description: "Danh sách kiểm tra các nguyên tắc bảo mật cần xem xét khi xây dựng ứng dụng phi tập trung của bạn" +author: "Trailofbits" +tags: [ "solidity", "hợp đồng thông minh", "tính bảo mật" ] +skill: intermediate +lang: vi +published: 2020-09-06 +source: Building secure contracts +sourceUrl: https://github.com/crytic/building-secure-contracts/blob/master/development-guidelines/guidelines.md +--- + +Làm theo các khuyến nghị cấp cao này để xây dựng các hợp đồng thông minh bảo mật hơn. + +## Nguyên tắc thiết kế {#design-guidelines} + +Thiết kế của hợp đồng nên được thảo luận trước, trước khi viết bất kỳ dòng mã nào. + +### Tài liệu và thông số kỹ thuật {#documentation-and-specifications} + +Tài liệu có thể được viết ở các cấp độ khác nhau và nên được cập nhật trong khi triển khai các hợp đồng: + +- **Mô tả hệ thống bằng tiếng Anh đơn giản**, mô tả những gì hợp đồng thực hiện và bất kỳ giả định nào về cơ sở mã. +- **Sơ đồ và biểu đồ kiến trúc**, bao gồm các tương tác hợp đồng và máy trạng thái của hệ thống. [Các trình in của Slither](https://github.com/crytic/slither/wiki/Printer-documentation) có thể giúp tạo ra các sơ đồ này. +- **Tài liệu mã kỹ lưỡng**, [định dạng Natspec](https://docs.soliditylang.org/en/develop/natspec-format.html) có thể được sử dụng cho Solidity. + +### Tính toán trên chuỗi và ngoài chuỗi {#onchain-vs-offchain-computation} + +- **Giữ càng nhiều mã ngoài chuỗi càng tốt.** Giữ cho lớp trên chuỗi nhỏ. Xử lý trước dữ liệu bằng mã ngoài chuỗi sao cho việc xác minh trên chuỗi trở nên đơn giản. Bạn có cần một danh sách có thứ tự không? Sắp xếp danh sách ngoài chuỗi, sau đó chỉ kiểm tra thứ tự của nó trên chuỗi. + +### Khả năng nâng cấp {#upgradeability} + +Chúng tôi đã thảo luận về các giải pháp khả năng nâng cấp khác nhau trong [bài đăng trên blog của chúng tôi](https://blog.trailofbits.com/2018/09/05/contract-upgrade-anti-patterns/). Hãy đưa ra lựa chọn có chủ ý về việc có hỗ trợ khả năng nâng cấp hay không trước khi viết bất kỳ mã nào. Quyết định này sẽ ảnh hưởng đến cách bạn cấu trúc mã của mình. Nói chung, chúng tôi khuyên bạn nên: + +- **Ưu tiên [di chuyển hợp đồng](https://blog.trailofbits.com/2018/10/29/how-contract-migration-works/) hơn khả năng nâng cấp.** Các hệ thống di chuyển có nhiều ưu điểm tương tự như các hệ thống có thể nâng cấp, mà không có nhược điểm của chúng. +- **Sử dụng mẫu tách dữ liệu thay vì mẫu delegatecallproxy.** Nếu dự án của bạn có sự tách biệt trừu tượng rõ ràng, khả năng nâng cấp bằng cách tách dữ liệu sẽ chỉ cần một vài điều chỉnh. Delegatecallproxy đòi hỏi chuyên môn về Máy chủ ảo Ethereum và rất dễ xảy ra lỗi. +- **Ghi lại quy trình di chuyển/nâng cấp trước khi triển khai.** Nếu bạn phải phản ứng trong tình trạng căng thẳng mà không có bất kỳ hướng dẫn nào, bạn sẽ mắc sai lầm. Viết trước quy trình cần tuân theo. Nó nên bao gồm: + - Các lệnh gọi khởi tạo các hợp đồng mới + - Nơi lưu trữ các khóa và cách truy cập chúng + - Cách kiểm tra việc triển khai! Phát triển và kiểm tra một tập lệnh sau triển khai. + +## Nguyên tắc triển khai {#implementation-guidelines} + +**Phấn đấu cho sự đơn giản.** Luôn sử dụng giải pháp đơn giản nhất phù hợp với mục đích của bạn. Bất kỳ thành viên nào trong nhóm của bạn cũng có thể hiểu được giải pháp của bạn. + +### Thành phần hàm {#function-composition} + +Kiến trúc của cơ sở mã nên giúp mã của bạn dễ dàng xem xét. Tránh các lựa chọn kiến trúc làm giảm khả năng suy luận về tính đúng đắn của nó. + +- **Phân chia logic của hệ thống**, thông qua nhiều hợp đồng hoặc bằng cách nhóm các hàm tương tự lại với nhau (ví dụ: xác thực, số học, ...). +- **Viết các hàm nhỏ, có mục đích rõ ràng.** Điều này sẽ tạo điều kiện thuận lợi cho việc xem xét dễ dàng hơn và cho phép thử nghiệm các thành phần riêng lẻ. + +### Kế thừa {#inheritance} + +- **Giữ cho việc kế thừa có thể quản lý được.** Kế thừa nên được sử dụng để phân chia logic, tuy nhiên, dự án của bạn nên hướng tới việc giảm thiểu độ sâu và độ rộng của cây kế thừa. +- **Sử dụng [trình in kế thừa] của Slither(https://github.com/crytic/slither/wiki/Printer-documentation#inheritance-graph) để kiểm tra hệ thống phân cấp của hợp đồng.** Trình in kế thừa sẽ giúp bạn xem lại kích thước của hệ thống phân cấp. + +### Sự kiện {#events} + +- **Ghi nhật ký tất cả các hoạt động quan trọng.** Các sự kiện sẽ giúp gỡ lỗi hợp đồng trong quá trình phát triển, và giám sát nó sau khi triển khai. + +### Tránh các cạm bẫy đã biết {#avoid-known-pitfalls} + +- **Hãy nhận biết các vấn đề bảo mật phổ biến nhất.** Có nhiều tài nguyên trực tuyến để tìm hiểu về các vấn đề phổ biến, chẳng hạn như [Ethernaut CTF](https://ethernaut.openzeppelin.com/), [Capture the Ether](https://capturetheether.com/), hoặc [Not so smart contracts](https://github.com/crytic/not-so-smart-contracts/). +- **Hãy chú ý đến các phần cảnh báo trong [tài liệu tham khảo của Solidity](https://docs.soliditylang.org/en/latest/).** Các phần cảnh báo sẽ thông báo cho bạn về hành vi không rõ ràng của ngôn ngữ. + +### Các phần phụ thuộc {#dependencies} + +- **Sử dụng các thư viện đã được kiểm tra kỹ lưỡng.** Nhập mã từ các thư viện đã được kiểm tra kỹ lưỡng sẽ làm giảm khả năng bạn viết mã có lỗi. Nếu bạn muốn viết một hợp đồng ERC20, hãy sử dụng [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC20). +- **Sử dụng trình quản lý phụ thuộc; tránh sao chép-dán mã.** Nếu bạn dựa vào một nguồn bên ngoài, thì bạn phải giữ cho nó được cập nhật với nguồn gốc. + +### Kiểm tra và xác minh {#testing-and-verification} + +- **Viết các bài kiểm tra đơn vị kỹ lưỡng.** Một bộ thử nghiệm sâu rộng là rất quan trọng để xây dựng phần mềm chất lượng cao. +- **Viết các kiểm tra và thuộc tính tùy chỉnh cho [Slither](https://github.com/crytic/slither), [Echidna](https://github.com/crytic/echidna) và [Manticore](https://github.com/trailofbits/manticore).** Các công cụ tự động sẽ giúp đảm bảo hợp đồng của bạn được bảo mật. Xem lại phần còn lại của hướng dẫn này để tìm hiểu cách viết các kiểm tra và thuộc tính hiệu quả. +- **Sử dụng [crytic.io](https://crytic.io/).** Crytic tích hợp với GitHub, cung cấp quyền truy cập vào các trình phát hiện Slither riêng tư, và chạy các kiểm tra thuộc tính tùy chỉnh từ Echidna. + +### Solidity {#solidity} + +- **Ưu tiên Solidity 0.5 hơn 0.4 và 0.6.** Theo chúng tôi, Solidity 0.5 bảo mật hơn và có các phương pháp thực hành tích hợp sẵn tốt hơn so với 0.4. Solidity 0.6 đã tỏ ra quá không ổn định để sản xuất và cần thời gian để hoàn thiện. +- **Sử dụng một bản phát hành ổn định để biên dịch; sử dụng bản phát hành mới nhất để kiểm tra cảnh báo.** Kiểm tra xem mã của bạn không có vấn đề nào được báo cáo với phiên bản trình biên dịch mới nhất. Tuy nhiên, Solidity có chu kỳ phát hành nhanh và có tiền sử về lỗi trình biên dịch, vì vậy chúng tôi không khuyến nghị phiên bản mới nhất để triển khai (xem [khuyến nghị về phiên bản solc của Slither](https://github.com/crytic/slither/wiki/Detector-Documentation#recommendation-33)). +- **Không sử dụng hợp ngữ nội tuyến.** Hợp ngữ đòi hỏi chuyên môn về Máy chủ ảo Ethereum. Đừng viết mã EVM nếu bạn chưa _nắm vững_ sách vàng. + +## Nguyên tắc triển khai {#deployment-guidelines} + +Sau khi hợp đồng đã được phát triển và triển khai: + +- **Giám sát hợp đồng của bạn.** Theo dõi nhật ký, và sẵn sàng phản ứng trong trường hợp hợp đồng hoặc ví bị xâm phạm. +- **Thêm thông tin liên hệ của bạn vào [blockchain-security-contacts](https://github.com/crytic/blockchain-security-contacts).** Danh sách này giúp các bên thứ ba liên hệ với bạn nếu một lỗ hổng bảo mật được phát hiện. +- **Bảo mật ví của người dùng có đặc quyền.** Làm theo các [phương pháp hay nhất của chúng tôi](https://blog.trailofbits.com/2018/11/27/10-rules-for-the-secure-use-of-cryptocurrency-hardware-wallets/) nếu bạn lưu trữ khóa trong ví phần cứng. +- **Có một kế hoạch ứng phó sự cố.** Hãy cân nhắc rằng các hợp đồng thông minh của bạn có thể bị xâm phạm. Ngay cả khi hợp đồng của bạn không có lỗi, kẻ tấn công có thể kiểm soát các khóa của chủ sở hữu hợp đồng. diff --git a/public/content/translations/vi/developers/tutorials/stealth-addr/index.md b/public/content/translations/vi/developers/tutorials/stealth-addr/index.md new file mode 100644 index 00000000000..ed51a28587b --- /dev/null +++ b/public/content/translations/vi/developers/tutorials/stealth-addr/index.md @@ -0,0 +1,443 @@ +--- +title: "Sử dụng Địa chỉ ẩn" +description: "Địa chỉ ẩn cho phép người dùng chuyển tài sản một cách ẩn danh. Sau khi đọc bài viết này, bạn sẽ có thể: Giải thích địa chỉ ẩn là gì và cách chúng hoạt động, hiểu cách sử dụng địa chỉ ẩn để bảo toàn tính ẩn danh và viết một ứng dụng dựa trên web sử dụng địa chỉ ẩn." +author: Ori Pomerantz +tags: + [ + "Địa chỉ ẩn", + "quyền riêng tư", + "mật mã học", + "rust", + "wasm" + ] +skill: intermediate +published: 2025-11-30 +lang: vi +sidebarDepth: 3 +--- + +Bạn là Bill. Vì những lý do chúng tôi sẽ không đi sâu vào, bạn muốn quyên góp cho chiến dịch "Alice cho Nữ hoàng Thế giới" và để Alice biết bạn đã quyên góp để cô ấy sẽ thưởng cho bạn nếu cô ấy thắng. Thật không may, chiến thắng của cô ấy không được đảm bảo. Có một chiến dịch cạnh tranh, "Carol cho Nữ hoàng Hệ mặt trời". Nếu Carol thắng và cô ấy phát hiện ra bạn đã quyên góp cho Alice, bạn sẽ gặp rắc rối. Vì vậy, bạn không thể chỉ chuyển 200 ETH từ tài khoản của bạn sang tài khoản của Alice. + +[ERC-5564](https://eips.ethereum.org/EIPS/eip-5564) có giải pháp. ERC này giải thích cách sử dụng [địa chỉ ẩn](https://nerolation.github.io/stealth-utils) để chuyển tiền ẩn danh. + +**Cảnh báo**: Kỹ thuật mật mã học đằng sau các địa chỉ ẩn, theo những gì chúng tôi biết, là hợp lý. Tuy nhiên, có những cuộc tấn công kênh bên tiềm ẩn. [Bên dưới](#go-wrong), bạn sẽ thấy những gì bạn có thể làm để giảm rủi ro này. + +## Cách hoạt động của địa chỉ ẩn {#how} + +Bài viết này sẽ cố gắng giải thích địa chỉ ẩn theo hai cách. Đầu tiên là [cách sử dụng chúng](#how-use). Phần này là đủ để hiểu phần còn lại của bài viết. Sau đó, có [lời giải thích về toán học đằng sau nó](#how-math). Nếu bạn quan tâm đến mật mã học, hãy đọc cả phần này. + +### Phiên bản đơn giản (cách sử dụng địa chỉ ẩn) {#how-use} + +Alice tạo hai khóa riêng tư và xuất bản các khóa công khai tương ứng (có thể được kết hợp thành một siêu địa chỉ có độ dài gấp đôi). Bill cũng tạo một khóa riêng tư và xuất bản khóa công khai tương ứng. + +Sử dụng khóa công khai của một bên và khóa riêng tư của bên kia, bạn có thể suy ra một bí mật chung mà chỉ Alice và Bill biết (nó không thể được suy ra chỉ từ các khóa công khai). Sử dụng bí mật chung này, Bill có được địa chỉ ẩn và có thể gửi tài sản đến đó. + +Alice cũng nhận được địa chỉ từ bí mật chung, nhưng vì cô ấy biết các khóa riêng tư cho các khóa công khai mà cô ấy đã xuất bản, cô ấy cũng có thể nhận được khóa riêng tư cho phép cô ấy rút tiền từ địa chỉ đó. + +### Toán học (tại sao địa chỉ ẩn hoạt động như thế này) {#how-math} + +Các địa chỉ ẩn tiêu chuẩn sử dụng [mật mã học đường cong elip (ECC)](https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/#elliptic-curves-building-blocks-of-a-better-trapdoor) để có được hiệu suất tốt hơn với ít bit khóa hơn, trong khi vẫn giữ nguyên mức độ bảo mật. Nhưng phần lớn chúng ta có thể bỏ qua điều đó và giả vờ rằng chúng ta đang sử dụng số học thông thường. + +Có một con số mà mọi người đều biết, _G_. Bạn có thể nhân với _G_. Nhưng do bản chất của ECC, thực tế không thể chia cho _G_. Cách thức hoạt động chung của mật mã học khóa công khai trong Ethereum là bạn có thể sử dụng khóa riêng tư, _Ppriv_, để ký các giao dịch sau đó được xác minh bằng khóa công khai, _Ppub = GPpriv_. + +Alice tạo hai khóa riêng tư, _Kpriv_ và _Vpriv_. _Kpriv_ sẽ được sử dụng để chi tiêu tiền từ địa chỉ ẩn, và _Vpriv_ để xem các địa chỉ thuộc về Alice. Alice sau đó xuất bản các khóa công khai: _Kpub = GKpriv_ và _Vpub = GVpriv_ + +Bill tạo khóa riêng tư thứ ba, _Rpriv_, và xuất bản _Rpub = GRpriv_ lên một sổ đăng ký trung tâm (Bill cũng có thể đã gửi nó cho Alice, nhưng chúng ta giả định rằng Carol đang nghe lén). + +Bill tính toán _RprivVpub = GRprivVpriv_, mà anh ta mong đợi Alice cũng biết (được giải thích bên dưới). Giá trị này được gọi là _S_, bí mật chung. Điều này cho Bill một khóa công khai, _Ppub = Kpub+G\*hàm băm(S)_. Từ khóa công khai này, anh ta có thể tính toán một địa chỉ và gửi bất kỳ tài nguyên nào anh ta muốn đến đó. Trong tương lai, nếu Alice thắng, Bill có thể nói cho cô ấy biết _Rpriv_ để chứng minh các tài nguyên đến từ anh ta. + +Alice tính toán _RpubVpriv = GRprivVpriv_. Điều này cho cô ấy cùng một bí mật chung, _S_. Bởi vì cô ấy biết khóa riêng tư, _Kpriv_, cô ấy có thể tính toán _Ppriv = Kpriv+hàm băm(S)_. Khóa này cho phép cô ấy truy cập tài sản trong địa chỉ kết quả từ _Ppub = GPpriv = GKpriv+G\*hàm băm(S) = Kpub+G\*hàm băm(S)_. + +Chúng tôi có một khóa xem riêng để cho phép Alice ký hợp đồng phụ với Dịch vụ Chiến dịch Thống trị Thế giới của Dave. Alice sẵn sàng cho Dave biết các địa chỉ công khai và thông báo cho cô ấy khi có thêm tiền, nhưng cô ấy không muốn anh ta tiêu tiền chiến dịch của mình. + +Bởi vì việc xem và chi tiêu sử dụng các khóa riêng biệt, Alice có thể cung cấp cho Dave _Vpriv_. Sau đó, Dave có thể tính toán _S = RpubVpriv = GRprivVpriv_ và bằng cách đó nhận được các khóa công khai (_Ppub = Kpub+G\*hàm băm(S)_). Nhưng không có _Kpriv_ Dave không thể nhận được khóa riêng tư. + +Tóm lại, đây là những giá trị mà những người tham gia khác nhau biết. + +| Alice | Đã xuất bản | Bill | Dave | | +| ------------------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------- | +| G | G | G | G | | +| _Kpriv_ | – | – | – | | +| _Vpriv_ | – | – | _Vpriv_ | | +| _Kpub = GKpriv_ | _Kpub_ | _Kpub_ | _Kpub_ | | +| _Vpub = GVpriv_ | _Vpub_ | _Vpub_ | _Vpub_ | | +| – | – | _Rpriv_ | – | | +| _Rpub_ | _Rpub_ | _Rpub = GRpriv_ | _Rpub_ | | +| _S = RpubVpriv = GRprivVpriv_ | – | _S = RprivVpub = GRprivVpriv_ | _S = _RpubVpriv_ = GRprivVpriv_ | | +| _Ppub = Kpub+G\*hàm băm(S)_ | – | _Ppub = Kpub+G\*hàm băm(S)_ | _Ppub = Kpub+G\*hàm băm(S)_ | | +| _Địa chỉ=f(Ppub)_ | – | _Địa chỉ=f(Ppub)_ | _Địa chỉ=f(Ppub)_ | _Địa chỉ=f(Ppub)_ | +| _Ppriv = Kpriv+hàm băm(S)_ | – | – | – | | + +## Khi địa chỉ ẩn gặp sự cố {#go-wrong} + +_Không có bí mật nào trên chuỗi khối_. Mặc dù địa chỉ ẩn có thể cung cấp cho bạn quyền riêng tư, nhưng quyền riêng tư đó dễ bị phân tích lưu lượng. Để lấy một ví dụ nhỏ, hãy tưởng tượng rằng Bill cấp tiền cho một địa chỉ và ngay lập tức gửi một giao dịch để xuất bản giá trị _Rpub_. Nếu không có _Vpriv_ của Alice, chúng tôi không thể chắc chắn rằng đây là một địa chỉ ẩn, nhưng đó là cách để đặt cược. Sau đó, chúng ta thấy một giao dịch khác chuyển toàn bộ ETH từ địa chỉ đó đến địa chỉ quỹ chiến dịch của Alice. Chúng ta có thể không chứng minh được điều đó, nhưng có khả năng Bill vừa quyên góp cho chiến dịch của Alice. Carol chắc chắn sẽ nghĩ như vậy. + +Bill có thể dễ dàng tách việc xuất bản _Rpub_ khỏi việc cấp vốn cho địa chỉ ẩn (thực hiện chúng vào các thời điểm khác nhau, từ các địa chỉ khác nhau). Tuy nhiên, điều đó là không đủ. Mô hình mà Carol tìm kiếm là Bill cấp vốn cho một địa chỉ, và sau đó quỹ chiến dịch của Alice rút tiền từ đó. + +Một giải pháp là chiến dịch của Alice không rút tiền trực tiếp mà dùng nó để trả cho bên thứ ba. Nếu chiến dịch của Alice gửi 10 ETH đến Dịch vụ Chiến dịch Thống trị Thế giới của Dave, Carol chỉ biết rằng Bill đã quyên góp cho một trong những khách hàng của Dave. Nếu Dave có đủ khách hàng, Carol sẽ không thể biết liệu Bill đã quyên góp cho Alice, người cạnh tranh với cô ấy, hay cho Adam, Albert hoặc Abigail mà Carol không quan tâm. Alice có thể bao gồm một giá trị đã được băm trong khoản thanh toán, và sau đó cung cấp cho Dave tiền ảnh để chứng minh rằng đó là khoản quyên góp của cô ấy. Ngoài ra, như đã lưu ý ở trên, nếu Alice đưa cho Dave _Vpriv_ của mình, anh ta đã biết khoản thanh toán đến từ ai. + +Vấn đề chính với giải pháp này là nó yêu cầu Alice phải quan tâm đến bí mật khi bí mật đó mang lại lợi ích cho Bill. Alice có thể muốn duy trì danh tiếng của mình để bạn của Bill là Bob cũng sẽ quyên góp cho cô ấy. Nhưng cũng có khả năng cô ấy sẽ không ngại vạch trần Bill, bởi vì khi đó anh ta sẽ sợ những gì sẽ xảy ra nếu Carol thắng. Bill có thể cuối cùng sẽ hỗ trợ Alice nhiều hơn nữa. + +### Sử dụng nhiều lớp ẩn {#multi-layer} + +Thay vì dựa vào Alice để bảo vệ quyền riêng tư của Bill, Bill có thể tự làm điều đó. Anh ta có thể tạo nhiều siêu địa chỉ cho những người hư cấu, Bob và Bella. Bill sau đó gửi ETH cho Bob, và "Bob" (thực chất là Bill) gửi nó cho Bella. "Bella" (cũng là Bill) gửi nó cho Alice. + +Carol vẫn có thể phân tích lưu lượng và thấy đường ống Bill-đến-Bob-đến-Bella-đến-Alice. Tuy nhiên, nếu "Bob" và "Bella" cũng sử dụng ETH cho các mục đích khác, sẽ không có vẻ như Bill đã chuyển bất cứ thứ gì cho Alice, ngay cả khi Alice ngay lập tức rút tiền từ địa chỉ ẩn đến địa chỉ chiến dịch đã biết của cô ấy. + +## Viết một ứng dụng địa chỉ ẩn {#write-app} + +Bài viết này giải thích một ứng dụng địa chỉ ẩn [có sẵn trên GitHub](https://github.com/qbzzt/251022-stealth-addresses.git). + +### Công cụ {#tools} + +Có [một thư viện địa chỉ ẩn typescript](https://github.com/ScopeLift/stealth-address-sdk) mà chúng ta có thể sử dụng. Tuy nhiên, các hoạt động mật mã có thể tốn nhiều CPU. Tôi thích triển khai chúng bằng một ngôn ngữ biên dịch, chẳng hạn như [Rust](https://rust-lang.org/) và sử dụng [WASM](https://webassembly.org/) để chạy mã trong trình duyệt. + +Chúng tôi sẽ sử dụng [Vite](https://vite.dev/) và [React](https://react.dev/). Đây là những công cụ tiêu chuẩn ngành; nếu bạn không quen thuộc với chúng, bạn có thể sử dụng [hướng dẫn này](/developers/tutorials/creating-a-wagmi-ui-for-your-contract/). Để sử dụng Vite, chúng ta cần Node. + +### Xem địa chỉ ẩn hoạt động {#in-action} + +1. Cài đặt các công cụ cần thiết: [Rust](https://rust-lang.org/tools/install/) và [Node](https://nodejs.org/en/download). + +2. Nhân bản kho lưu trữ GitHub. + + ```sh + git clone https://github.com/qbzzt/251022-stealth-addresses.git + cd 251022-stealth-addresses + ``` + +3. Cài đặt các điều kiện tiên quyết và biên dịch mã Rust. + + ```sh + cd src/rust-wasm + rustup target add wasm32-unknown-unknown + cargo install wasm-pack + wasm-pack build --target web + ``` + +4. Khởi động máy chủ web. + + ```sh + cd ../.. + npm install + npm run dev + ``` + +5. Duyệt đến [ứng dụng](http://localhost:5173/). Trang ứng dụng này có hai khung: một cho giao diện người dùng của Alice và một cho giao diện của Bill. Hai khung không giao tiếp với nhau; chúng chỉ ở trên cùng một trang để thuận tiện. + +6. Với vai trò là Alice, nhấp vào **Tạo một Siêu địa chỉ ẩn**. Thao tác này sẽ hiển thị địa chỉ ẩn mới và các khóa riêng tư tương ứng. Sao chép siêu địa chỉ ẩn vào bảng nhớ tạm. + +7. Với vai trò là Bill, dán siêu địa chỉ ẩn mới và nhấp vào **Tạo một địa chỉ**. Thao tác này cung cấp cho bạn địa chỉ để cấp vốn cho Alice. + +8. Sao chép địa chỉ và khóa công khai của Bill và dán chúng vào khu vực "Khóa riêng tư cho địa chỉ do Bill tạo" của giao diện người dùng của Alice. Sau khi các trường đó được điền, bạn sẽ thấy khóa riêng tư để truy cập tài sản tại địa chỉ đó. + +9. Bạn có thể sử dụng [máy tính trực tuyến](https://iancoleman.net/ethereum-private-key-to-address/) để đảm bảo khóa riêng tư tương ứng với địa chỉ. + +### Cách chương trình hoạt động {#how-the-program-works} + +#### Thành phần WASM {#wasm} + +Mã nguồn biên dịch thành WASM được viết bằng [Rust](https://rust-lang.org/). Bạn có thể xem nó trong [`src/rust_wasm/src/lib.rs`](https://github.com/qbzzt/251022-stealth-addresses/blob/main/src/rust-wasm/src/lib.rs). Mã này chủ yếu là một giao diện giữa mã JavaScript và [thư viện `eth-stealth-addresses`](https://github.com/kassandraoftroy/eth-stealth-addresses). + +**`Cargo.toml`** + +[`Cargo.toml`](https://doc.rust-lang.org/cargo/reference/manifest.html) trong Rust tương tự như [`package.json`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json) trong JavaScript. Nó chứa thông tin gói, khai báo phụ thuộc, v.v. + +```toml +[package] +name = "rust-wasm" +version = "0.1.0" +edition = "2024" + +[dependencies] +eth-stealth-addresses = "0.1.0" +hex = "0.4.3" +wasm-bindgen = "0.2.104" +getrandom = { version = "0.2", features = ["js"] } +``` + +Gói [`getrandom`](https://docs.rs/getrandom/latest/getrandom/) cần tạo ra các giá trị ngẫu nhiên. Điều đó không thể được thực hiện bằng các phương tiện thuật toán thuần túy; nó đòi hỏi quyền truy cập vào một quy trình vật lý như một nguồn entropy. Định nghĩa này chỉ định rằng chúng ta sẽ nhận được entropy đó bằng cách yêu cầu trình duyệt mà chúng ta đang chạy. + +```toml +console_error_panic_hook = "0.1.7" +``` + +[Thư viện này](https://docs.rs/console_error_panic_hook/latest/console_error_panic_hook/) cung cấp cho chúng tôi các thông báo lỗi có ý nghĩa hơn khi mã WASM bị panic và không thể tiếp tục. + +```toml +[lib] +crate-type = ["cdylib", "rlib"] +``` + +Loại đầu ra cần thiết để tạo mã WASM. + +**`lib.rs`** + +Đây là mã Rust thực tế. + +```rust +use wasm_bindgen::prelude::*; +``` + +Các định nghĩa để tạo một gói WASM từ Rust. Chúng được ghi lại [tại đây](https://wasm-bindgen.github.io/wasm-bindgen/reference/attributes/index.html). + +```rust +use eth_stealth_addresses::{ + generate_stealth_meta_address, + generate_stealth_address, + compute_stealth_key +}; +``` + +Các hàm chúng tôi cần từ [thư viện `eth-stealth-addresses`](https://github.com/kassandraoftroy/eth-stealth-addresses). + +```rust +use hex::{decode,encode}; +``` + +Rust thường sử dụng các [mảng](https://doc.rust-lang.org/std/primitive.array.html) byte (`[u8; ]`) cho các giá trị. Nhưng trong JavaScript, chúng ta thường sử dụng các chuỗi thập lục phân. [Thư viện `hex`](https://docs.rs/hex/latest/hex/) dịch cho chúng ta từ dạng biểu diễn này sang dạng khác. + +```rust +#[wasm_bindgen] +``` + +Tạo các ràng buộc WASM để có thể gọi hàm này từ JavaScript. + +```rust +pub fn wasm_generate_stealth_meta_address() -> String { +``` + +Cách dễ nhất để trả về một đối tượng có nhiều trường là trả về một chuỗi JSON. + +```rust + let (address, spend_private_key, view_private_key) = + generate_stealth_meta_address(); +``` + +Hàm [`generate_stealth_meta_address`](https://docs.rs/eth-stealth-addresses/latest/eth_stealth_addresses/fn.generate_stealth_meta_address.html) trả về ba trường: + +- Siêu địa chỉ (_Kpub_ và _Vpub_) +- Khóa xem riêng tư (_Vpriv_) +- Khóa chi tiêu riêng tư (_Kpriv_) + +Cú pháp [tuple](https://doc.rust-lang.org/std/primitive.tuple.html) cho phép chúng ta tách các giá trị đó ra một lần nữa. + +```rust + format!("{{\"address\":\"{}\",\"view_private_key\":\"{}\",\"spend_private_key\":\"{}\"}}", + encode(address), + encode(view_private_key), + encode(spend_private_key) + ) +} +``` + +Sử dụng macro [`format!`](https://doc.rust-lang.org/std/fmt/index.html) để tạo chuỗi được mã hóa JSON. Sử dụng [`hex::encode`](https://docs.rs/hex/latest/hex/fn.encode.html) để thay đổi các mảng thành chuỗi hex. + +```rust +fn str_to_array(s: &str) -> Option<[u8; N]> { +``` + +Hàm này biến một chuỗi hex (do JavaScript cung cấp) thành một mảng byte. Chúng tôi sử dụng nó để phân tích cú pháp các giá trị được cung cấp bởi mã JavaScript. Hàm này phức tạp vì cách Rust xử lý các mảng và vectơ. + +Biểu thức `` được gọi là một [generic](https://doc.rust-lang.org/book/ch10-01-syntax.html). `N` là một tham số điều khiển độ dài của mảng được trả về. Hàm này thực sự được gọi là `str_to_array::`, trong đó `n` là độ dài mảng. + +Giá trị trả về là `Option<[u8; N]>`, có nghĩa là mảng được trả về là [tùy chọn](https://doc.rust-lang.org/std/option/). Đây là một mẫu điển hình trong Rust cho các hàm có thể thất bại. + +Ví dụ: nếu chúng ta gọi `str_to_array::10("bad060a7")`, hàm được cho là sẽ trả về một mảng mười giá trị, nhưng đầu vào chỉ có bốn byte. Hàm cần phải thất bại và nó làm như vậy bằng cách trả về `None`. Giá trị trả về cho `str_to_array::4("bad060a7")` sẽ là `Some<[0xba, 0xd0, 0x60, 0xa7]>`. + +```rust + // decode returns Result, _> + let vec = decode(s).ok()?; +``` + +Hàm [`hex::decode`](https://docs.rs/hex/latest/hex/fn.decode.html) trả về một `Result, FromHexError>`. Loại [`Result`](https://doc.rust-lang.org/std/result/) có thể chứa kết quả thành công (`Ok(value)`) hoặc lỗi (`Err(error)`). + +Phương thức `.ok()` biến `Result` thành một `Option`, có giá trị là giá trị `Ok()` nếu thành công hoặc `None` nếu không. Cuối cùng, [toán tử dấu hỏi](https://doc.rust-lang.org/std/option/#the-question-mark-operator-) sẽ hủy bỏ các hàm hiện tại và trả về `None` nếu `Option` trống. Nếu không, nó sẽ mở gói giá trị và trả về giá trị đó (trong trường hợp này, để gán một giá trị cho `vec`). + +Đây có vẻ là một phương pháp xử lý lỗi phức tạp một cách kỳ lạ, nhưng `Result` và `Option` đảm bảo rằng tất cả các lỗi đều được xử lý, bằng cách này hay cách khác. + +```rust + if vec.len() != N { return None; } +``` + +Nếu số lượng byte không chính xác, đó là một lỗi và chúng tôi trả về `None`. + +```rust + // try_into consumes vec and attempts to make [u8; N] + let array: [u8; N] = vec.try_into().ok()?; +``` + +Rust có hai loại mảng. [Mảng](https://doc.rust-lang.org/std/primitive.array.html) có kích thước cố định. [Vectơ](https://doc.rust-lang.org/std/vec/index.html) có thể phát triển và thu nhỏ. `hex::decode` trả về một vectơ, nhưng thư viện `eth_stealth_addresses` muốn nhận các mảng. [`.try_into()`](https://doc.rust-lang.org/std/convert/trait.TryInto.html#required-methods) chuyển đổi một giá trị thành một loại khác, ví dụ, một vectơ thành một mảng. + +```rust + Some(array) +} +``` + +Rust không yêu cầu bạn sử dụng từ khóa [`return`](https://doc.rust-lang.org/std/keyword.return.html) khi trả về một giá trị ở cuối hàm. + +```rust +#[wasm_bindgen] +pub fn wasm_generate_stealth_address(stealth_address: &str) -> Option { +``` + +Hàm này nhận một siêu địa chỉ công khai, bao gồm cả _Vpub_ và _Kpub_. Nó trả về địa chỉ ẩn, khóa công khai để xuất bản (_Rpub_) và giá trị quét một byte giúp tăng tốc độ xác định địa chỉ đã xuất bản nào có thể thuộc về Alice. + +Giá trị quét là một phần của bí mật chung (_S = GRprivVpriv_). Giá trị này có sẵn cho Alice, và việc kiểm tra nó nhanh hơn nhiều so với việc kiểm tra xem _f(Kpub+G\*hàm băm(S))_ có bằng địa chỉ đã xuất bản hay không. + +```rust + let (address, r_pub, scan) = + generate_stealth_address(&str_to_array::<66>(stealth_address)?); +``` + +Chúng tôi sử dụng hàm [`generate_stealth_address`](https://docs.rs/eth-stealth-addresses/latest/eth_stealth_addresses/fn.generate_stealth_address.html) của thư viện. + +```rust + format!("{{\"address\":\"{}\",\"rPub\":\"{}\",\"scan\":\"{}\"}}", + encode(address), + encode(r_pub), + encode(&[scan]) + ).into() +} +``` + +Chuẩn bị chuỗi đầu ra được mã hóa JSON. + +```rust +#[wasm_bindgen] +pub fn wasm_compute_stealth_key( + address: &str, + bill_pub_key: &str, + view_private_key: &str, + spend_private_key: &str +) -> Option { + . + . + . +} +``` + +Hàm này sử dụng [`compute_stealth_key`](https://docs.rs/eth-stealth-addresses/latest/eth_stealth_addresses/fn.compute_stealth_key.html) của thư viện để tính toán khóa riêng tư để rút khỏi địa chỉ (_Rpriv_). Phép tính này yêu cầu các giá trị sau: + +- Địa chỉ (_Địa chỉ=f(Ppub)_) +- Khóa công khai do Bill tạo (_Rpub_) +- Khóa xem riêng tư (_Vpriv_) +- Khóa chi tiêu riêng tư (_Kpriv_) + +```rust +#[wasm_bindgen(start)] +``` + +[`#[wasm_bindgen(start)]`](https://wasm-bindgen.github.io/wasm-bindgen/reference/attributes/on-rust-exports/start.html) chỉ định rằng hàm được thực thi khi mã WASM được khởi tạo. + +```rust +pub fn main() { + console_error_panic_hook::set_once(); +} +``` + +Mã này chỉ định rằng đầu ra panic được gửi đến bảng điều khiển JavaScript. Để xem nó hoạt động, hãy sử dụng ứng dụng và cung cấp cho Bill một siêu địa chỉ không hợp lệ (chỉ cần thay đổi một chữ số thập lục phân). Bạn sẽ thấy lỗi này trong bảng điều khiển JavaScript: + +``` +rust_wasm.js:236 panicked at /home/ori/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/subtle-2.6.1/src/lib.rs:701:9: +assertion `left == right` failed + left: 0 + right: 1 +``` + +Theo sau là một dấu vết ngăn xếp. Sau đó, cung cấp cho Bill siêu địa chỉ hợp lệ và cung cấp cho Alice một địa chỉ không hợp lệ hoặc một khóa công khai không hợp lệ. Bạn sẽ thấy lỗi này: + +``` +rust_wasm.js:236 panicked at /home/ori/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/eth-stealth-addresses-0.1.0/src/lib.rs:78:9: +keys do not generate stealth address +``` + +Một lần nữa, theo sau là một dấu vết ngăn xếp. + +#### Giao diện người dùng {#ui} + +Giao diện người dùng được viết bằng [React](https://react.dev/) và được phục vụ bởi [Vite](https://vite.dev/). Bạn có thể tìm hiểu về chúng bằng cách sử dụng [hướng dẫn này](/developers/tutorials/creating-a-wagmi-ui-for-your-contract/). Không cần [WAGMI](https://wagmi.sh/) ở đây vì chúng tôi không tương tác trực tiếp với chuỗi khối hoặc ví. + +Phần duy nhất không rõ ràng của giao diện người dùng là kết nối WASM. Đây là cách nó hoạt động. + +**`vite.config.js`** + +Tệp này chứa [cấu hình Vite](https://vite.dev/config/). + +```js +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import wasm from "vite-plugin-wasm"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react(), wasm()], +}) +``` + +Chúng ta cần hai plugin Vite: [react](https://www.npmjs.com/package/@vitejs/plugin-react) và [wasm](https://github.com/Menci/vite-plugin-wasm#readme). + +**`App.jsx`** + +Tệp này là thành phần chính của ứng dụng. Nó là một bộ chứa bao gồm hai thành phần: `Alice` và `Bill`, giao diện người dùng cho những người dùng đó. Phần liên quan cho WASM là mã khởi tạo. + +```jsx +import init from './rust-wasm/pkg/rust_wasm.js' +``` + +Khi chúng tôi sử dụng [`wasm-pack`](https://rustwasm.github.io/docs/wasm-pack/), nó tạo ra hai tệp chúng tôi sử dụng ở đây: một tệp wasm với mã thực tế (ở đây là `src/rust-wasm/pkg/rust_wasm_bg.wasm`) và một tệp JavaScript với các định nghĩa để sử dụng nó (ở đây là `src/rust_wasm/pkg/rust_wasm.js`). Xuất mặc định của tệp JavaScript đó là mã cần chạy để khởi tạo WASM. + +```jsx +function App() { + . + . + . + useEffect(() => { + const loadWasm = async () => { + try { + await init(); + setWasmReady(true) + } catch (err) { + console.error('Lỗi khi tải wasm:', err) + alert("Lỗi Wasm: " + err) + } + } + + loadWasm() + }, [] + ) +``` + +[Hook `useEffect`](https://react.dev/reference/react/useEffect) cho phép bạn chỉ định một hàm được thực thi khi các biến trạng thái thay đổi. Ở đây, danh sách các biến trạng thái trống (`[]`), vì vậy hàm này chỉ được thực thi một lần khi trang tải. + +Hàm hiệu ứng phải trả về ngay lập tức. Để sử dụng mã không đồng bộ, chẳng hạn như `init` của WASM (phải tải tệp `.wasm` và do đó mất thời gian), chúng tôi xác định một hàm [`async`](https://en.wikipedia.org/wiki/Async/await) nội bộ và chạy nó mà không có `await`. + +**`Bill.jsx`** + +Đây là giao diện người dùng cho Bill. Nó có một hành động duy nhất, tạo một địa chỉ dựa trên siêu địa chỉ ẩn do Alice cung cấp. + +```jsx +import { wasm_generate_stealth_address } from './rust-wasm/pkg/rust_wasm.js' +``` + +Ngoài việc xuất mặc định, mã JavaScript được tạo bởi `wasm-pack` còn xuất một hàm cho mọi hàm trong mã WASM. + +```jsx +