From 173e397a6a47686abe0b4267e9ee8cb85a9212ec Mon Sep 17 00:00:00 2001 From: Joshua <62268199+minimalsm@users.noreply.github.com> Date: Sat, 14 Feb 2026 00:15:49 +0000 Subject: [PATCH 1/2] i18n(zh-tw): translation import part 09 of 13 (23 files) --- .../secure-development-workflow/index.md | 52 + .../tutorials/send-token-ethersjs/index.md | 210 ++ .../index.md | 208 ++ .../tutorials/server-components/index.md | 295 +++ .../index.md | 92 + .../developers/tutorials/short-abi/index.md | 585 +++++ .../index.md | 91 + .../tutorials/stealth-addr/index.md | 436 ++++ .../index.md | 307 +++ .../token-integration-checklist/index.md | 80 + .../index.md | 314 +++ .../index.md | 177 ++ .../uniswap-v2-annotated-code/index.md | 1969 +++++++++++++++++ .../tutorials/using-websockets/index.md | 245 ++ .../index.md | 293 +++ .../index.md | 204 ++ .../index.md | 199 ++ .../tutorials/yellow-paper-evm/index.md | 278 +++ .../content/translations/zh-tw/eips/index.md | 47 +- .../zh-tw/energy-consumption/index.md | 69 +- .../translations/zh-tw/eth/supply/index.md | 80 + .../zh-tw/ethereum-forks/index.md | 373 ++-- .../translations/zh-tw/foundation/index.md | 43 +- 23 files changed, 6397 insertions(+), 250 deletions(-) create mode 100644 public/content/translations/zh-tw/developers/tutorials/secure-development-workflow/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/send-token-ethersjs/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/sending-transactions-using-web3-and-alchemy/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/server-components/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/set-up-web3js-to-use-ethereum-in-javascript/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/short-abi/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/smart-contract-security-guidelines/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/stealth-addr/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/the-graph-fixing-web3-data-querying/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/token-integration-checklist/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/transfers-and-approval-of-erc-20-tokens-from-a-solidity-smart-contract/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/understand-the-erc-20-token-smart-contract/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/uniswap-v2-annotated-code/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/using-websockets/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/waffle-dynamic-mocking-and-testing-calls/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/waffle-say-hello-world-with-hardhat-and-ethers/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/waffle-test-simple-smart-contract/index.md create mode 100644 public/content/translations/zh-tw/developers/tutorials/yellow-paper-evm/index.md create mode 100644 public/content/translations/zh-tw/eth/supply/index.md diff --git a/public/content/translations/zh-tw/developers/tutorials/secure-development-workflow/index.md b/public/content/translations/zh-tw/developers/tutorials/secure-development-workflow/index.md new file mode 100644 index 00000000000..e04f9071168 --- /dev/null +++ b/public/content/translations/zh-tw/developers/tutorials/secure-development-workflow/index.md @@ -0,0 +1,52 @@ +--- +title: "智慧型合約安全列清單" +description: "一推薦程序來編輯安全智慧型合約" +author: "Trailofbits" +tags: [ "智能合約", "安全性", "solidity" ] +skill: intermediate +lang: zh-tw +published: 2020-09-07 +source: Building secure contracts +sourceUrl: https://github.com/crytic/building-secure-contracts/blob/master/development-guidelines/workflow.md +--- + +## 智能合約開發檢查清單 {#smart-contract-development-checklist} + +此為一推薦之高階程序來遵照當你編輯你的智慧型合約. + +確認已知安全問題: + +- 使用 [Slither](https://github.com/crytic/slither) 檢閱您的合約。 此具一40 內建偵查器來檢查常見程式漏洞. 運行其於各新程式之檢查點並確保此具有一清晰回報(或利用一分流模式來靜音某些問題). +- 使用 [Crytic](https://crytic.io/) 檢閱您的合約。 此確認Slither不包含之50額外問題. Crytic能幫助你的團隊來處於開發優勢, 藉由簡單來顯示安全物提於GitHub提取請求. + +為你的合約考慮特殊規範: + +- 你的合約是否可被升級? 使用 [`slither-check-upgradeability`](https://github.com/crytic/slither/wiki/Upgradeability-Checks) 或 [Crytic](https://blog.trailofbits.com/2020/06/12/upgradeable-contracts-made-safer-with-crytic/) 檢查可升級性程式碼中的瑕疵。 我們已記錄17種可能導致失敗之升級方式. +- 你的合約是否聲稱符合ERCs標準? 使用 [`slither-check-erc`](https://github.com/crytic/slither/wiki/ERC-Conformance) 檢查。 此工具立即表示6項常見檢測指標觀點. +- 你是否與一第三方代幣互動? 在信賴外部合約前,請先檢閱我們的[代幣整合檢查清單](/developers/tutorials/token-integration-checklist/)。 + +可視重要安全特徵於你的合約程式: + +- 檢閱 Slither 的 [inheritance-graph](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph) 輸出器。 避免不經意之影覆蓋及C3線性化問題. +- 檢閱 Slither 的 [function-summary](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary) 輸出器。 此回報功能函數可視性及其訪問控制. +- 檢閱 Slither 的 [vars-and-auth](https://github.com/trailofbits/slither/wiki/Printer-documentation#variables-written-and-authorization) 輸出器。 此回報訪問控制及狀態變量. + +紀錄重要安全特性並使用自動化測試生成者來判斷其: + +- 學習如何[為您的程式碼記錄安全屬性](/developers/tutorials/guide-to-smart-contract-security-tools/)。 此剛開始極為困難, 但其為一最重要之活動來達成一良好安全結果. 此也為一基本要求來為任何先進技術於此教程. +- 在 Solidity 中定義安全屬性,以便與 [Echidna](https://github.com/crytic/echidna) 和 [Manticore](https://manticore.readthedocs.io/en/latest/verifier.html) 一起使用。 專注於你的狀態機器, 訪問控制, 算數操作, 外部互動, 及基本一致性. +- 使用 [Slither 的 Python 應用程式介面 (API)](/developers/tutorials/how-to-use-slither-to-find-smart-contract-bugs/) 定義安全屬性。 專注於繼承, 變量依據, 訪問控制, 及其他架構問題. +- 在每次提交時,使用 [Crytic](https://crytic.io) 執行您的屬性測試。 Crytic能消化及價值安全特性測試所以任何參與者於你開發團隊能簡單來查看其是否正確通過於GitHub. 失敗測試可能阻擋提議. + +最終, 請留心自動工具無法簡單發現之問題: + +- 隱私缺乏性: 所有參與者將能查看你的交易當其排隊於池內. +- 偷跑運行交易 +- 加密操作 +- 高風險互動與外部Defi部件 + +## 尋求協助 {#ask-for-help} + +[以太坊辦公時間](https://calendly.com/dan-trailofbits/office-hours) 於每週二下午舉行。 此1小時, 1-對-1對應為一極佳機會來讓任何人來詢問關於安全, 道具使用憂慮, 並取得專家回報關於你的目前選擇方案. 我們將幫助你於此對應機會. + +加入我們的 Slack:[Empire Hacking](https://join.slack.com/t/empirehacking/shared_invite/zt-h97bbrj8-1jwuiU33nnzg67JcvIciUw)。 我們永遠於#crytic 及 #ethereum通道如你有任何問題. diff --git a/public/content/translations/zh-tw/developers/tutorials/send-token-ethersjs/index.md b/public/content/translations/zh-tw/developers/tutorials/send-token-ethersjs/index.md new file mode 100644 index 00000000000..969bbd6ac91 --- /dev/null +++ b/public/content/translations/zh-tw/developers/tutorials/send-token-ethersjs/index.md @@ -0,0 +1,210 @@ +--- +title: "使用 ethers.js 傳送代幣" +description: "使用 ethers.js 傳送代幣的初學者指南。" +author: Kim YongJun +tags: [ "ETHERS.JS", "ERC-20", "代幣" ] +skill: beginner +lang: zh-tw +published: 2021-04-06 +--- + +## 使用 ethers.js (5.0) 傳送代幣 {#send-token} + +### 在本教學中,您將學習如何 {#you-learn-about} + +- 匯入 ethers.js +- 傳送代幣 +- 根據網路流量情況設定 gas 價格 + +### 開始使用 {#to-get-started} + +開始之前,我們必須先將 ethers.js 函式庫匯入 javascript 程式碼中 +包含 ethers.js (5.0) + +### 安裝 {#install-ethersjs} + +```shell +/home/ricmoo> npm install --save ethers +``` + +瀏覽器中的 ES6 + +```html + +``` + +瀏覽器中的 ES3 (UMD) + +```html + +``` + +### 參數 {#param} + +1. **`contract_address`**:代幣合約地址(當您要傳送的代幣不是 ether 時,需要合約地址) +2. **`send_token_amount`**:您想傳送給接收者的代幣數量 +3. **`to_address`**:接收者的地址 +4. **`send_account`**:傳送者的地址 +5. **`private_key`**:傳送者的私密金鑰,用以簽署交易並實際傳送代幣 + +## 注意事項 {#notice} + +`signTransaction(tx)` 已被移除,因為 `sendTransaction()` 會在內部處理。 + +## 傳送程序 {#procedure} + +### 1. 連線至網路 (測試網) {#connect-to-network} + +#### 設定提供者 (Infura) {#set-provider} + +連線至 Ropsten 測試網 + +```javascript +window.ethersProvider = new ethers.providers.InfuraProvider("ropsten") +``` + +### 2. 建立錢包 {#create-wallet} + +```javascript +let wallet = new ethers.Wallet(private_key) +``` + +### 3 將錢包連線至網路 {#connect-wallet-to-net} + +```javascript +let walletSigner = wallet.connect(window.ethersProvider) +``` + +### 4 取得目前的 gas 價格 {#get-gas} + +```javascript +window.ethersProvider.getGasPrice() // gas 價格 +``` + +### 5 定義交易 {#define-transaction} + +下方定義的變數相依於 `send_token()`。 + +### 交易參數 {#transaction-params} + +1. **`send_account`**:代幣傳送者的地址 +2. **`to_address`**:代幣接收者的地址 +3. **`send_token_amount`**:要傳送的代幣數量 +4. **`gas_limit`**:gas 上限 +5. **`gas_price`**:gas 價格 + +[關於如何使用,請參閱下方](#how-to-use) + +```javascript +const tx = { + from: send_account, + to: to_address, + value: ethers.utils.parseEther(send_token_amount), + nonce: window.ethersProvider.getTransactionCount(send_account, "latest"), + gasLimit: ethers.utils.hexlify(gas_limit), // 100000 + gasPrice: gas_price, +} +``` + +### 6. 傳送 {#transfer} + +```javascript +walletSigner.sendTransaction(tx).then((transaction) => { + console.dir(transaction) + alert("傳送完成!") +}) +``` + +## 如何使用 {#how-to-use} + +```javascript +let private_key = + "41559d28e936dc92104ff30691519693fc753ffbee6251a611b9aa1878f12a4d" +let send_token_amount = "1" +let to_address = "0x4c10D2734Fb76D3236E522509181CC3Ba8DE0e80" +let send_address = "0xda27a282B5B6c5229699891CfA6b900A716539E6" +let gas_limit = "0x100000" +let wallet = new ethers.Wallet(private_key) +let walletSigner = wallet.connect(window.ethersProvider) +let contract_address = "" +window.ethersProvider = new ethers.providers.InfuraProvider("ropsten") + +send_token( + contract_address, + send_token_amount, + to_address, + send_address, + private_key +) +``` + +### 成功! {#success} + +![交易成功完成的圖片](./successful-transaction.png) + +## send_token() {#send-token-method} + +```javascript +function send_token( + contract_address, + send_token_amount, + to_address, + send_account, + private_key +) { + let wallet = new ethers.Wallet(private_key) + let walletSigner = wallet.connect(window.ethersProvider) + + window.ethersProvider.getGasPrice().then((currentGasPrice) => { + let gas_price = ethers.utils.hexlify(parseInt(currentGasPrice)) + console.log(`gas_price: ${gas_price}`) + + if (contract_address) { + // 一般代幣傳送 + let contract = new ethers.Contract( + contract_address, + send_abi, + walletSigner + ) + + // 多少代幣? + let numberOfTokens = ethers.utils.parseUnits(send_token_amount, 18) + console.log(`numberOfTokens: ${numberOfTokens}`) + + // 傳送代幣 + contract.transfer(to_address, numberOfTokens).then((transferResult) => { + console.dir(transferResult) + alert("已傳送代幣") + }) + } // ether 傳送 + else { + const tx = { + from: send_account, + to: to_address, + value: ethers.utils.parseEther(send_token_amount), + nonce: window.ethersProvider.getTransactionCount( + send_account, + "latest" + ), + gasLimit: ethers.utils.hexlify(gas_limit), // 100000 + gasPrice: gas_price, + } + console.dir(tx) + try { + walletSigner.sendTransaction(tx).then((transaction) => { + console.dir(transaction) + alert("傳送完成!") + }) + } catch (error) { + alert("傳送失敗!!") + } + } + }) +} +``` diff --git a/public/content/translations/zh-tw/developers/tutorials/sending-transactions-using-web3-and-alchemy/index.md b/public/content/translations/zh-tw/developers/tutorials/sending-transactions-using-web3-and-alchemy/index.md new file mode 100644 index 00000000000..444f5940420 --- /dev/null +++ b/public/content/translations/zh-tw/developers/tutorials/sending-transactions-using-web3-and-alchemy/index.md @@ -0,0 +1,208 @@ +--- +title: "使用 Web3 發送交易" +description: "這是一份初學者友善指南,說明如何使用 Web3 發送以太坊交易。 將交易發送到以太坊區塊鏈有三個主要步驟:建立、簽署和廣播。 我們將會逐一說明。" +author: "Elan Halpern" +tags: [ "交易", "web3.js", "alchemy" ] +skill: beginner +lang: zh-tw +published: 2020-11-04 +source: Alchemy docs +sourceUrl: https://www.alchemy.com/docs/how-to-send-transactions-on-ethereum +--- + +這是一份初學者友善指南,說明如何使用 Web3 發送以太坊交易。 將交易發送到以太坊區塊鏈有三個主要步驟:建立、簽署和廣播。 我們將逐一說明這三個步驟,希望能解答您可能有的任何問題! 在本教學中,我們將使用 [Alchemy](https://www.alchemy.com/) 將交易發送到以太坊鏈。 您可以在[此處建立免費的 Alchemy 帳戶](https://auth.alchemyapi.io/signup)。 + +\*\*注意:\*\*本指南適用於在您應用程式的_後端_簽署交易。 如果您想在前端整合簽署交易,請參閱[整合 Web3 與瀏覽器供應商](https://docs.alchemy.com/reference/api-overview#with-a-browser-provider)。 + +## 基本知識 {#the-basics} + +和大多數初入門的區塊鏈開發人員一樣,您可能已經研究過如何發送交易(這應該是件很簡單的事),卻看到一大堆指南,每個指南的說法都不一樣,讓您有點不知所措和困惑。 如果您也遇到這種情況,別擔心,我們都曾有過同樣的經歷! 那麼,在開始之前,讓我們先釐清幾件事: + +### 1. Alchemy 不會儲存您的私密金鑰 {#alchemy-does-not-store-your-private-keys} + +- 這表示 Alchemy 無法代表您簽署和發送交易。 這是出於安全考量。 Alchemy 絕不會要求您分享您的私密金鑰,您也絕不應該將您的私密金鑰與託管節點(或任何人)分享。 +- 您可以使用 Alchemy 的核心 API 從區塊鏈讀取資料,但要寫入資料,您需要在透過 Alchemy 發送交易前,使用其他工具簽署交易(這適用於任何其他[節點服務](/developers/docs/nodes-and-clients/nodes-as-a-service/))。 + +### 2. 什麼是「簽署者」? {#what-is-a-signer} + +- 簽署者會使用您的私密金鑰為您簽署交易。 在本教學中,我們將使用 [Alchemy web3](https://docs.alchemyapi.io/alchemy/documentation/alchemy-web3) 來簽署我們的交易,但您也可以使用任何其他 web3 函式庫。 +- 在前端,[MetaMask](https://metamask.io/) 就是一個簽署者的好例子,它會代表您簽署並發送交易。 + +### 3. 為什麼我需要簽署我的交易? {#why-do-i-need-to-sign-my-transactions} + +- 每位想在以太坊網路上發送交易的使用者都必須簽署該交易(使用他們的私密金鑰),以驗證交易的來源確實是其所聲稱的發送者。 +- 保護此私密金鑰極為重要,因為擁有它就等於完全控制您的以太坊帳戶,讓您(或任何有權存取的人)能代表您執行交易。 + +### 4. 我該如何保護我的私密金鑰? {#how-do-i-protect-my-private-key} + +- 保護您的私密金鑰並用它來發送交易的方法有很多種。 在本教學中,我們將使用一個 `.env` 檔案。 然而,您也可以使用儲存私密金鑰的獨立供應商、使用金鑰儲存檔案或其他選項。 + +### 5. `eth_sendTransaction` 和 `eth_sendRawTransaction` 之間有什麼區別? {#difference-between-send-and-send-raw} + +`eth_sendTransaction` 和 `eth_sendRawTransaction` 都是以太坊 API 函式,可將交易廣播到以太坊網路,以便將其新增到未來的區塊中。 它們的不同之處在於處理交易簽署的方式。 + +- [`eth_sendTransaction`](https://docs.web3js.org/api/web3-eth/function/sendTransaction) 用於發送_未簽署_的交易,這表示您發送到的節點必須管理您的私密金鑰,才能在將交易廣播到鏈上之前對其進行簽署。 由於 Alchemy 不持有使用者的私密金鑰,因此不支援此方法。 +- [`eth_sendRawTransaction`](https://docs.alchemyapi.io/documentation/alchemy-api-reference/json-rpc#eth_sendrawtransaction) 用於廣播已經簽署的交易。 這表示您必須先使用 [`signTransaction(tx, private_key)`](https://docs.web3js.org/api/web3-eth-accounts/function/signTransaction),然後將結果傳遞到 `eth_sendRawTransaction` 中。 + +使用 web3 時,可透過呼叫 [web3.eth.sendSignedTransaction](https://docs.web3js.org/api/web3-eth/function/sendSignedTransaction) 函式來存取 `eth_sendRawTransaction`。 + +這就是我們將在本教學中使用的內容。 + +### 6. 什麼是 web3 函式庫? {#what-is-the-web3-library} + +- Web3.js 是一個標準 JSON-RPC 呼叫的包裝函式庫,在以太坊開發中相當常用。 +- 有許多針對不同語言的 web3 函式庫。 在本教學中,我們將使用以 JavaScript 編寫的 [Alchemy Web3](https://docs.alchemy.com/reference/api-overview)。 您可以[在此處](https://docs.alchemyapi.io/guides/getting-started#other-web3-libraries)查看其他選項,例如 [ethers.js](https://docs.ethers.org/v5/)。 + +好了,既然我們已經解決了這些問題,就讓我們繼續進行教學吧。 隨時歡迎在 Alchemy [Discord](https://discord.gg/gWuC7zB) 中提問! + +### 7. 如何發送安全、燃料優化和私密的交易? {#how-to-send-secure-gas-optimized-and-private-transactions} + +- [Alchemy 有一套 Transact API](https://docs.alchemy.com/reference/transact-api-quickstart)。 您可以使用這些 API 來發送增強型交易、在交易發生前進行模擬、發送私密交易,以及發送燃料優化的交易。 +- 您也可以使用 [Notify API](https://docs.alchemy.com/docs/alchemy-notify) 在您的交易從記憶體池中取出並新增到鏈上時收到提醒。 + +\*\*注意:\*\*本指南需要一個 Alchemy 帳戶、一個以太坊地址或 MetaMask 錢包,並安裝 NodeJs 和 npm。 如果沒有,請按照以下步驟操作: + +1. [建立免費的 Alchemy 帳戶](https://auth.alchemyapi.io/signup) +2. [建立 MetaMask 帳戶](https://metamask.io/)(或取得一個以太坊地址) +3. [按照這些步驟安裝 NodeJs 和 NPM](https://docs.alchemy.com/alchemy/guides/alchemy-for-macs) + +## 發送交易的步驟 {#steps-to-sending-your-transaction} + +### 1. 在 Sepolia 測試網上建立一個 Alchemy 應用程式 {#create-an-alchemy-app-on-the-sepolia-testnet} + +前往您的 [Alchemy 儀表板](https://dashboard.alchemyapi.io/) 並建立一個新應用程式,為您的網路選擇 Sepolia(或任何其他測試網)。 + +### 2. 從 Sepolia 水龍頭請求 ETH {#request-eth-from-sepolia-faucet} + +按照 [Alchemy Sepolia 水龍頭](https://www.sepoliafaucet.com/) 上的說明接收 ETH。 請務必提供您的 **Sepolia** 以太坊地址(來自 MetaMask),而不是其他網路的地址。 按照說明操作後,請再次檢查您的錢包是否已收到 ETH。 + +### 3. 建立一個新專案目錄並 `cd` 進入 {#create-a-new-project-direction} + +從命令列(macs 為終端機)建立一個新專案目錄並進入其中: + +``` +mkdir sendtx-example +cd sendtx-example +``` + +### 4. 安裝 Alchemy Web3(或任何 web3 函式庫) {#install-alchemy-web3} + +在您的專案目錄中執行以下指令來安裝 [Alchemy Web3](https://docs.alchemy.com/reference/api-overview): + +注意,如果您想使用 ethers.js 函式庫,請[按照此處的說明操作](https://docs.alchemy.com/docs/how-to-send-transactions-on-ethereum)。 + +``` +npm install @alch/alchemy-web3 +``` + +### 5. 安裝 dotenv {#install-dotenv} + +我們將使用一個 `.env` 檔案來安全地儲存我們的 API 金鑰和私密金鑰。 + +``` +npm install dotenv --save +``` + +### 6. 建立 `.env` 檔案 {#create-the-dotenv-file} + +在您的專案目錄中建立一個 `.env` 檔案並新增以下內容(取代「`your-api-url`」和「`your-private-key`」) + +- 若要尋找您的 Alchemy API URL,請前往您儀表板上剛建立的應用程式的詳細資料頁面,點擊右上角的「View Key」,然後複製 HTTP URL。 +- 若要使用 MetaMask 尋找您的私密金鑰,請參閱本[指南](https://metamask.zendesk.com/hc/en-us/articles/360015289632-How-to-Export-an-Account-Private-Key)。 + +``` +API_URL = "your-api-url" +PRIVATE_KEY = "your-private-key" +``` + + + + +不要提交 .env! 請務必不要與任何人分享或洩露您的 .env 檔案,因為這樣做會洩露您的機密。 如果您正在使用版本控制,請將您的 .env 新增到 gitignore 檔案中。 + + + + +### 7. 建立 `sendTx.js` 檔案 {#create-sendtx-js} + +太好了,既然我們的敏感資料已在 `.env` 檔案中受到保護,就讓我們開始編寫程式碼吧。 在我們的發送交易範例中,我們會將 ETH 發送回 Sepolia 水龍頭。 + +建立一個 `sendTx.js` 檔案,我們將在其中設定並發送我們的範例交易,然後將以下程式碼行新增到該檔案中: + +``` +async function main() { + require('dotenv').config(); + const { API_URL, PRIVATE_KEY } = process.env; + const { createAlchemyWeb3 } = require("@alch/alchemy-web3"); + const web3 = createAlchemyWeb3(API_URL); + const myAddress = '0x610Ae88399fc1687FA7530Aac28eC2539c7d6d63' //待辦事項:將此地址替換為您自己的公開地址 + + const nonce = await web3.eth.getTransactionCount(myAddress, 'latest'); // nonce 從 0 開始計數 + + const transaction = { + 'to': '0x31B98D14007bDEe637298086988A0bBd31184523', // 用於返還 eth 的水龍頭地址 + 'value': 1000000000000000000, // 1 ETH + 'gas': 30000, + 'nonce': nonce, + // 可選的資料欄位,用於發送訊息或執行智能合約 + }; + + const signedTx = await web3.eth.accounts.signTransaction(transaction, PRIVATE_KEY); + + web3.eth.sendSignedTransaction(signedTx.rawTransaction, function(error, hash) { + if (!error) { + console.log("🎉 您的交易哈希為: ", hash, "\n 請到 Alchemy 的記憶體池查看您的交易狀態!"); + } else { + console.log("❗提交交易時出錯:", error) + } + }); +} + +main(); +``` + +請務必將**第 6 行**的地址替換為您自己的公開地址。 + +現在,在我們開始執行這段程式碼之前,讓我們先談談這裡的一些組成部分。 + +- `nonce`:nonce 規範用於追蹤從您的地址發送的交易數量。 我們需要它來確保安全並防止[重放攻擊](https://docs.alchemyapi.io/resources/blockchain-glossary#account-nonce)。 為了取得從您的地址發送的交易數量,我們使用 [getTransactionCount](https://docs.alchemyapi.io/documentation/alchemy-api-reference/json-rpc#eth_gettransactioncount)。 +- `transaction`:交易物件有幾個我們需要指定的方面 + - `to`:這是我們要發送 ETH 的目標地址。 在這種情況下,我們是將 ETH 發送回我們最初請求的 [Sepolia 水龍頭](https://sepoliafaucet.com/)。 + - `value`:這是我們希望發送的金額,以 Wei 為單位,其中 10^18 Wei = 1 ETH + - `gas`:有很多方法可以決定交易中要包含的正確燃料數量。 Alchemy 甚至有一個[燃料價格 webhook](https://docs.alchemyapi.io/guides/alchemy-notify#address-activity-1),可以在燃料價格降至特定閾值時通知您。 對於主網交易,最好檢查像 [ETH Gas Station](https://ethgasstation.info/) 這樣的燃料估算器,以確定要包含的正確燃料數量。 21000 是以太坊上一次操作將使用的最低燃料量,因此為了確保我們的交易能夠執行,我們在此設定為 30000。 + - `nonce`:請參見上面的 nonce 定義。 Nonce 從零開始計數。 + - [可選] data:用於在您的轉帳中發送附加資訊,或呼叫智能合約,餘額轉帳非必要,請參閱下面的說明。 +- `signedTx`:為了簽署我們的交易物件,我們將使用 `signTransaction` 方法和我們的 `PRIVATE_KEY` +- `sendSignedTransaction`:一旦我們有了已簽署的交易,就可以使用 `sendSignedTransaction` 將其發送出去,以便包含在後續的區塊中 + +**關於 data 的說明** +在以太坊中可以發送兩種主要類型的交易。 + +- 餘額轉帳:將 ETH 從一個地址發送到另一個地址。 不需要 data 欄位,但是,如果您想在交易中附帶額外資訊,可以在此欄位中以 HEX 格式包含該資訊。 + - 例如,假設我們想將 IPFS 文件的哈希寫入以太坊鏈,以便為其提供一個不可變的時間戳。 我們的 data 欄位看起來就會像這樣:`web3.utils.toHex(‘IPFS 哈希‘)`。 現在任何人都可以查詢鏈,查看該文件是何時新增的。 +- 智能合約交易:在鏈上執行一些智能合約程式碼。 在這種情況下,data 欄位應包含您希望執行的智能函式以及任何參數。 + - 如需實際範例,請參閱此 [Hello World 教學](https://docs.alchemyapi.io/alchemy/tutorials/hello-world-smart-contract#step-8-create-the-transaction)中的步驟 8。 + +### 8. 使用 `node sendTx.js` 執行程式碼 {#run-the-code-using-node-sendtx-js} + +返回您的終端機或命令列並執行: + +``` +node sendTx.js +``` + +### 9. 在記憶體池中查看您的交易 {#see-your-transaction-in-the-mempool} + +在您的 Alchemy 儀表板中開啟[記憶體池頁面](https://dashboard.alchemyapi.io/mempool),並按您建立的應用程式進行篩選以尋找您的交易。 在這裡,我們可以看到我們的交易從待處理狀態轉換為已探勘狀態(如果成功)或已丟棄狀態(如果不成功)。 請務必將其保持在「全部」狀態,以便捕獲「已探勘」、「待處理」和「已丟棄」的交易。 您也可以透過搜尋發送到地址 `0x31b98d14007bdee637298086988a0bbd31184523` 的交易來尋找您的交易。 + +找到交易後,若要查看其詳細資訊,請選擇交易哈希,這會將您帶到如下所示的檢視畫面: + +![記憶體池觀察器螢幕截圖](./mempool.png) + +從那裡,您可以點擊紅色圈出的圖示,在 Etherscan 上查看您的交易! + +**太棒了! 您剛剛使用 Alchemy 發送了您的第一筆以太坊交易 🎉** + +_若對本指南有任何回饋與建議,請在 Alchemy 的 [Discord](https://discord.gg/A39JVCM) 上傳送訊息給 Elan!_ + +_原文發表於 [https://docs.alchemyapi.io/tutorials/sending-transactions-using-web3-and-alchemy](https://docs.alchemyapi.io/tutorials/sending-transactions-using-web3-and-alchemy)_ diff --git a/public/content/translations/zh-tw/developers/tutorials/server-components/index.md b/public/content/translations/zh-tw/developers/tutorials/server-components/index.md new file mode 100644 index 00000000000..c44318b6f29 --- /dev/null +++ b/public/content/translations/zh-tw/developers/tutorials/server-components/index.md @@ -0,0 +1,295 @@ +--- +title: "Web3 應用程式的伺服器元件和代理" +description: "閱讀本教學後,您將能編寫 TypeScript 伺服器,以偵聽區塊鏈上的事件,並透過自己的交易做出相應的回應。 這將讓您能夠編寫中心化應用程式(因為伺服器是一個故障點),但可以與 Web3 實體互動。 同樣的技術也可以用來編寫一個無需人工干預即可回應鏈上事件的代理。" + +author: Ori Pomerantz +lang: zh-tw +tags: [ "代理", "伺服器", "鏈下" ] +skill: beginner +published: 2024-07-15 +--- + +## 介紹 {#introduction} + +在大多數情況下,去中心化應用程式會使用伺服器來分發軟體,但所有實際互動都發生在用戶端(通常是網頁瀏覽器)和區塊鏈之間。 + +![網頁伺服器、用戶端和區塊鏈之間的正常互動](./fig-1.svg) + +然而,在某些情況下,應用程式可以從獨立執行的伺服器元件中受益。 這樣的伺服器能夠透過發行交易來回應事件以及來自其他來源(如 API)的請求。 + +![新增伺服器後的互動](./fig-2.svg) + +這樣的伺服器可以完成幾個可能的任務。 + +- 秘密狀態的持有者。 在遊戲中,不讓玩家知道遊戲所知的所有資訊通常很有用。 然而,_區塊鏈上沒有秘密_,區塊鏈中的任何資訊都很容易被任何人發現。 因此,如果遊戲狀態的一部分要保密,就必須儲存在其他地方(而且可能需要使用[零知識證明](/zero-knowledge-proofs)來驗證該狀態的效果)。 + +- 中心化預言機。 如果利害關係夠低,一個從網路上讀取一些資訊然後發布到鏈上的外部伺服器,可能就足以作為[預言機](/developers/docs/oracles/)來使用。 + +- 代理。 如果沒有交易來啟動,區塊鏈上什麼事都不會發生。 當機會出現時,伺服器可以代表使用者執行套利等操作,例如[套利](/developers/docs/mev/#mev-examples-dex-arbitrage)。 + +## 範例程式 {#sample-program} + +您可以在 [github](https://github.com/qbzzt/20240715-server-component) 上看到一個範例伺服器。 這個伺服器會偵聽來自[此合約](https://eth-holesky.blockscout.com/address/0xB8f6460Dc30c44401Be26B0d6eD250873d8a50A6?tab=contract_code) 的事件,這是 Hardhat 的 Greeter 的修改版本。 當問候語被更改時,它會將其改回來。 + +若要執行它: + +1. 複製儲存庫。 + + ```sh copy + git clone https://github.com/qbzzt/20240715-server-component.git + cd 20240715-server-component + ``` + +2. 安裝必要的套件。 如果您還沒有,[請先安裝 Node](https://nodejs.org/en/download/package-manager)。 + + ```sh copy + npm install + ``` + +3. 編輯 `.env` 來指定在 Holesky 測試網上擁有 ETH 的帳戶的私密金鑰。 如果您在 Holesky 上沒有 ETH,您可以使用[這個水龍頭](https://holesky-faucet.pk910.de/)。 + + ```sh filename=".env" copy + PRIVATE_KEY=0x <此處填入私密金鑰> + ``` + +4. 啟動伺服器。 + + ```sh copy + npm start + ``` + +5. 前往[區塊瀏覽器](https://eth-holesky.blockscout.com/address/0xB8f6460Dc30c44401Be26B0d6eD250873d8a50A6?tab=write_contract),並使用與擁有私密金鑰的地址不同的地址來修改問候語。 查看問候語是否自動被改回。 + +### 它是如何運作的? {#how-it-works} + +理解如何編寫伺服器元件最簡單的方法是逐行檢查範例。 + +#### `src/app.ts` {#src-app-ts} + +程式的絕大部分都包含在 [`src/app.ts`](https://github.com/qbzzt/20240715-server-component/blob/main/src/app.ts) 中。 + +##### 建立先決物件 + +```typescript +import { + createPublicClient, + createWalletClient, + getContract, + http, + Address, +} from "viem" +``` + +這些是我們需要的 [Viem](https://viem.sh/) 實體、函式以及 [`Address` 類型](https://viem.sh/docs/glossary/types#address)。 這個伺服器是用 [TypeScript](https://www.typescriptlang.org/) 編寫的,它是 JavaScript 的一個擴充,使其成為[強型別](https://en.wikipedia.org/wiki/Strong_and_weak_typing)。 + +```typescript +import { privateKeyToAccount } from "viem/accounts" +``` + +[這個函式](https://viem.sh/docs/accounts/privateKey) 讓我們可以根據私密金鑰產生錢包資訊,包括地址。 + +```typescript +import { holesky } from "viem/chains" +``` + +要在 Viem 中使用區塊鏈,您需要匯入其定義。 在這種情況下,我們想要連接到 [Holesky](https://github.com/eth-clients/holesky) 測試區塊鏈。 + +```typescript +// 這就是我們如何將 .env 中的定義新增到 process.env 的方法。 +import * as dotenv from "dotenv" +dotenv.config() +``` + +這就是我們將 `.env` 讀入環境的方式。 我們需要它來取得私密金鑰(稍後會看到)。 + +```typescript +const greeterAddress : Address = "0xB8f6460Dc30c44401Be26B0d6eD250873d8a50A6" +const greeterABI = [ + { + "inputs": [ + { + "internalType": "string", + "name": "_greeting", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + . + . + . + { + "inputs": [ + { + "internalType": "string", + "name": "_greeting", + "type": "string" + } + ], + "name": "setGreeting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] as const +``` + +要使用合約,我們需要它的地址和 [ABI](/glossary/#abi)。 我們在這裡提供了這兩者。 + +在 JavaScript(以及因此在 TypeScript 中),您不能為常數指定新值,但您_可以_修改儲存在其中的物件。 透過使用 `as const` 後綴,我們告訴 TypeScript 該清單本身是常數,不可更改。 + +```typescript +const publicClient = createPublicClient({ + chain: holesky, + transport: http(), +}) +``` + +建立一個 Viem [公開用戶端](https://viem.sh/docs/clients/public.html)。 公開用戶端沒有附加的私密金鑰,因此無法傳送交易。 他們可以呼叫 [`view` 函式](https://www.tutorialspoint.com/solidity/solidity_view_functions.htm)、讀取帳戶餘額等。 + +```typescript +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) +``` + +環境變數可在 [`process.env`](https://www.totaltypescript.com/how-to-strongly-type-process-env) 中取得。 然而,TypeScript 是強型別的。 環境變數可以是任何字串,或為空,所以環境變數的類型是 `string | undefined`。 然而,金鑰在 Viem 中被定義為 `0x${string}`(`0x` 後面跟著一個字串)。 在這裡,我們告訴 TypeScript `PRIVATE_KEY` 環境變數將是該類型。 如果不是,我們將會得到一個執行時錯誤。 + +[`privateKeyToAccount`](https://viem.sh/docs/accounts/privateKey) 函式接著使用此私密金鑰來建立一個完整的帳戶物件。 + +```typescript +const walletClient = createWalletClient({ + account, + chain: holesky, + transport: http(), +}) +``` + +接下來,我們使用帳戶物件來建立一個[錢包用戶端](https://viem.sh/docs/clients/wallet)。 此用戶端擁有私密金鑰和地址,因此可以用於傳送交易。 + +```typescript +const greeter = getContract({ + address: greeterAddress, + abi: greeterABI, + client: { public: publicClient, wallet: walletClient }, +}) +``` + +現在我們有了所有先決條件,終於可以建立一個[合約實例](https://viem.sh/docs/contract/getContract)。 我們將使用此合約實例與鏈上合約進行通訊。 + +##### 從區塊鏈讀取 + +```typescript +console.log(`Current greeting:`, await greeter.read.greet()) +``` + +唯讀的合約函式([`view`](https://www.tutorialspoint.com/solidity/solidity_view_functions.htm) 和 [`pure`](https://www.tutorialspoint.com/solidity/solidity_pure_functions.htm))可在 `read` 下找到。 在這種情況下,我們使用它來存取 [`greet`](https://eth-holesky.blockscout.com/address/0xB8f6460Dc30c44401Be26B0d6eD250873d8a50A6?tab=read_contract#cfae3217) 函式,該函式會傳回問候語。 + +JavaScript 是單執行緒的,所以當我們啟動一個長時間執行的程序時,我們需要[指定我們以非同步方式執行](https://eloquentjavascript.net/11_async.html#h-XvLsfAhtsE)。 呼叫區塊鏈,即使是唯讀操作,也需要電腦和區塊鏈節點之間的來回通訊。 這就是為什麼我們在這裡指定程式碼需要 `await` 結果的原因。 + +如果您對其運作方式感興趣,可以[在此處閱讀相關內容](https://www.w3schools.com/js/js_promise.asp),但實際上您只需要知道,如果您啟動一個耗時較長的操作,就需要 `await` 結果,並且任何執行此操作的函式都必須宣告為 `async`。 + +##### 發行交易 + +```typescript +const setGreeting = async (greeting: string): Promise => { +``` + +這就是您呼叫來發行一筆更改問候語的交易的函式。 由於這是一個耗時較長的操作,該函式被宣告為 `async`。 由於內部實作,任何 `async` 函式都需要傳回一個 `Promise` 物件。 在這種情況下,`Promise` 表示我們沒有指定 `Promise` 中確切會傳回什麼。 + +```typescript +const txHash = await greeter.write.setGreeting([greeting]) +``` + +合約實例的 `write` 欄位包含所有寫入區塊鏈狀態的函式(那些需要傳送交易的函式),例如 [`setGreeting`](https://eth-holesky.blockscout.com/address/0xB8f6460Dc30c44401Be26B0d6eD250873d8a50A6?tab=write_contract#a4136862)。 參數(如果有的話)以清單形式提供,函式會傳回交易的哈希。 + +```typescript + console.log(`正在修復中,請參閱 https://eth-holesky.blockscout.com/tx/${txHash}`) + + return txHash +} +``` + +報告交易的哈希(作為查看它的區塊瀏覽器 URL 的一部分)並傳回它。 + +##### 回應事件 + +```typescript +greeter.watchEvent.SetGreeting({ +``` + +[`watchEvent` 函式](https://viem.sh/docs/actions/public/watchEvent) 讓您指定在發出事件時要執行的函式。 如果您只關心一種事件類型(在此例中為 `SetGreeting`),您可以使用此語法將自己限制在該事件類型。 + +```typescript + onLogs: logs => { +``` + +當有日誌項目時,`onLogs` 函式會被呼叫。 在以太坊中,「日誌」和「事件」通常可以互換使用。 + +```typescript +console.log( + `地址 ${logs[0].args.sender} 已將問候語變更為 ${logs[0].args.greeting}` +) +``` + +可能有多個事件,但為簡單起見,我們只關心第一個。 `logs[0].args` 是事件的引數,在此例中為 `sender` 和 `greeting`。 + +```typescript + if (logs[0].args.sender != account.address) + setGreeting(`${account.address} insists on it being Hello!`) + } +}) +``` + +如果傳送者_不是_這個伺服器,請使用 `setGreeting` 來更改問候語。 + +#### `package.json` {#package-json} + +[這個檔案](https://github.com/qbzzt/20240715-server-component/blob/main/package.json) 控制 [Node.js](https://nodejs.org/en) 的設定。 本文僅解釋重要的定義。 + +```json +{ + "main": "dist/index.js", +``` + +這個定義指定了要執行的 JavaScript 檔案。 + +```json + "scripts": { + "start": "tsc && node dist/app.js", + }, +``` + +指令碼是各種應用程式操作。 在這種情況下,我們只有 `start`,它會編譯然後執行伺服器。 `tsc` 命令是 `typescript` 套件的一部分,可將 TypeScript 編譯成 JavaScript。 如果您想手動執行,它位於 `node_modules/.bin`。 第二個命令執行伺服器。 + +```json + "type": "module", +``` + +JavaScript 節點應用程式有多種類型。 `module` 類型讓我們可以在頂層程式碼中使用 `await`,這在您執行慢速(也就是非同步)操作時很重要。 + +```json + "devDependencies": { + "@types/node": "^20.14.2", + "typescript": "^5.4.5" + }, +``` + +這些是僅在開發時需要的套件。 在這裡我們需要 `typescript`,而且因為我們將它與 Node.js 一起使用,我們也需要取得節點變數和物件的類型,例如 `process`。 [`^` 符號](https://github.com/npm/node-semver?tab=readme-ov-file#caret-ranges-123-025-004) 表示該版本或沒有破壞性變更的更高版本。 有關版本號碼含義的更多資訊,請參閱[此處](https://semver.org)。 + +```json + "dependencies": { + "dotenv": "^16.4.5", + "viem": "2.14.1" + } +} +``` + +這些是在執行 `dist/app.js` 時,在執行期間所需的套件。 + +## 結論 {#conclusion} + +我們在這裡建立的中心化伺服器完成了它的工作,即作為使用者的代理。 任何其他希望去中心化應用程式繼續運作並願意花費 gas 的人,都可以用自己的地址執行一個新的伺服器實例。 + +然而,這僅在中心化伺服器的操作可以輕鬆驗證時才有效。 如果中心化伺服器擁有任何秘密狀態資訊,或執行困難的計算,那麼它就是一個您需要信任才能使用該應用程式的中心化實體,而這正是區塊鏈試圖避免的。 在未來的文章中,我計畫展示如何使用[零知識證明](/zero-knowledge-proofs)來解決這個問題。 + +[在此查看我的更多作品](https://cryptodocguy.pro/)。 diff --git a/public/content/translations/zh-tw/developers/tutorials/set-up-web3js-to-use-ethereum-in-javascript/index.md b/public/content/translations/zh-tw/developers/tutorials/set-up-web3js-to-use-ethereum-in-javascript/index.md new file mode 100644 index 00000000000..d7e55910151 --- /dev/null +++ b/public/content/translations/zh-tw/developers/tutorials/set-up-web3js-to-use-ethereum-in-javascript/index.md @@ -0,0 +1,92 @@ +--- +title: "在 JavaScript 中設定 web3.js 以使用以太坊區塊鏈" +description: "了解如何設定與配置 web3.js 函式庫,以便從 JavaScript 應用程式與以太坊區塊鏈互動。" +author: "jdourlens" +tags: [ "web3.js", "javascript" ] +skill: beginner +lang: zh-tw +published: 2020-04-11 +source: EthereumDev +sourceUrl: https://ethereumdev.io/setup-web3js-to-use-the-ethereum-blockchain-in-javascript/ +address: "0x19dE91Af973F404EDF5B4c093983a7c6E3EC8ccE" +--- + +在本教學中,我們將了解如何開始使用 [web3.js](https://web3js.readthedocs.io/) 來與以太坊區塊鏈互動。 Web3.js 可同時用於前端和後端,以從區塊鏈讀取資料、進行交易,甚至部署智能合約。 + +第一步是將 web3.js 引入您的專案。 若要在網頁中使用,您可以使用像 JSDeliver 這類的 CDN 來直接匯入該函式庫。 + +```html + +``` + +如果您偏好安裝函式庫,以便在後端或使用建置工具的前端專案中使用,您可以使用 npm 來安裝: + +```bash +npm install web3 --save +``` + +然後,要將 Web3.js 匯入 Node.js 指令稿或 Browserify 前端專案,您可以使用下面這行 JavaScript 程式碼: + +```js +const Web3 = require("web3") +``` + +現在我們已經將函式庫引入專案中,接著需要將其初始化。 您的專案需要能夠與區塊鏈通訊。 大多數以太坊函式庫透過 RPC 呼叫與[節點](/developers/docs/nodes-and-clients/)通訊。 為了啟動我們的 Web3 供應商,我們將實例化一個 Web3 執行個體,並將供應商的 URL 作為建構函式傳遞。 如果您電腦上有執行中的節點或 [ganache 執行個體](https://ethereumdev.io/testing-your-smart-contract-with-existing-protocols-ganache-fork/),它會看起來像這樣: + +```js +const web3 = new Web3("http://localhost:8545") +``` + +如果您想直接存取託管節點,可以在[節點即服務](/developers/docs/nodes-and-clients/nodes-as-a-service)中找到選項。 + +```js +const web3 = new Web3("https://cloudflare-eth.com") +``` + +為了測試我們是否正確地設定了 Web3 執行個體,我們將嘗試使用 `getBlockNumber` 函式來擷取最新的區塊號碼。 此函式接受一個回呼作為參數,並以整數形式傳回區塊號碼。 + +```js +var Web3 = require("web3") +const web3 = new Web3("https://cloudflare-eth.com") + +web3.eth.getBlockNumber(function (error, result) { + console.log(result) +}) +``` + +如果您執行此程式,它將只會印出最新的區塊號碼:也就是區塊鏈的頂端。 您也可以使用 `await/async` 函式呼叫,以避免在程式碼中出現巢狀回呼: + +```js +async function getBlockNumber() { + const latestBlockNumber = await web3.eth.getBlockNumber() + console.log(latestBlockNumber) + return latestBlockNumber +} + +getBlockNumber() +``` + +您可以在 [web3.js 官方文件](https://docs.web3js.org/)中,查看 Web3 執行個體上所有可用的函式。 + +大多數 Web3 函式庫都是非同步的,因為函式庫會在背景對節點進行 JSON-RPC 呼叫,而節點會傳回結果。 + + + +如果您是在瀏覽器中作業,有些錢包會直接注入一個 Web3 執行個體,您應該盡可能使用它,特別是當您打算與使用者的以太坊地址互動以進行交易時。 + +以下是偵測 MetaMask 錢包是否可用,並在可用時嘗試啟用的程式碼片段。 之後,您便能讀取使用者的餘額,並讓他們驗證您希望他們在以太坊區塊鏈上進行的交易: + +```js +if (window.ethereum != null) { + state.web3 = new Web3(window.ethereum) + try { + // 如有需要,請求帳戶存取權限 + await window.ethereum.enable() + // 帳戶現已公開 + } catch (error) { + // 使用者拒絕帳戶存取權限... + } +} +``` + +web3.js 也有替代方案,例如 [Ethers.js](https://docs.ethers.io/),而且也相當常用。 在下一個教學中,我們將會了解[如何在區塊鏈上輕鬆地監聽新傳入的區塊,並查看其內容](https://ethereumdev.io/listening-to-new-transactions-happening-on-the-blockchain/)。 diff --git a/public/content/translations/zh-tw/developers/tutorials/short-abi/index.md b/public/content/translations/zh-tw/developers/tutorials/short-abi/index.md new file mode 100644 index 00000000000..9127e08b7b9 --- /dev/null +++ b/public/content/translations/zh-tw/developers/tutorials/short-abi/index.md @@ -0,0 +1,585 @@ +--- +title: "為優化 Calldata 而設的精簡 ABI" +description: "為樂觀卷軸優化智能合約" +author: Ori Pomerantz +lang: zh-tw +tags: [ "Layer 2" ] +skill: intermediate +published: 2022-04-01 +--- + +## 介紹 {#introduction} + +在本文中,您將學習[樂觀卷軸](/developers/docs/scaling/optimistic-rollups),了解其交易成本,以及這種不同的成本結構如何要求我們針對不同於以太坊主網的事物進行優化。 +您也將學習如何實作此項優化。 + +### 利益揭露 {#full-disclosure} + +我是 [Optimism](https://www.optimism.io/) 的全職員工,因此本文中的範例將在 Optimism 上運行。 +然而,此處說明的技術也應同樣適用於其他卷軸。 + +### 術語 {#terminology} + +在討論卷軸時,術語「第 1 層」(L1) 用於指代主網,也就是正式運作的以太坊網路。 +術語「第 2 層」(L2) 用於指代卷軸或任何其他依賴 L1 安全性,但其大部分處理在鏈下進行的系統。 + +## 如何能進一步降低 L2 交易的成本? {#how-can-we-further-reduce-the-cost-of-L2-transactions} + +[樂觀卷軸](/developers/docs/scaling/optimistic-rollups) 必須保存每筆歷史交易的紀錄,以便任何人都能夠檢查它們並驗證當前狀態是否正確。 +將資料寫入以太坊主網最便宜的方法,是將其寫為 calldata。 +[Optimism](https://help.optimism.io/hc/en-us/articles/4413163242779-What-is-a-rollup-) 和 [Arbitrum](https://developer.offchainlabs.com/docs/rollup_basics#intro-to-rollups) 都選擇了這個解決方案。 + +### L2 交易的成本 {#cost-of-l2-transactions} + +L2 交易的成本由兩部分組成: + +1. L2 處理,通常非常便宜 +2. L1 儲存,與主網的 gas 費用相關 + +在我撰寫本文時,Optimism 上的 L2 gas 成本為 0.001 [Gwei](/developers/docs/gas/#pre-london)。 +另一方面,L1 的 gas 成本約為 40 gwei。 +[您可以在此處查看目前價格](https://public-grafana.optimism.io/d/9hkhMxn7z/public-dashboard?orgId=1&refresh=5m)。 + +一個 calldata 位元組的成本為 4 gas (如果其值為零) 或 16 gas (如果其值為任何其他值)。 +在 EVM 上最昂貴的操作之一是寫入儲存空間。 +在 L2 上將一個 32 位元組的字詞寫入儲存空間的最高成本為 22100 gas。 目前,這相當於 22.1 gwei。 +因此,如果我們能節省一個 calldata 的零位元組,我們將能寫入約 200 個位元組到儲存空間,而且仍然划算。 + +### ABI {#the-abi} + +大多數交易從外部帳戶存取合約。 +大多數合約都是用 Solidity 編寫的,並根據[應用程式二進位介面 (ABI)](https://docs.soliditylang.org/en/latest/abi-spec.html#formal-specification-of-the-encoding) 來解譯其資料欄位。 + +然而,ABI 是為 L1 設計的,在 L1 上,一個 calldata 位元組的成本約等於四次算術運算,但在 L2 上,一個 calldata 位元組的成本超過一千次算術運算。 +calldata 的劃分如下: + +| 部分 | 長度 | 位元組 | 浪費的位元組 | 浪費的 gas | 必要的位元組 | 必要的 gas | +| ----- | -: | ----: | -----: | ------: | -----: | ------: | +| 函式選擇器 | 4 | 0-3 | 3 | 48 | 1 | 16 | +| 零值 | 12 | 4-15 | 12 | 48 | 0 | 0 | +| 目標地址 | 20 | 16-35 | 0 | 0 | 20 | 320 | +| 數量 | 32 | 36-67 | 17 | 64 | 15 | 240 | +| 總計 | 68 | | | 160 | | 576 | + +說明: + +- **函式選擇器**:合約的函式少於 256 個,因此我們可以用單一位元組來區分它們。 + 這些位元組通常為非零值,因此[成本為 16 gas](https://eips.ethereum.org/EIPS/eip-2028)。 +- **零值**:這些位元組永遠為零,因為一個 20 位元組的地址並不需要一個 32 位元組的字詞來儲存它。 + 儲存零值的位元組成本為 4 gas ([請參閱黃皮書](https://ethereum.github.io/yellowpaper/paper.pdf),附錄 G, + 第 27 頁,`G``txdatazero` 的值)。 +- **數量**:如果我們假設在此合約中 `decimals` 為 18 (正常值),而我們轉帳的代幣最大數量為 1018,那我們得到的最大數量就是 1036。 + 25615 > 1036,所以 15 個位元組就夠了。 + +在 L1 上浪費 160 gas 通常可以忽略不計。 一筆交易至少花費 [21,000 gas](https://yakkomajuri.medium.com/blockchain-definition-of-the-week-ethereum-gas-2f976af774ed),所以額外的 0.8% 無關緊要。 +然而,在 L2 上,情況就不同了。 幾乎整筆交易的成本都花在將其寫入 L1。 +除了交易的 calldata,還有 109 個位元組的交易標頭 (目標地址、簽名等)。 +因此總成本為 `109*16+576+160=2480`,我們浪費了其中約 6.5%。 + +## 在您無法控制目標的情況下降低成本 {#reducing-costs-when-you-dont-control-the-destination} + +假設您無法控制目標合約,您仍然可以使用類似[這個](https://github.com/qbzzt/ethereum.org-20220330-shortABI)的解決方案。 +讓我們來看看相關的檔案。 + +### Token.sol {#token-sol} + +[這是目標合約](https://github.com/qbzzt/ethereum.org-20220330-shortABI/blob/master/contracts/Token.sol)。 +它是一個標準的 ERC-20 合約,帶有一個額外的功能。 +這個 `faucet` 函式讓任何使用者都能取得一些代幣來使用。 +它會讓一個生產環境的 ERC-20 合約變得無用,但當 ERC-20 只是為了方便測試而存在時,它能讓事情變得更簡單。 + +```solidity + /** + * @dev 讓呼叫者獲得 1000 個代幣使用 + */ + function faucet() external { + _mint(msg.sender, 1000); + } // function faucet +``` + +### CalldataInterpreter.sol {#calldatainterpreter-sol} + +[這個合約是預期交易會用較短的 calldata 來呼叫的合約](https://github.com/qbzzt/ethereum.org-20220330-shortABI/blob/master/contracts/CalldataInterpreter.sol)。 +讓我們逐行檢視它。 + +```solidity +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + + +import { OrisUselessToken } from "./Token.sol"; +``` + +我們需要匯入代幣合約,才能知道如何呼叫它的函式。 + +```solidity +contract CalldataInterpreter { + + OrisUselessToken public immutable token; +``` + +我們作為代理的代幣地址。 + +```solidity + + /** + * @dev 指定代幣地址 + * @param tokenAddr_ ERC-20 合約地址 + */ + constructor( + address tokenAddr_ + ) { + token = OrisUselessToken(tokenAddr_); + } // constructor +``` + +代幣地址是我們唯一需要指定的參數。 + +```solidity + function calldataVal(uint startByte, uint length) + private pure returns (uint) { +``` + +從 calldata 讀取一個值。 + +```solidity + uint _retVal; + + require(length < 0x21, + "calldataVal 長度限制為 32 位元組"); + + require(length + startByte <= msg.data.length, + "calldataVal 嘗試讀取超過 calldatasize"); +``` + +我們將載入一個 32 位元組 (256 位元) 的字詞到記憶體中,並移除不屬於我們所需欄位的位元組。 +這個演算法不適用於長度超過 32 位元組的值,當然我們也不能讀取超過 calldata 結尾的內容。 +在 L1 上,可能需要跳過這些測試以節省 gas,但在 L2 上 gas 非常便宜,這使得我們可以進行任何能想到的健全性檢查。 + +```solidity + assembly { + _retVal := calldataload(startByte) + } +``` + +我們本可以從對 `fallback()` 的呼叫中複製資料 (見下文),但使用 [Yul](https://docs.soliditylang.org/en/v0.8.12/yul.html) (EVM 的組合語言) 會更容易。 + +這裡我們使用 [CALLDATALOAD 操作碼](https://www.evm.codes/#35) 將從 `startByte` 到 `startByte+31` 的位元組讀入堆疊。 +一般來說,Yul 中操作碼的語法是 `<操作碼名稱>(<第一個堆疊值,如果有的話>,<第二個堆疊值,如果有的話>...)`。 + +```solidity + + _retVal = _retVal >> (256-length*8); +``` + +只有最高有效位的 `length` 個位元組是欄位的一部分,所以我們[向右移位](https://en.wikipedia.org/wiki/Logical_shift)以去除其他值。 +這還有一個額外的好處,就是將值移動到欄位的右側,這樣它就是值本身,而不是值乘以 256 的某次方。 + +```solidity + + return _retVal; + } + + + fallback() external { +``` + +當對 Solidity 合約的呼叫不符合任何函式簽章時,它會呼叫[ `fallback()` 函式](https://docs.soliditylang.org/en/v0.8.12/contracts.html#fallback-function) (假設存在的話)。 +對於 `CalldataInterpreter`,_任何_ 呼叫都會到達這裡,因為沒有其他 `external` 或 `public` 函式。 + +```solidity + uint _func; + + _func = calldataVal(0, 1); +``` + +讀取 calldata 的第一個位元組,它會告訴我們是哪個函式。 +函式可能無法在此處使用的原因有二: + +1. 為 `pure` 或 `view` 的函式不會改變狀態,也不會花費 gas (在鏈下呼叫時)。 + 試圖降低它們的 gas 成本沒有意義。 +2. 依賴 [`msg.sender`](https://docs.soliditylang.org/en/v0.8.12/units-and-global-variables.html#block-and-transaction-properties) 的函式。 + `msg.sender` 的值將是 `CalldataInterpreter` 的地址,而不是呼叫者的地址。 + +遺憾的是,[查看 ERC-20 規範](https://eips.ethereum.org/EIPS/eip-20)後,只剩下一個函式,`transfer`。 +這讓我們只剩下兩個函式:`transfer` (因為我們可以呼叫 `transferFrom`) 和 `faucet` (因為我們可以將代幣轉回給呼叫我們的人)。 + +```solidity + + // 使用來自 calldata 的資訊 + // 呼叫代幣的狀態變更方法 + + // faucet + if (_func == 1) { +``` + +對 `faucet()` 的呼叫,它沒有參數。 + +```solidity + token.faucet(); + token.transfer(msg.sender, + token.balanceOf(address(this))); + } +``` + +在我們呼叫 `token.faucet()` 之後,我們會得到代幣。 然而,作為代理合約,我們並**不**需要代幣。 +呼叫我們的外部擁有帳戶 (EOA) 或合約才需要。 +所以我們將我們所有的代幣轉給呼叫我們的人。 + +```solidity + // transfer (假設我們有權限這麼做) + if (_func == 2) { +``` + +轉帳代幣需要兩個參數:目標地址和數量。 + +```solidity + token.transferFrom( + msg.sender, +``` + +我們只允許呼叫者轉帳他們擁有的代幣 + +```solidity + address(uint160(calldataVal(1, 20))), +``` + +目標地址從位元組 #1 開始 (位元組 #0 是函式)。 +作為一個地址,它的長度是 20 位元組。 + +```solidity + calldataVal(21, 2) +``` + +對於這個特定的合約,我們假設任何人都想轉帳的代幣最大數量可以用兩個位元組表示 (小於 65536)。 + +```solidity + ); + } +``` + +總體而言,一次轉帳需要 35 個位元組的 calldata: + +| 部分 | 長度 | 位元組 | +| ----- | -: | ----: | +| 函式選擇器 | 1 | 0 | +| 目標地址 | 32 | 1-32 | +| 數量 | 2 | 33-34 | + +```solidity + } // fallback + +} // contract CalldataInterpreter +``` + +### test.js {#test-js} + +[這個 JavaScript 單元測試](https://github.com/qbzzt/ethereum.org-20220330-shortABI/blob/master/test/test.js) 向我們展示了如何使用這個機制 (以及如何驗證它能正確運作)。 +我將假設您了解 [chai](https://www.chaijs.com/) 和 [ethers](https://docs.ethers.io/v5/),並只解釋專門適用於該合約的部分。 + +```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() +``` + +我們首先部署這兩個合約。 + +```javascript + // 取得代幣來使用 + const faucetTx = { +``` + +我們不能使用我們通常會使用的高階函式 (例如 `token.faucet()`) 來建立交易,因為我們沒有遵循 ABI。 +相反地,我們必須自己建立交易然後再傳送它。 + +```javascript + to: cdi.address, + data: "0x01" +``` + +我們需要為交易提供兩個參數: + +1. `to`,目標地址。 + 這是 calldata 解譯器合約。 +2. `data`,要傳送的 calldata。 + 在 `faucet` 呼叫的情況下,資料是單一位元組 `0x01`。 + +```javascript + + } + await (await signer.sendTransaction(faucetTx)).wait() +``` + +我們呼叫[簽署者的 `sendTransaction` 方法](https://docs.ethers.io/v5/api/signer/#Signer-sendTransaction),因為我們已經指定了目標 (`faucetTx.to`) 且我們需要交易被簽署。 + +```javascript +// 檢查 faucet 是否正確提供代幣 +expect(await token.balanceOf(signer.address)).to.equal(1000) +``` + +在這裡我們驗證餘額。 +`view` 函式不需要節省 gas,所以我們就正常執行它們。 + +```javascript +// 給予 CDI 額度 (核准無法被代理) +const approveTX = await token.approve(cdi.address, 10000) +await approveTX.wait() +expect(await token.allowance(signer.address, cdi.address)).to.equal(10000) +``` + +給予 calldata 解譯器一個額度以便能夠進行轉帳。 + +```javascript +// 轉帳代幣 +const destAddr = "0xf5a6ead936fb47f342bb63e676479bddf26ebe1d" +const transferTx = { + to: cdi.address, + data: "0x02" + destAddr.slice(2, 42) + "0100", +} +``` + +建立一個轉帳交易。 第一個位元組是「0x02」,接著是目標地址,最後是數量 (0x0100,十進位為 256)。 + +```javascript + await (await signer.sendTransaction(transferTx)).wait() + + // 檢查我們是否少了 256 個代幣 + expect (await token.balanceOf(signer.address)).to.equal(1000-256) + + // 以及我們的目標是否收到了它們 + expect (await token.balanceOf(destAddr)).to.equal(256) + }) // it +}) // describe +``` + +## 在您能控制目標合約的情況下降低成本 {#reducing-the-cost-when-you-do-control-the-destination-contract} + +如果您確實能控制目標合約,您可以建立繞過 `msg.sender` 檢查的函式,因為它們信任 calldata 解譯器。 +[您可以在這裡的 `control-contract` 分支中看到一個範例](https://github.com/qbzzt/ethereum.org-20220330-shortABI/tree/control-contract)。 + +如果合約只回應外部交易,我們只需要一個合約就夠了。 +然而,那會破壞[可組合性](/developers/docs/smart-contracts/composability/)。 +比較好的做法是,讓一個合約回應正常的 ERC-20 呼叫,另一個合約回應帶有短呼叫資料的交易。 + +### Token.sol {#token-sol-2} + +在這個範例中,我們可以修改 `Token.sol`。 +這讓我們可以擁有一些只有代理才能呼叫的函式。 +以下是新的部分: + +```solidity + // 唯一允許指定 CalldataInterpreter 地址的地址 + address owner; + + // CalldataInterpreter 地址 + address proxy = address(0); +``` + +ERC-20 合約需要知道授權代理的身分。 +但是,我們無法在建構函式中設定此變數,因為我們還不知道它的值。 +這個合約會先被實例化,因為代理在其建構函式中需要代幣的地址。 + +```solidity + /** + * @dev 呼叫 ERC20 建構函式。 + */ + constructor( + ) ERC20("Oris useless token-2", "OUT-2") { + owner = msg.sender; + } +``` + +創建者的地址 (稱為 `owner`) 儲存在這裡,因為這是唯一允許設定代理的地址。 + +```solidity + /** + * @dev 設定代理 (CalldataInterpreter) 的地址。 + * 只能由 owner 呼叫一次 + */ + function setProxy(address _proxy) external { + require(msg.sender == owner, "只能由 owner 呼叫"); + require(proxy == address(0), "代理已設定"); + + proxy = _proxy; + } // function setProxy +``` + +代理具有特權存取權限,因為它可以繞過安全性檢查。 +為確保我們可以信任代理,我們只讓 `owner` 呼叫此函式,而且只能呼叫一次。 +一旦 `proxy` 有了真實的值 (非零),該值就無法改變,所以即使 owner 決定變壞,或者其助記詞被洩露,我們仍然是安全的。 + +```solidity + /** + * @dev 有些函式可能只能由代理呼叫。 + */ + modifier onlyProxy { +``` + +這是一個 [`modifier` 函式](https://www.tutorialspoint.com/solidity/solidity_function_modifiers.htm),它會修改其他函式的運作方式。 + +```solidity + require(msg.sender == proxy); +``` + +首先,驗證我們是被代理呼叫的,而不是其他人。 +如果不是,則 `revert`。 + +```solidity + _; + } +``` + +如果是,則執行我們修改的函式。 + +```solidity + /* 允許代理實際為帳戶進行代理的函式 */ + + 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; + } +``` + +這三項操作通常要求訊息直接來自轉移代幣或批准額度的實體。 +此處,我們有一個代理版本來執行這些操作,此代理: + +1. 由 `onlyProxy()` 修改,因此不允許任何其他人控制它們。 +2. 將通常是 `msg.sender` 的地址作為額外參數。 + +### CalldataInterpreter.sol {#calldatainterpreter-sol-2} + +此 calldata 解譯器幾乎與上面的解釋器相同,只是被代理的函式會接收 `msg.sender` 參數,且 `transfer` 不需要額度。 + +```solidity + // transfer (不需額度) + if (_func == 2) { + token.transferProxy( + msg.sender, + address(uint160(calldataVal(1, 20))), + calldataVal(21, 2) + ); + } + + // approve + 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} + +前面的測試程式碼和這段程式碼之間有一些變化。 + +```js +const Cdi = await ethers.getContractFactory("CalldataInterpreter") +const cdi = await Cdi.deploy(token.address) +await cdi.deployed() +await token.setProxy(cdi.address) +``` + +我們需要告訴 ERC-20 合約要信任哪個代理 + +```js +console.log("CalldataInterpreter addr:", cdi.address) + +// 需要兩個簽署者來驗證額度 +const signers = await ethers.getSigners() +const signer = signers[0] +const poorSigner = signers[1] +``` + +要檢查 `approve()` 和 `transferFrom()`,我們需要第二個簽署者。 +我們稱它為 `poorSigner`,因為它不會得到我們的任何代幣 (當然它確實需要有 ETH)。 + +```js +// 轉帳代幣 +const destAddr = "0xf5a6ead936fb47f342bb63e676479bddf26ebe1d" +const transferTx = { + to: cdi.address, + data: "0x02" + destAddr.slice(2, 42) + "0100", +} +await (await signer.sendTransaction(transferTx)).wait() +``` + +因為 ERC-20 合約信任代理 (`cdi`),所以我們不需要額度來中繼轉帳。 + +```js +// 核准與 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() + +// 檢查 approve / transferFrom 組合是否正確完成 +expect(await token.balanceOf(destAddr2)).to.equal(255) +``` + +測試這兩個新函式。 +請注意,`transferFromTx` 需要兩個地址參數:額度的給予者和接收者。 + +## 結論 {#conclusion} + +[Optimism](https://medium.com/ethereum-optimism/the-road-to-sub-dollar-transactions-part-2-compression-edition-6bb2890e3e92) 和 [Arbitrum](https://developer.offchainlabs.com/docs/special_features) 都在尋找方法來減少寫入 L1 的 calldata 大小,從而降低交易成本。 +然而,作為尋求通用解決方案的基礎設施提供商,我們的能力有限。 +身為去中心化應用程式開發者,您擁有應用程式特定的知識,這讓您能比我們在通用解決方案中更有效地優化您的 calldata。 +希望本文能幫助您找到滿足您需求的理想解決方案。 + +[在此查看我的更多作品](https://cryptodocguy.pro/)。 + diff --git a/public/content/translations/zh-tw/developers/tutorials/smart-contract-security-guidelines/index.md b/public/content/translations/zh-tw/developers/tutorials/smart-contract-security-guidelines/index.md new file mode 100644 index 00000000000..583ef8275a5 --- /dev/null +++ b/public/content/translations/zh-tw/developers/tutorials/smart-contract-security-guidelines/index.md @@ -0,0 +1,91 @@ +--- +title: "智慧型合約安全指南" +description: "一安全指南列表來介紹如何考慮安全當建立你個人Dapp" +author: "Trailofbits" +tags: [ "穩固", "智能合約", "安全性" ] +skill: intermediate +lang: zh-tw +published: 2020-09-06 +source: Building secure contracts +sourceUrl: https://github.com/crytic/building-secure-contracts/blob/master/development-guidelines/guidelines.md +--- + +遵照以下高階推薦來建立一更加安全之智慧型合約. + +## 設計準則 {#design-guidelines} + +智慧型合約設計應被事前討論, 遠早於任何程式被編輯前. + +### 文件與規範 {#documentation-and-specifications} + +文檔能被編輯於多重等級, 並需備更新當被導入合約時: + +- **以淺顯易懂的英文描述系統**,說明合約的功能以及對程式碼庫的任何假設。 +- **綱要與架構圖**,包括合約互動和系統的狀態機器。 [Slither printers](https://github.com/crytic/slither/wiki/Printer-documentation) 可協助產生這些綱要。 +- **詳盡的程式碼文件**,Solidity 可使用 [Natspec 格式](https://docs.soliditylang.org/en/develop/natspec-format.html)。 + +### 鏈上與鏈外運算 {#onchain-vs-offchain-computation} + +- \*\*盡可能將程式碼保持在鏈外。\*\*保持鏈上層的精簡。 以鏈外程式碼預先處理資料,使鏈上驗證更簡單。 你需要一採購列表嗎? 排列鏈下列表, 並只查看先前老舊之鏈上資訊. + +### 可升級性 {#upgradeability} + +我們在[我們的部落格文章](https://blog.trailofbits.com/2018/09/05/contract-upgrade-anti-patterns/)中討論了不同的可升級性解決方案。 編輯程式前, 先意識決定來支持或非支持此更新能力. 這個決定將會影響您架構程式碼的方式。 通常上來說, 我們推薦: + +- \*\*優先選擇[合約遷移](https://blog.trailofbits.com/2018/10/29/how-contract-migration-works/),而非可升級性。\*\*遷移系統擁有許多與可升級系統相同的優點,但沒有其缺點。 +- \*\*使用資料分離模式,而非 delegatecallproxy 模式。\*\*如果您的專案有清楚的抽象分離,使用資料分離的可升級性將只需要少許調整。 Delegatecallproxy 系統要求EVM專家, 且其具高錯誤率. +- \*\*在部署前記錄遷移/升級程序。\*\*如果您必須在沒有任何指導方針的壓力下做出反應,就會犯錯。 事前編輯一程式步驟. 此應包含: + - 一調用來展開一新合約 + - 鑰鍵儲存場所及入手方式 + - 解釋如何查看部署! 開發並測試一部署後腳本 + +## 實作準則 {#implementation-guidelines} + +\*\*力求簡單。\*\*永遠使用符合您目的最簡單的解決方案。 團隊任何成員應全員了解你的方案. + +### 函式組合 {#function-composition} + +你數據庫之構成結構應使程式能被簡單閱讀. 避免程式結構降低閱讀正確率. + +- **分割您系統的邏輯**,可以透過多個合約或將相似的函式分組(例如,驗證、算術...)來達成。 +- \*\*撰寫目的明確的小函式。\*\*這將有助於簡化審查,並允許對個別組件進行測試。 + +### 繼承 {#inheritance} + +- \*\*保持繼承的可管理性。\*\*繼承應用來分割邏輯,但是,您的專案應致力於最小化繼承樹的深度和廣度。 +- \*\*使用 Slither 的 [inheritance printer](https://github.com/crytic/slither/wiki/Printer-documentation#inheritance-graph) 檢查合約的層次結構。\*\*inheritance printer 將幫助您檢視層次結構的大小。 + +### Events {#events} + +- \*\*記錄所有關鍵操作。\*\*事件將有助於在開發期間對合約進行偵錯,並在部署後對其進行監控。 + +### 避免已知的陷阱 {#avoid-known-pitfalls} + +- \*\*注意最常見的安全性問題。\*\*有許多線上資源可以學習常見問題,例如 [Ethernaut CTF](https://ethernaut.openzeppelin.com/)、[Capture the Ether](https://capturetheether.com/) 或 [Not so smart contracts](https://github.com/crytic/not-so-smart-contracts/)。 +- \*\*注意 [Solidity 文件](https://docs.soliditylang.org/en/latest/)中的警告部分。\*\*警告部分會告知您該語言中不甚明顯的行為。 + +### 相依性 {#dependencies} + +- \*\*使用經過良好測試的程式庫。\*\*從經過良好測試的程式庫匯入程式碼將降低您編寫有錯誤程式碼的可能性。 如果您想編寫 ERC20 合約,請使用 [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC20)。 +- \*\*使用相依性管理員;避免複製貼上程式碼。\*\*如果您依賴外部來源,那麼您必須使其與原始來源保持同步。 + +### 測試與驗證 {#testing-and-verification} + +- \*\*撰寫詳盡的單元測試。\*\*一個廣泛的測試套件對於建構高品質軟體至關重要。 +- \*\*撰寫 [Slither](https://github.com/crytic/slither)、[Echidna](https://github.com/crytic/echidna) 和 [Manticore](https://github.com/trailofbits/manticore) 的自訂檢查和屬性。\*\*自動化工具將有助於確保您的合約安全。 回顧此指南其他部分來學習如何編輯一有效查看及屬性. +- **使用 [crytic.io](https://crytic.io/)。** Crytic 與 GitHub 整合,提供對私有 Slither 偵測器的存取權限,並從 Echidna 執行自訂屬性檢查。 + +### Solidity {#solidity} + +- \*\*優先使用 Solidity 0.5,而非 0.4 和 0.6。\*\*在我們看來,Solidity 0.5 比 0.4 更安全,並具有更好的內建實踐。 Solidity 0.6已被證實其於實際生成還未安全, 並需要些時間來發展成熟. +- \*\*使用穩定版本進行編譯;使用最新版本檢查警告。\*\*請檢查您的程式碼在最新的編譯器版本中沒有回報任何問題。 然而,Solidity 的發佈週期很快,且有編譯器錯誤的歷史,因此我們不建議使用最新版本進行部署(請參閱 Slither 的 [solc 版本建議](https://github.com/crytic/slither/wiki/Detector-Documentation#recommendation-33))。 +- \*\*不要使用內嵌組合語言。\*\*組合語言需要 EVM 專業知識。 如果您還沒有_精通_黃皮書,請不要編寫 EVM 程式碼。 + +## 部署準則 {#deployment-guidelines} + +一旦合約被開發及部署: + +- \*\*監控您的合約。\*\*觀察日誌,並準備好在合約或錢包遭到入侵時做出反應。 +- \*\*將您的聯絡資訊新增至 [blockchain-security-contacts](https://github.com/crytic/blockchain-security-contacts)。\*\*如果發現安全性漏洞,此列表有助於第三方與您聯絡。 +- \*\*保護特權使用者的錢包。\*\*如果您將金鑰儲存在硬體錢包中,請遵循我們的[最佳實踐](https://blog.trailofbits.com/2018/11/27/10-rules-for-the-secure-use-of-cryptocurrency-hardware-wallets/)。 +- \*\*制定事件應變計畫。\*\*請考慮到您的智慧合約可能會受到損害。 即使合約本身無任何bug, 一攻擊者還是可能嘗試掌控合約持有者之密鑰. diff --git a/public/content/translations/zh-tw/developers/tutorials/stealth-addr/index.md b/public/content/translations/zh-tw/developers/tutorials/stealth-addr/index.md new file mode 100644 index 00000000000..7ff35821895 --- /dev/null +++ b/public/content/translations/zh-tw/developers/tutorials/stealth-addr/index.md @@ -0,0 +1,436 @@ +--- +title: "使用隱匿地址" +description: "隱匿地址讓使用者能夠匿名轉移資產。 閱讀本文後,您將能夠:解釋什麼是隱匿地址及其運作方式、瞭解如何以維護匿名性的方式使用隱匿地址,以及編寫使用隱匿地址的網頁應用程式。" +author: Ori Pomerantz +tags: [ "隱匿地址", "隱私", "密碼學", "rust", "wasm" ] +skill: intermediate +published: 2025-11-30 +lang: zh-tw +sidebarDepth: 3 +--- + +您是 Bill。 我們不深入探討原因,假設您想捐款給「Alice 競選世界女王」活動,並希望 Alice 知道您捐了款,以便在她勝選後給予您酬勞。 可惜,她不保證會勝選。 有個競爭活動:「Carol 競選太陽系女皇」。 如果 Carol 勝選,而且她發現您捐款給 Alice,您就會有麻煩了。 所以您不能直接從您的帳戶轉 200 ETH 給 Alice。 + +[ERC-5564](https://eips.ethereum.org/EIPS/eip-5564) 提供了這個問題的解決辦法。 此 ERC 說明了如何使用 [隱匿地址](https://nerolation.github.io/stealth-utils) 進行匿名轉帳。 + +**警告**:據我們所知,隱匿地址背後的密碼學是健全的。 不過,仍有潛在的旁路攻擊。 您可以在[下方](#go-wrong)看到如何降低此風險。 + +## 隱匿地址的運作方式 {#how} + +本文將會以兩種方式說明隱匿地址。 第一種是[如何使用](#how-use)。 這部分就足以理解本文的其餘內容。 接著會[說明其背後的數學原理](#how-math)。 如果您對密碼學感興趣,也請閱讀這部分。 + +### 簡易版本(如何使用隱匿地址) {#how-use} + +Alice 建立了兩把私鑰,並發布了相應的公鑰(可合併成單一雙倍長度的中繼地址)。 Bill 也建立了一把私鑰,並發布了相應的公鑰。 + +使用其中一方的公鑰和另一方的私鑰,您可以推導出只有 Alice 和 Bill 知道的共享密鑰(無法僅從公鑰推導)。 Bill 可透過此共享密鑰取得隱匿地址,並將資產傳送至該地址。 + +Alice 也能從共享密鑰取得地址,但由於她知道自己所發布公鑰的私鑰,她也能取得讓她從該地址提款的私鑰。 + +### 數學原理(隱匿地址為何如此運作) {#how-math} + +標準隱匿地址使用[橢圓曲線密碼學 (ECC)](https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/#elliptic-curves-building-blocks-of-a-better-trapdoor) 來以較少的金鑰位元達到更佳的效能,同時維持相同等級的安全性。 但在大多數情況下,我們可以忽略這點,假裝我們使用的是常規算術。 + +有一個大家都知道的數字 _G_。 您可以將其乘以 _G_。 但由於橢圓曲線密碼學 (ECC) 的性質,幾乎不可能將其除以 _G_。 以太坊中公鑰密碼學的一般運作方式是,您可以使用私鑰 _Ppriv_ 來簽署交易,然後由公鑰 _Ppub = GPpriv_ 進行驗證。 + +Alice 建立兩把私鑰:_Kpriv_ 和 _Vpriv_。 _Kpriv_ 將用來從隱匿地址花費金錢,而 _Vpriv_ 則用來檢視屬於 Alice 的地址。 Alice 接著發布公鑰:_Kpub = GKpriv_ 和 _Vpub = GVpriv_ + +Bill 建立了第三把私鑰 _Rpriv_,並將 _Rpub = GRpriv_ 發布到中央註冊處(Bill 也可以把它傳送給 Alice,但我們假設 Carol 正在竊聽)。 + +Bill 計算 _RprivVpub = GRprivVpriv_,他預期 Alice 也會知道(如下文說明)。 此值稱為 _S_,也就是共享密鑰。 這給了 Bill 一把公鑰:_Ppub = Kpub+G\*hash(S)_。 他可以從這把公鑰計算出一個地址,並將任何他想要的資源傳送到該地址。 未來如果 Alice 勝選,Bill 可以告訴她 _Rpriv_ 以證明資源來自於他。 + +Alice 計算 _RpubVpriv = GRprivVpriv_。 這給了她相同的共享密鑰 _S_。 因為她知道私鑰 _Kpriv_,她可以計算出 _Ppriv = Kpriv+hash(S)_。 這把金鑰讓她能存取地址中的資產,而該地址是從 _Ppub = GPpriv = GKpriv+G\*hash(S) = Kpub+G\*hash(S)_ 產生的。 + +我們有一把獨立的檢視金鑰,讓 Alice 可以將工作分包給 Dave 的「世界統治競選服務」。 Alice 願意讓 Dave 知道公用地址,並在有更多資金時通知她,但她不希望 Dave 花費她的競選資金。 + +因為檢視和花費使用不同的金鑰,所以 Alice 可以把 _Vpriv_ 給 Dave。 然後 Dave 可以計算 _S = RpubVpriv = GRprivVpriv_,並以此取得公鑰(_Ppub = Kpub+G\*hash(S)_)。 但如果沒有 _Kpriv_,Dave 就無法取得私鑰。 + +總而言之,以下是不同參與者所知道的值。 + +| Alice | 已發布 | 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\*hash(S)_ | - | _Ppub = Kpub+G\*hash(S)_ | _Ppub = Kpub+G\*hash(S)_ | | +| _Address=f(Ppub)_ | - | _Address=f(Ppub)_ | _Address=f(Ppub)_ | _Address=f(Ppub)_ | +| _Ppriv = Kpriv+hash(S)_ | - | - | - | | + +## 隱匿地址出錯時 {#go-wrong} + +_區塊鏈上沒有祕密_。 雖然隱匿地址可以提供隱私,但這種隱私很容易受到流量分析的影響。 舉一個簡單的例子,假設 Bill 為一個地址提供資金,並立即傳送一筆交易來發布一個 _Rpub_ 值。 如果沒有 Alice 的 _Vpriv_,我們無法確定這是一個隱匿地址,但可以這麼猜測。 然後,我們看到另一筆交易,將該地址的所有 ETH 轉移到 Alice 的競選基金地址。 我們可能無法證明,但很有可能 Bill 剛剛捐款給 Alice 的競選活動。 Carol 肯定會這麼想。 + +Bill 很容易將 _Rpub_ 的發布與隱匿地址的資金分開(在不同時間,從不同地址進行)。 然而,這還不夠。 Carol 尋找的模式是 Bill 為一個地址提供資金,然後 Alice 的競選基金從中提款。 + +一個解決方案是 Alice 的競選活動不要直接提款,而是用它來支付給第三方。 如果 Alice 的競選活動將 10 ETH 傳送到 Dave 的「世界統治競選服務」,Carol 只知道 Bill 捐款給 Dave 的其中一個客戶。 如果 Dave 有足夠的客戶,Carol 就無法知道 Bill 是捐款給與她競爭的 Alice,還是捐給 Carol 不在乎的 Adam、Albert 或 Abigail。 Alice 可以在付款中包含一個哈希值,然後向 Dave 提供原像,以證明這是她的捐款。 或者,如上所述,如果 Alice 給了 Dave 她的 _Vpriv_,他就已經知道付款來自誰。 + +這個解決方案的主要問題是,它要求 Alice 在保密對 Bill 有利時,也要關心保密。 Alice 可能想要維持她的聲譽,這樣 Bill 的朋友 Bob 也會捐款給她。 但也有可能她不介意揭發 Bill,因為這樣 Bill 就會害怕如果 Carol 獲勝會發生什麼事。 Bill 最終可能會提供 Alice 更多的支持。 + +### 使用多個隱匿層 {#multi-layer} + +Bill 可以自己保護自己的隱私,而不是依靠 Alice。 他可以為虛構的人物 Bob 和 Bella 產生多個中繼地址。 然後 Bill 將 ETH 傳送給 Bob,而「Bob」(實際上是 Bill)再將其傳送給 Bella。 「Bella」(也是 Bill)再將其傳送給 Alice。 + +Carol 仍然可以進行流量分析,並看到從 Bill 到 Bob 再到 Bella 再到 Alice 的管道。 然而,如果「Bob」和「Bella」也將 ETH 用於其他目的,那麼即使 Alice 立即從隱匿地址提款到她已知的競選地址,也不會顯示 Bill 將任何東西轉移給 Alice。 + +## 編寫隱匿地址應用程式 {#write-app} + +本文說明了一個[在 GitHub 上可用的](https://github.com/qbzzt/251022-stealth-addresses.git)隱匿地址應用程式。 + +### 工具 {#tools} + +我們可以使用[一個 typescript 隱匿地址程式庫](https://github.com/ScopeLift/stealth-address-sdk)。 然而,密碼學操作可能非常耗費 CPU。 我偏好以 [Rust](https://rust-lang.org/) 這類編譯語言實作,並使用 [WASM](https://webassembly.org/) 在瀏覽器中執行程式碼。 + +我們將使用 [Vite](https://vite.dev/) 和 [React](https://react.dev/)。 這些是業界標準的工具;如果您不熟悉,可以使用[此教學](/developers/tutorials/creating-a-wagmi-ui-for-your-contract/)。 要使用 Vite,我們需要 Node。 + +### 實際操作隱匿地址 {#in-action} + +1. 安裝必要的工具:[Rust](https://rust-lang.org/tools/install/) 和 [Node](https://nodejs.org/en/download)。 + +2. 複製 GitHub 存放庫。 + + ```sh + git clone https://github.com/qbzzt/251022-stealth-addresses.git + cd 251022-stealth-addresses + ``` + +3. 安裝先決條件並編譯 Rust 程式碼。 + + ```sh + cd src/rust-wasm + rustup target add wasm32-unknown-unknown + cargo install wasm-pack + wasm-pack build --target web + ``` + +4. 啟動網頁伺服器。 + + ```sh + cd ../.. + npm install + npm run dev + ``` + +5. 瀏覽至[應用程式](http://localhost:5173/)。 此應用程式頁面有兩個框架:一個用於 Alice 的使用者介面,另一個用於 Bill 的使用者介面。 這兩個框架不互相通訊;它們僅為了方便而放在同一個頁面上。 + +6. 以 Alice 的身分,點擊 **Generate a Stealth Meta-Address**(產生隱匿中繼地址)。 這會顯示新的隱匿地址和相應的私鑰。 將隱匿中繼地址複製到剪貼簿。 + +7. 以 Bill 的身分,貼上新的隱匿中繼地址並點擊 **Generate an address**(產生地址)。 這會提供您要為 Alice 提供資金的地址。 + +8. 複製地址和 Bill 的公鑰,並將它們貼到 Alice 使用者介面的「Private key for address generated by Bill」(Bill 產生的地址之私鑰)區域中。 填寫完這些欄位後,您會看到存取該地址資產的私鑰。 + +9. 您可以使用[線上計算機](https://iancoleman.net/ethereum-private-key-to-address/)來確保私鑰與地址相符。 + +### 程式運作方式 {#how-the-program-works} + +#### WASM 元件 {#wasm} + +編譯成 WASM 的原始碼是以 [Rust](https://rust-lang.org/) 編寫的。 您可以在 [`src/rust_wasm/src/lib.rs`](https://github.com/qbzzt/251022-stealth-addresses/blob/main/src/rust-wasm/src/lib.rs) 中看到它。 此程式碼主要是 JavaScript 程式碼與 [`eth-stealth-addresses` 程式庫](https://github.com/kassandraoftroy/eth-stealth-addresses)之間的介面。 + +**`Cargo.toml`** + +Rust 中的 [`Cargo.toml`](https://doc.rust-lang.org/cargo/reference/manifest.html) 類似於 JavaScript 中的 [`package.json`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json)。 其中包含套件資訊、相依性宣告等。 + +```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"] } +``` + +[`getrandom`](https://docs.rs/getrandom/latest/getrandom/) 套件需要產生隨機值。 這無法單純透過演算法達成;它需要存取實體程序作為熵的來源。 此定義指定我們將透過詢問我們正在執行的瀏覽器來取得該熵。 + +```toml +console_error_panic_hook = "0.1.7" +``` + +[此程式庫](https://docs.rs/console_error_panic_hook/latest/console_error_panic_hook/) 在 WASM 程式碼發生恐慌且無法繼續時,提供我們更有意義的錯誤訊息。 + +```toml +[lib] +crate-type = ["cdylib", "rlib"] +``` + +產生 WASM 程式碼所需的輸出類型。 + +**`lib.rs`** + +這是實際的 Rust 程式碼。 + +```rust +use wasm_bindgen::prelude::*; +``` + +從 Rust 建立 WASM 套件的定義。 [此處](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 +}; +``` + +我們需要從 [`eth-stealth-addresses` 程式庫](https://github.com/kassandraoftroy/eth-stealth-addresses) 取得的函式。 + +```rust +use hex::{decode,encode}; +``` + +Rust 通常使用位元組[陣列](https://doc.rust-lang.org/std/primitive.array.html) (`[u8; ]`) 來表示值。 但在 JavaScript 中,我們通常使用十六進位字串。 [`hex` 程式庫](https://docs.rs/hex/latest/hex/) 為我們將一種表示法轉換為另一種。 + +```rust +#[wasm_bindgen] +``` + +產生 WASM 繫結,以便能從 JavaScript 呼叫此函式。 + +```rust +pub fn wasm_generate_stealth_meta_address() -> String { +``` + +傳回具有多個欄位的物件最簡單的方式是傳回 JSON 字串。 + +```rust + let (address, spend_private_key, view_private_key) = + generate_stealth_meta_address(); +``` + +[`generate_stealth_meta_address`](https://docs.rs/eth-stealth-addresses/latest/eth_stealth_addresses/fn.generate_stealth_meta_address.html) 傳回三個欄位: + +- 中繼地址(_Kpub_ 和 _Vpub_) +- 檢視私鑰(_Vpriv_) +- 花費私鑰(_Kpriv_) + +[tuple](https://doc.rust-lang.org/std/primitive.tuple.html) 語法讓我們可以再次分離這些值。 + +```rust + format!("{{\"address\":\"{}\",\"view_private_key\":\"{}\",\"spend_private_key\":\"{}\"}}", + encode(address), + encode(view_private_key), + encode(spend_private_key) + ) +} +``` + +使用 [`format!`](https://doc.rust-lang.org/std/fmt/index.html) 巨集來產生 JSON 編碼的字串。 使用 [`hex::encode`](https://docs.rs/hex/latest/hex/fn.encode.html) 將陣列變更為十六進位字串。 + +```rust +fn str_to_array(s: &str) -> Option<[u8; N]> { +``` + +此函式將十六進位字串(由 JavaScript 提供)轉換為位元組陣列。 我們用它來剖析 JavaScript 程式碼提供的值。 此函式很複雜,因為 Rust 處理陣列和向量的方式。 + +`` 運算式稱為[泛型](https://doc.rust-lang.org/book/ch10-01-syntax.html)。 `N` 是一個控制傳回陣列長度的參數。 此函式實際上稱為 `str_to_array::`,其中 `n` 是陣列長度。 + +傳回值是 `Option<[u8; N]>`,表示傳回的陣列是[可選的](https://doc.rust-lang.org/std/option/)。 這是 Rust 中函式可能失敗的典型模式。 + +例如,如果我們呼叫 `str_to_array::10("bad060a7")`,函式應該傳回一個十值陣列,但輸入只有四個位元組。 此函式需要失敗,而它透過傳回 `None` 來達成。 `str_to_array::4("bad060a7")` 的傳回值會是 `Some<[0xba, 0xd0, 0x60, 0xa7]>`。 + +```rust + // decode 傳回 Result, _> + let vec = decode(s).ok()?; +``` + +[`hex::decode`](https://docs.rs/hex/latest/hex/fn.decode.html) 函式傳回 `Result, FromHexError>`。 [`Result`](https://doc.rust-lang.org/std/result/) 類型可以包含成功結果(`Ok(value)`)或錯誤(`Err(error)`)。 + +`.ok()` 方法會將 `Result` 轉換為 `Option`,其值若成功則為 `Ok()` 值,否則為 `None`。 最後,[問號運算子](https://doc.rust-lang.org/std/option/#the-question-mark-operator-) 會在 `Option` 為空時中止目前的函式並傳回 `None`。 否則,它會解開值並傳回它(在此情況下,是將值指派給 `vec`)。 + +這看起來像是一種處理錯誤的奇怪複雜方法,但 `Result` 和 `Option` 確保所有錯誤都以某種方式處理。 + +```rust + if vec.len() != N { return None; } +``` + +如果位元組數不正確,那就是失敗,我們會傳回 `None`。 + +```rust + // try_into 會耗用 vec 並嘗試建立 [u8; N] + let array: [u8; N] = vec.try_into().ok()?; +``` + +Rust 有兩種陣列類型。 [陣列](https://doc.rust-lang.org/std/primitive.array.html) 的大小固定。 [向量](https://doc.rust-lang.org/std/vec/index.html) 可以增長和縮小。 `hex::decode` 傳回一個向量,但 `eth_stealth_addresses` 程式庫想要接收陣列。 [`.try_into()`](https://doc.rust-lang.org/std/convert/trait.TryInto.html#required-methods) 會將一個值轉換成另一種型別,例如,將向量轉換成陣列。 + +```rust + Some(array) +} +``` + +在函式結尾傳回值時,Rust 不要求您使用 [`return`](https://doc.rust-lang.org/std/keyword.return.html) 關鍵字。 + +```rust +#[wasm_bindgen] +pub fn wasm_generate_stealth_address(stealth_address: &str) -> Option { +``` + +此函式接收一個公用中繼地址,其中包含 _Vpub_ 和 _Kpub_。 它會傳回隱匿地址、要發布的公鑰(_Rpub_),以及一個一位元組的掃描值,用於加速識別哪些已發布的地址可能屬於 Alice。 + +掃描值是共享密鑰(_S = GRprivVpriv_)的一部分。 此值可供 Alice 使用,檢查此值比檢查 _f(Kpub+G\*hash(S))_ 是否等於已發布地址快得多。 + +```rust + let (address, r_pub, scan) = + generate_stealth_address(&str_to_array::<66>(stealth_address)?); +``` + +我們使用程式庫的 [`generate_stealth_address`](https://docs.rs/eth-stealth-addresses/latest/eth_stealth_addresses/fn.generate_stealth_address.html)。 + +```rust + format!("{{\"address\":\"{}\",\"rPub\":\"{}\",\"scan\":\"{}\"}}", + encode(address), + encode(r_pub), + encode(&[scan]) + ).into() +} +``` + +準備 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 { + . + . + . +} +``` + +此函式使用程式庫的 [`compute_stealth_key`](https://docs.rs/eth-stealth-addresses/latest/eth_stealth_addresses/fn.compute_stealth_key.html) 來計算從地址提款的私鑰 (_Rpriv_)。 此計算需要以下值: + +- 地址(_Address=f(Ppub)_) +- Bill 產生的公鑰(_Rpub_) +- 檢視私鑰(_Vpriv_) +- 花費私鑰(_Kpriv_) + +```rust +#[wasm_bindgen(start)] +``` + +[`#[wasm_bindgen(start)]`](https://wasm-bindgen.github.io/wasm-bindgen/reference/attributes/on-rust-exports/start.html) 指定函式在 WASM 程式碼初始化時執行。 + +```rust +pub fn main() { + console_error_panic_hook::set_once(); +} +``` + +此程式碼指定將恐慌輸出傳送到 JavaScript 主機。 要看它實際運作,請使用應用程式並給 Bill 一個無效的中繼地址(只要更改一個十六進位數字)。 您會在 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 +``` + +後面跟著堆疊追蹤。 然後給 Bill 有效的中繼地址,並給 Alice 一個無效的地址或無效的公鑰。 您將會看到此錯誤: + +``` +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 +``` + +同樣地,後面跟著堆疊追蹤。 + +#### 使用者介面 {#ui} + +使用者介面是使用 [React](https://react.dev/) 編寫並由 [Vite](https://vite.dev/) 提供服務。 您可以使用[此教學](/developers/tutorials/creating-a-wagmi-ui-for-your-contract/)來瞭解它們。 這裡不需要 [WAGMI](https://wagmi.sh/),因為我們不直接與區塊鏈或錢包互動。 + +使用者介面唯一不那麼明顯的部分是 WASM 的連線能力。 其運作方式如下。 + +**`vite.config.js`** + +此檔案包含 [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()], +}) +``` + +我們需要兩個 Vite 外掛程式:[react](https://www.npmjs.com/package/@vitejs/plugin-react) 和 [wasm](https://github.com/Menci/vite-plugin-wasm#readme)。 + +**`App.jsx`** + +此檔案是應用程式的主要元件。 它是一個容器,包含兩個元件:`Alice` 和 `Bill`,也就是這些使用者的使用者介面。 與 WASM 相關的部分是初始化程式碼。 + +```jsx +import init from './rust-wasm/pkg/rust_wasm.js' +``` + +當我們使用 [`wasm-pack`](https://rustwasm.github.io/docs/wasm-pack/) 時,它會建立我們在此處使用的兩個檔案:一個包含實際程式碼的 wasm 檔(此處為 `src/rust-wasm/pkg/rust_wasm_bg.wasm`),以及一個包含使用定義的 JavaScript 檔(此處為 `src/rust_wasm/pkg/rust_wasm.js`)。 該 JavaScript 檔案的預設匯出是啟動 WASM 所需執行的程式碼。 + +```jsx +function App() { + . + . + . + useEffect(() => { + const loadWasm = async () => { + try { + await init(); + setWasmReady(true) + } catch (err) { + console.error('Error loading wasm:', err) + alert("Wasm error: " + err) + } + } + + loadWasm() + }, [] + ) +``` + +[`useEffect` 鉤子](https://react.dev/reference/react/useEffect) 可讓您指定一個函式,該函式會在狀態變數變更時執行。 在此,狀態變數清單是空的(`[]`),所以此函式只會在頁面載入時執行一次。 + +效果函式必須立即傳回。 要使用非同步程式碼,例如 WASM `init`(必須載入 `.wasm` 檔,因此需要時間),我們定義一個內部 [`async`](https://en.wikipedia.org/wiki/Async/await) 函式,並在沒有 `await` 的情況下執行它。 + +**`Bill.jsx`** + +這是 Bill 的使用者介面。 它只有一個動作,就是根據 Alice 提供的隱匿中繼地址建立一個地址。 + +```jsx +import { wasm_generate_stealth_address } from './rust-wasm/pkg/rust_wasm.js' +``` + +除了預設匯出之外,`wasm-pack` 產生的 JavaScript 程式碼還會為 WASM 程式碼中的每個函式匯出一個函式。 + +```jsx +