<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-TW"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://cypherpunks-core.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://cypherpunks-core.github.io/" rel="alternate" type="text/html" hreflang="zh-TW" /><updated>2026-01-07T06:27:14+00:00</updated><id>https://cypherpunks-core.github.io/feed.xml</id><title type="html">密碼龐克 Cypherpunks Taiwan</title><subtitle>密碼學使自由和隱私再次偉大。Cryptography makes freedom and privacy great again.</subtitle><author><name>Cypherpunks Taiwan</name><email>01360086@me.mcu.edu.tw</email></author><entry><title type="html">Cypherpunks Taiwan 小聚圓滿落幕｜謝謝每一位參與這場自由技術對話的人</title><link href="https://cypherpunks-core.github.io/news/community/2025/04/01/Cypherpunks-Taiwan(8)/" rel="alternate" type="text/html" title="Cypherpunks Taiwan 小聚圓滿落幕｜謝謝每一位參與這場自由技術對話的人" /><published>2025-04-01T00:00:00+00:00</published><updated>2025-04-01T00:00:00+00:00</updated><id>https://cypherpunks-core.github.io/news/community/2025/04/01/Cypherpunks%20Taiwan(8)</id><content type="html" xml:base="https://cypherpunks-core.github.io/news/community/2025/04/01/Cypherpunks-Taiwan(8)/"><![CDATA[<p>3/31，我們在 Tempo House 二樓，正式啟動了 Cypherpunks Taiwan 的重啟。這是一場關於隱私、制度、自由技術與去中心化未來的對話，也是一場由社群自發發起的集體行動。我們想找回一個空間，讓關心這些議題的人可以見面、交流、共振。</p>

<h2 id="-感謝每一位講者為現場帶來深刻與真誠的聲音">🎤 感謝每一位講者，為現場帶來深刻與真誠的聲音：</h2>

<ul>
  <li>
    <p><strong>Dr. Awesome Doge｜Founder @Cypherpunks Taiwan</strong></p>

    <p>《Revive Cypherpunk》</p>

    <p>密碼龐克不只是簡單的技術社群，更是一場持續進行的倫理實踐運動。密碼龐克們堅信：<strong>唯有透過密碼學，我們才能在高度監控的數位社會中奪回自由與主權</strong>。這場活動讓來自各國的 Cypherpunks 齊聚一堂，交流實踐經驗，並再次強調那句核心信念——<strong>以技術為工具，以自由為核心。</strong></p>
  </li>
  <li>
    <p><strong>Max Wu｜ Co-Founder @HackMD</strong></p>

    <p>《HackMD, the core of community and how it works》</p>

    <p><strong>HackMD 是密碼龐克精神在協作領域的實踐。透過簡潔開放的 Markdown 語言，結合 OT 與 CRDT 等底層協定，讓社群能夠自由建構知識、無需仰賴中心化的審查與控制</strong>。正如密碼龐克們用匿名網路與加密通訊對抗監控資本主義，<strong>Max 也透過工具設計，持續賦能社群，實現技術上的自主與自由。</strong></p>
  </li>
  <li>
    <p><strong>林旅強 Richard｜ Co-Founder @開源社</strong></p>

    <p>《我（們）與 Cypherpunk 的距離》</p>

    <p>Richard 在商業領域中的角色，可以被視為一種<strong>實踐密碼龐克原則的努力</strong>。Richard 善用其在戰略層面的影響力，引導技術與產品的發展方向，使其更貼近密碼龐克對「數位自主」、「抗衡監控」的核心願景。這種實踐展現了密碼龐克精神在現實世界中的另一種可能。</p>
  </li>
  <li>
    <p><strong>Neil Lee｜執行董事 @EchoX</strong></p>

    <p>《在監管壓力下，合規與隱私技術的平衡》</p>

    <p>延續密碼龐克「<strong>我們不指望政府或企業給我們隱私，我們用密碼學捍衛它</strong>」的理念，Neil 正在監管與技術的灰色地帶中，探索合規框架下的隱私保護。EchoX 尋求在「透明 vs. 隱私」之間找到平衡點，讓個人財務資料仍能在法規下保持應有的自主性與防護力。</p>
  </li>
  <li>
    <p><strong>特別嘉賓：寶博士（現任立法委員） &amp; Peter @Plan B Network</strong></p>

    <p>兩位的臨時對談，從政策現場聊到產業鏈結與資安隱私，精彩內容讓現場氣氛沸騰。<strong>Plan B Network 更大方贊助冷錢包</strong>，讓與會者除了思想上的收穫，還能實際帶回一份對自身資產的加密保障</p>
  </li>
</ul>

<h2 id="-活動成果">🙏 活動成果</h2>

<p>現場超過 60 人參與，從技術人、開源工作者、公職、研究者到圈外人，這場活動證明了：</p>

<p><strong>關心制度與技術的人還很多，只是缺一個聚在一起的場域。</strong></p>

<ul>
  <li>📸 講者簡報與活動照片整理中，後續將分享至社群頻道</li>
  <li>📬 想參與下一次？歡迎加入 Telegram 群組：<a href="https://t.me/cypherpunkstw">https://t.me/cypherpunkstw</a></li>
</ul>

<h2 id="未來展望">未來展望</h2>

<p><strong>Cypherpunks write code — and build community.</strong></p>

<p>我們不只是懷舊，而是試著定義未來的「開源社會基礎建設」。</p>

<p>這場聚會不是終點，而是一行剛 commit 的程式碼。</p>

<p>See you next round.</p>

<p>#CypherpunksTaiwan #Privacy #OpenSource #Web3 #Decentralization #CryptoCulture #TaiwanTechCommunity #HackMD #TONX #Tether #去中心化 #資料主權</p>

<h2 id="-活動精選照片">📸 活動精選照片</h2>

<p><img src="/img/151.png" alt="活動開場：Dr. Awesome Doge 分享「Revive Cypherpunk」" /></p>

<p><img src="/img/152.png" alt="與會者專注聆聽講者分享" /></p>

<p><img src="/img/153.png" alt="現場交流討論熱烈" /></p>

<p><img src="/img/154.png" alt="Cypherpunks Taiwan 社群大合照" /></p>]]></content><author><name>Cypherpunks Taiwan</name></author><category term="news" /><category term="community" /><category term="cypherpunks" /><category term="privacy" /><category term="decentralization" /><category term="technology" /><category term="community" /><summary type="html"><![CDATA[3/31，我們在 Tempo House 二樓，正式啟動了 Cypherpunks Taiwan 的重啟。這是一場關於隱私、制度、自由技術與去中心化未來的對話，也是一場由社群自發發起的集體行動。我們想找回一個空間，讓關心這些議題的人可以見面、交流、共振。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cypherpunks-core.github.io/img/151.png" /><media:content medium="image" url="https://cypherpunks-core.github.io/img/151.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Rust Bitcoin 開發入門（四）：進階應用與整合</title><link href="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/rust/2025/03/25/rust-bitcoin-tutorial-4-advanced-applications/" rel="alternate" type="text/html" title="Rust Bitcoin 開發入門（四）：進階應用與整合" /><published>2025-03-25T00:00:00+00:00</published><updated>2025-03-25T00:00:00+00:00</updated><id>https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/rust/2025/03/25/rust-bitcoin-tutorial-4-advanced-applications</id><content type="html" xml:base="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/rust/2025/03/25/rust-bitcoin-tutorial-4-advanced-applications/"><![CDATA[<p>這是 Rust Bitcoin 開發入門系列的最後一篇。本篇探討如何構建完整的 Bitcoin 應用，包括與節點互動、錢包開發和實際部署考量。</p>

<p><strong>系列文章導航：</strong></p>
<ul>
  <li><a href="/2025/03/22/rust-bitcoin-tutorial-1-environment-basics/">第一篇：環境設置與基礎概念</a></li>
  <li><a href="/2025/03/23/rust-bitcoin-tutorial-2-addresses-transactions/">第二篇：地址生成與交易構建</a></li>
  <li><a href="/2025/03/24/rust-bitcoin-tutorial-3-scripts-signatures/">第三篇：腳本與簽名</a></li>
  <li><strong>第四篇：進階應用與整合</strong>（本篇）</li>
</ul>

<hr />

<h2 id="1-區塊鏈數據存取策略">1. 區塊鏈數據存取策略</h2>

<h3 id="11-理解不同的存取方式">1.1 理解不同的存取方式</h3>

<p>要構建實際的 Bitcoin 應用，你需要與區塊鏈數據互動。有幾種主要的方式可以做到這一點，每種都有其優缺點。</p>

<p><strong>Bitcoin Core RPC</strong> 是最直接的方式。你運行一個完整節點，透過 JSON-RPC 介面查詢數據。這種方式提供了最高的安全性和隱私性——你不依賴任何第三方。缺點是需要存儲完整的區塊鏈（數百 GB），且初始同步需要數天。</p>

<p><strong>Electrum 協議</strong> 使用客戶端-伺服器模式。輕量級客戶端連接到 Electrum 伺服器，伺服器維護一個索引的區塊鏈數據庫。客戶端可以快速查詢地址餘額和交易歷史，而不需要下載完整區塊鏈。缺點是你需要信任伺服器提供正確的數據（除非你自己運行伺服器）。</p>

<p><strong>Esplora API</strong> 是 Blockstream 開發的另一種輕量級解決方案，透過 REST API 提供區塊鏈數據。它類似於 Electrum，但使用標準的 HTTP 介面，更容易整合。</p>

<p>選擇哪種方式取決於你的應用需求。對於個人錢包，Electrum 或 Esplora 通常足夠。對於需要最高安全性的應用（如交易所），你應該運行自己的完整節點。</p>

<h3 id="12-bitcoin-core-rpc">1.2 Bitcoin Core RPC</h3>

<p>Bitcoin Core 提供了一個豐富的 JSON-RPC 介面，可以查詢區塊鏈狀態、管理錢包和廣播交易。使用 <code class="language-plaintext highlighter-rouge">bitcoincore-rpc</code> crate 可以方便地從 Rust 存取這個介面。</p>

<p>首先需要配置 Bitcoin Core 啟用 RPC：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre># bitcoin.conf
server=1
rpcuser=your_username
rpcpassword=your_password
rpcport=8332  # mainnet
</pre></td></tr></tbody></table></code></pre></div></div>

<p>然後在 Rust 中連接：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoincore_rpc</span><span class="p">::{</span><span class="n">Auth</span><span class="p">,</span> <span class="n">Client</span><span class="p">,</span> <span class="n">RpcApi</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">connect_to_node</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Client</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">rpc</span> <span class="o">=</span> <span class="nn">Client</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span>
        <span class="s">"http://127.0.0.1:8332"</span><span class="p">,</span>
        <span class="nn">Auth</span><span class="p">::</span><span class="nf">UserPass</span><span class="p">(</span><span class="s">"your_username"</span><span class="nf">.into</span><span class="p">(),</span> <span class="s">"your_password"</span><span class="nf">.into</span><span class="p">()),</span>
    <span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 驗證連接</span>
    <span class="k">let</span> <span class="n">info</span> <span class="o">=</span> <span class="n">rpc</span><span class="nf">.get_blockchain_info</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"已連接到 Bitcoin Core"</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"區塊數: {}"</span><span class="p">,</span> <span class="n">info</span><span class="py">.blocks</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"同步進度: {:.2}%"</span><span class="p">,</span> <span class="n">info</span><span class="py">.verification_progress</span> <span class="o">*</span> <span class="mf">100.0</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">(</span><span class="n">rpc</span><span class="p">)</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>RPC 提供了廣泛的功能。你可以查詢區塊鏈信息、獲取特定區塊和交易、管理錢包、估算費率、廣播交易等。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">rpc_examples</span><span class="p">(</span><span class="n">rpc</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Client</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 獲取最新區塊</span>
    <span class="k">let</span> <span class="n">best_hash</span> <span class="o">=</span> <span class="n">rpc</span><span class="nf">.get_best_block_hash</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">block</span> <span class="o">=</span> <span class="n">rpc</span><span class="nf">.get_block</span><span class="p">(</span><span class="o">&amp;</span><span class="n">best_hash</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"最新區塊包含 {} 筆交易"</span><span class="p">,</span> <span class="n">block</span><span class="py">.txdata</span><span class="nf">.len</span><span class="p">());</span>

    <span class="c1">// 估算費率（6 區塊確認目標）</span>
    <span class="k">let</span> <span class="n">fee</span> <span class="o">=</span> <span class="n">rpc</span><span class="nf">.estimate_smart_fee</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="nb">None</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">rate</span><span class="p">)</span> <span class="o">=</span> <span class="n">fee</span><span class="py">.fee_rate</span> <span class="p">{</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"建議費率: {} sat/vB"</span><span class="p">,</span> <span class="n">rate</span><span class="nf">.to_sat</span><span class="p">()</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 獲取 mempool 狀態</span>
    <span class="k">let</span> <span class="n">mempool</span> <span class="o">=</span> <span class="n">rpc</span><span class="nf">.get_mempool_info</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"Mempool 中有 {} 筆待確認交易"</span><span class="p">,</span> <span class="n">mempool</span><span class="py">.size</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="13-electrum-協議">1.3 Electrum 協議</h3>

<p>Electrum 協議設計用於輕量級客戶端。它讓你可以快速查詢特定地址的餘額和歷史，而不需要掃描整個區塊鏈。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">electrum_client</span><span class="p">::{</span><span class="n">Client</span><span class="p">,</span> <span class="n">ElectrumApi</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">electrum_example</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 連接到 Electrum 伺服器</span>
    <span class="k">let</span> <span class="n">client</span> <span class="o">=</span> <span class="nn">Client</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"ssl://electrum.blockstream.info:60002"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 查詢地址餘額</span>
    <span class="k">let</span> <span class="n">address</span> <span class="o">=</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="s">"bc1q..."</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">script</span> <span class="o">=</span> <span class="n">address</span><span class="nf">.script_pubkey</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">balance</span> <span class="o">=</span> <span class="n">client</span><span class="nf">.script_get_balance</span><span class="p">(</span><span class="o">&amp;</span><span class="n">script</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"已確認餘額: {} sat"</span><span class="p">,</span> <span class="n">balance</span><span class="py">.confirmed</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"未確認餘額: {} sat"</span><span class="p">,</span> <span class="n">balance</span><span class="py">.unconfirmed</span><span class="p">);</span>

    <span class="c1">// 獲取交易歷史</span>
    <span class="k">let</span> <span class="n">history</span> <span class="o">=</span> <span class="n">client</span><span class="nf">.script_get_history</span><span class="p">(</span><span class="o">&amp;</span><span class="n">script</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"共有 {} 筆相關交易"</span><span class="p">,</span> <span class="n">history</span><span class="nf">.len</span><span class="p">());</span>

    <span class="c1">// 獲取未花費輸出</span>
    <span class="k">let</span> <span class="n">utxos</span> <span class="o">=</span> <span class="n">client</span><span class="nf">.script_list_unspent</span><span class="p">(</span><span class="o">&amp;</span><span class="n">script</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">for</span> <span class="n">utxo</span> <span class="k">in</span> <span class="n">utxos</span> <span class="p">{</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"UTXO: {}:{} - {} sat"</span><span class="p">,</span>
            <span class="n">utxo</span><span class="py">.tx_hash</span><span class="p">,</span> <span class="n">utxo</span><span class="py">.tx_pos</span><span class="p">,</span> <span class="n">utxo</span><span class="py">.value</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Electrum 的一個重要特性是訂閱功能。你可以訂閱特定地址的變化通知，這對於支付處理器等需要監控入帳的應用很有用。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">monitor_address</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Client</span><span class="p">,</span> <span class="n">script</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Script</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 訂閱地址狀態變化</span>
    <span class="k">let</span> <span class="n">status</span> <span class="o">=</span> <span class="n">client</span><span class="nf">.script_subscribe</span><span class="p">(</span><span class="n">script</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"初始狀態: {:?}"</span><span class="p">,</span> <span class="n">status</span><span class="p">);</span>

    <span class="c1">// 在實際應用中，你會在後台執行緒中監聽變化</span>
    <span class="c1">// 當地址收到新交易時，會收到通知</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="2-使用-bdk-構建錢包">2. 使用 BDK 構建錢包</h2>

<h3 id="21-什麼是-bdk">2.1 什麼是 BDK</h3>

<p>BDK（Bitcoin Development Kit）是一個現代化的錢包開發框架。它抽象了錢包開發的複雜性，讓開發者可以專注於應用邏輯，而不是底層的區塊鏈細節。</p>

<p>BDK 的設計理念是基於「描述符」（descriptors）。描述符是一種標準化的方式來描述如何生成地址和花費資金。這種方法比傳統的「地址列表」方式更靈活，可以輕鬆支持各種腳本類型（P2PKH、P2WPKH、多簽等）。</p>

<p>BDK 的架構分為幾層：</p>

<p><strong>錢包層</strong> 處理地址生成、交易構建和簽名。它不關心如何獲取區塊鏈數據或如何存儲狀態。</p>

<p><strong>區塊鏈後端</strong> 提供區塊鏈數據。BDK 支持多種後端：Electrum、Esplora、Bitcoin Core RPC 等。你可以根據需求選擇。</p>

<p><strong>數據庫後端</strong> 存儲錢包狀態（已知交易、UTXO 等）。BDK 支持內存數據庫、SQLite 等。</p>

<h3 id="22-創建-bdk-錢包">2.2 創建 BDK 錢包</h3>

<p>讓我們從創建一個簡單的錢包開始。我們將使用助記詞生成密鑰，創建原生 SegWit（BIP84）錢包。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bdk</span><span class="p">::{</span>
    <span class="n">Wallet</span><span class="p">,</span>
    <span class="nn">database</span><span class="p">::</span><span class="n">MemoryDatabase</span><span class="p">,</span>
    <span class="nn">bitcoin</span><span class="p">::</span><span class="n">Network</span><span class="p">,</span>
<span class="p">};</span>
<span class="k">use</span> <span class="nn">bip39</span><span class="p">::</span><span class="n">Mnemonic</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">create_wallet</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Wallet</span><span class="o">&lt;</span><span class="n">MemoryDatabase</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
    <span class="c1">// 生成新的助記詞（實際應用中應該讓用戶備份）</span>
    <span class="k">let</span> <span class="n">mnemonic</span> <span class="o">=</span> <span class="nn">Mnemonic</span><span class="p">::</span><span class="nf">generate</span><span class="p">(</span><span class="mi">12</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"請備份您的助記詞: {}"</span><span class="p">,</span> <span class="n">mnemonic</span><span class="p">);</span>

    <span class="c1">// 從助記詞派生主密鑰</span>
    <span class="k">let</span> <span class="n">seed</span> <span class="o">=</span> <span class="n">mnemonic</span><span class="nf">.to_seed</span><span class="p">(</span><span class="s">""</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">xprv</span> <span class="o">=</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">bip32</span><span class="p">::</span><span class="nn">Xpriv</span><span class="p">::</span><span class="nf">new_master</span><span class="p">(</span><span class="nn">Network</span><span class="p">::</span><span class="n">Testnet</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">seed</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 創建 BIP84 描述符</span>
    <span class="c1">// 外部鏈（接收地址）：m/84'/1'/0'/0/*</span>
    <span class="c1">// 內部鏈（找零地址）：m/84'/1'/0'/1/*</span>
    <span class="k">let</span> <span class="n">external</span> <span class="o">=</span> <span class="nd">format!</span><span class="p">(</span><span class="s">"wpkh({}/84'/1'/0'/0/*)"</span><span class="p">,</span> <span class="n">xprv</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">internal</span> <span class="o">=</span> <span class="nd">format!</span><span class="p">(</span><span class="s">"wpkh({}/84'/1'/0'/1/*)"</span><span class="p">,</span> <span class="n">xprv</span><span class="p">);</span>

    <span class="c1">// 創建錢包</span>
    <span class="k">let</span> <span class="n">wallet</span> <span class="o">=</span> <span class="nn">Wallet</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="n">external</span><span class="p">,</span>
        <span class="nf">Some</span><span class="p">(</span><span class="o">&amp;</span><span class="n">internal</span><span class="p">),</span>
        <span class="nn">Network</span><span class="p">::</span><span class="n">Testnet</span><span class="p">,</span>
        <span class="nn">MemoryDatabase</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
    <span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 生成幾個地址</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">生成的地址:"</span><span class="p">);</span>
    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..</span><span class="mi">3</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">addr</span> <span class="o">=</span> <span class="n">wallet</span><span class="nf">.get_address</span><span class="p">(</span><span class="nn">bdk</span><span class="p">::</span><span class="nn">wallet</span><span class="p">::</span><span class="nn">AddressIndex</span><span class="p">::</span><span class="n">New</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"  {}: {}"</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">addr</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nf">Ok</span><span class="p">(</span><span class="n">wallet</span><span class="p">)</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>描述符的威力在於其靈活性。你可以輕鬆創建不同類型的錢包：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="c1">// 單簽 P2WPKH（原生 SegWit）</span>
<span class="k">let</span> <span class="n">single_sig</span> <span class="o">=</span> <span class="s">"wpkh([fingerprint/84'/0'/0']xpub.../0/*)"</span><span class="p">;</span>

<span class="c1">// 2-of-3 多簽</span>
<span class="k">let</span> <span class="n">multisig</span> <span class="o">=</span> <span class="s">"wsh(multi(2,[fp1]xpub1.../0/*,[fp2]xpub2.../0/*,[fp3]xpub3.../0/*))"</span><span class="p">;</span>

<span class="c1">// Taproot</span>
<span class="k">let</span> <span class="n">taproot</span> <span class="o">=</span> <span class="s">"tr([fingerprint/86'/0'/0']xpub.../0/*)"</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="23-同步和查詢">2.3 同步和查詢</h3>

<p>創建錢包後，需要與區塊鏈同步以獲取餘額和交易歷史。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bdk</span><span class="p">::{</span>
    <span class="nn">blockchain</span><span class="p">::{</span><span class="n">ElectrumBlockchain</span><span class="p">,</span> <span class="n">GetHeight</span><span class="p">},</span>
    <span class="n">SyncOptions</span><span class="p">,</span>
<span class="p">};</span>

<span class="k">fn</span> <span class="nf">sync_and_query</span><span class="p">(</span><span class="n">wallet</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Wallet</span><span class="o">&lt;</span><span class="n">MemoryDatabase</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 創建區塊鏈後端</span>
    <span class="k">let</span> <span class="n">blockchain</span> <span class="o">=</span> <span class="nn">ElectrumBlockchain</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span>
        <span class="nn">electrum_client</span><span class="p">::</span><span class="nn">Client</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"ssl://electrum.blockstream.info:60002"</span><span class="p">)</span><span class="o">?</span>
    <span class="p">);</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"當前區塊高度: {}"</span><span class="p">,</span> <span class="n">blockchain</span><span class="nf">.get_height</span><span class="p">()</span><span class="o">?</span><span class="p">);</span>

    <span class="c1">// 同步錢包</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"正在同步..."</span><span class="p">);</span>
    <span class="n">wallet</span><span class="nf">.sync</span><span class="p">(</span><span class="o">&amp;</span><span class="n">blockchain</span><span class="p">,</span> <span class="nn">SyncOptions</span><span class="p">::</span><span class="nf">default</span><span class="p">())</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"同步完成"</span><span class="p">);</span>

    <span class="c1">// 查詢餘額</span>
    <span class="k">let</span> <span class="n">balance</span> <span class="o">=</span> <span class="n">wallet</span><span class="nf">.get_balance</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">餘額:"</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"  已確認: {} sat"</span><span class="p">,</span> <span class="n">balance</span><span class="py">.confirmed</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"  待確認入帳: {} sat"</span><span class="p">,</span> <span class="n">balance</span><span class="py">.untrusted_pending</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"  待確認出帳: {} sat"</span><span class="p">,</span> <span class="n">balance</span><span class="py">.trusted_pending</span><span class="p">);</span>

    <span class="c1">// 列出 UTXO</span>
    <span class="k">let</span> <span class="n">utxos</span> <span class="o">=</span> <span class="n">wallet</span><span class="nf">.list_unspent</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
    <span class="k">if</span> <span class="o">!</span><span class="n">utxos</span><span class="nf">.is_empty</span><span class="p">()</span> <span class="p">{</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">UTXO:"</span><span class="p">);</span>
        <span class="k">for</span> <span class="n">utxo</span> <span class="k">in</span> <span class="n">utxos</span> <span class="p">{</span>
            <span class="nd">println!</span><span class="p">(</span><span class="s">"  {}:{} - {} sat"</span><span class="p">,</span>
                <span class="n">utxo</span><span class="py">.outpoint.txid</span><span class="p">,</span>
                <span class="n">utxo</span><span class="py">.outpoint.vout</span><span class="p">,</span>
                <span class="n">utxo</span><span class="py">.txout.value</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="24-構建和發送交易">2.4 構建和發送交易</h3>

<p>BDK 的交易構建器提供了直觀的 API 來創建交易。它自動處理選幣、費用計算和找零。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bdk</span><span class="p">::{</span><span class="n">SignOptions</span><span class="p">,</span> <span class="n">FeeRate</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">bdk</span><span class="p">::</span><span class="nn">blockchain</span><span class="p">::</span><span class="n">Blockchain</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">send_transaction</span><span class="p">(</span>
    <span class="n">wallet</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Wallet</span><span class="o">&lt;</span><span class="n">MemoryDatabase</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">blockchain</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">ElectrumBlockchain</span><span class="p">,</span>
    <span class="n">recipient</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Address</span><span class="p">,</span>
    <span class="n">amount_sat</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Txid</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 構建交易</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">builder</span> <span class="o">=</span> <span class="n">wallet</span><span class="nf">.build_tx</span><span class="p">();</span>

    <span class="n">builder</span>
        <span class="c1">// 添加接收者</span>
        <span class="nf">.add_recipient</span><span class="p">(</span><span class="n">recipient</span><span class="nf">.script_pubkey</span><span class="p">(),</span> <span class="n">amount_sat</span><span class="p">)</span>
        <span class="c1">// 設置費率（sat/vB）</span>
        <span class="nf">.fee_rate</span><span class="p">(</span><span class="nn">FeeRate</span><span class="p">::</span><span class="nf">from_sat_per_vb</span><span class="p">(</span><span class="mf">10.0</span><span class="p">))</span>
        <span class="c1">// 啟用 RBF（允許稍後提高費率）</span>
        <span class="nf">.enable_rbf</span><span class="p">();</span>

    <span class="c1">// 完成構建</span>
    <span class="k">let</span> <span class="p">(</span><span class="k">mut</span> <span class="n">psbt</span><span class="p">,</span> <span class="n">tx_details</span><span class="p">)</span> <span class="o">=</span> <span class="n">builder</span><span class="nf">.finish</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"交易詳情:"</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"  發送金額: {} sat"</span><span class="p">,</span> <span class="n">amount_sat</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"  手續費: {} sat"</span><span class="p">,</span> <span class="n">tx_details</span><span class="py">.fee</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span>

    <span class="c1">// 簽名</span>
    <span class="k">let</span> <span class="n">finalized</span> <span class="o">=</span> <span class="n">wallet</span><span class="nf">.sign</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">psbt</span><span class="p">,</span> <span class="nn">SignOptions</span><span class="p">::</span><span class="nf">default</span><span class="p">())</span><span class="o">?</span><span class="p">;</span>
    <span class="k">if</span> <span class="o">!</span><span class="n">finalized</span> <span class="p">{</span>
        <span class="nn">anyhow</span><span class="p">::</span><span class="nd">bail!</span><span class="p">(</span><span class="s">"簽名未完成"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 提取最終交易</span>
    <span class="k">let</span> <span class="n">tx</span> <span class="o">=</span> <span class="n">psbt</span><span class="nf">.extract_tx</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">txid</span> <span class="o">=</span> <span class="n">tx</span><span class="nf">.compute_txid</span><span class="p">();</span>

    <span class="c1">// 廣播</span>
    <span class="n">blockchain</span><span class="nf">.broadcast</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tx</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"交易已廣播: {}"</span><span class="p">,</span> <span class="n">txid</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">(</span><span class="n">txid</span><span class="p">)</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>BDK 還支持更複雜的場景，如手動選幣：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">advanced_transaction</span><span class="p">(</span><span class="n">wallet</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Wallet</span><span class="o">&lt;</span><span class="n">MemoryDatabase</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">builder</span> <span class="o">=</span> <span class="n">wallet</span><span class="nf">.build_tx</span><span class="p">();</span>

    <span class="c1">// 手動選擇特定的 UTXO</span>
    <span class="k">let</span> <span class="n">utxos</span> <span class="o">=</span> <span class="n">wallet</span><span class="nf">.list_unspent</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">utxo</span><span class="p">)</span> <span class="o">=</span> <span class="n">utxos</span><span class="nf">.first</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">builder</span><span class="nf">.add_utxo</span><span class="p">(</span><span class="n">utxo</span><span class="py">.outpoint</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// 或者使用特定的選幣策略</span>
    <span class="c1">// builder.coin_selection(LargestFirstCoinSelection);</span>

    <span class="c1">// 添加 OP_RETURN 數據</span>
    <span class="c1">// builder.add_data(&amp;[1, 2, 3, 4]);</span>

    <span class="c1">// 設置自定義序列號（用於時間鎖）</span>
    <span class="c1">// builder.set_sequence(Sequence::from_height(144));</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="3-安全考量">3. 安全考量</h2>

<h3 id="31-密鑰管理">3.1 密鑰管理</h3>

<p>密鑰管理是 Bitcoin 應用最關鍵的安全面向。私鑰一旦洩露，資金就會永久丟失。以下是一些最佳實踐。</p>

<p><strong>內存安全</strong>：敏感數據（私鑰、助記詞）在使用後應該立即從內存中清除。Rust 的 <code class="language-plaintext highlighter-rouge">zeroize</code> crate 提供了這個功能。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">zeroize</span><span class="p">::</span><span class="n">Zeroize</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">secure_key_handling</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">secret</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0u8</span><span class="p">;</span> <span class="mi">32</span><span class="p">];</span>
    <span class="c1">// ... 使用 secret ...</span>

    <span class="c1">// 使用完畢後清零</span>
    <span class="n">secret</span><span class="nf">.zeroize</span><span class="p">();</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>永遠不要記錄敏感數據</strong>：日誌中不應該出現私鑰、助記詞或其他敏感信息。這是一個常見的錯誤。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="c1">// 錯誤！不要這樣做</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"私鑰: {}"</span><span class="p">,</span> <span class="n">private_key</span><span class="p">);</span>

<span class="c1">// 正確：只記錄非敏感信息</span>
<span class="nd">println!</span><span class="p">(</span><span class="s">"正在處理地址: {}"</span><span class="p">,</span> <span class="n">address</span><span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>考慮使用硬體錢包</strong>：對於生產環境，考慮整合硬體錢包（如 Ledger 或 Trezor）。私鑰永遠不離開硬體設備，大大降低了被盜風險。</p>

<h3 id="32-輸入驗證">3.2 輸入驗證</h3>

<p>永遠不要信任外部輸入。在處理用戶提供的地址或金額之前，必須驗證其有效性。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">validate_inputs</span><span class="p">(</span>
    <span class="n">address_str</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span>
    <span class="n">amount_sat</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="n">network</span><span class="p">:</span> <span class="n">Network</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">(</span><span class="n">Address</span><span class="p">,</span> <span class="n">Amount</span><span class="p">)</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 驗證地址格式</span>
    <span class="k">let</span> <span class="n">address</span> <span class="o">=</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="n">address_str</span><span class="p">)</span>
        <span class="nf">.map_err</span><span class="p">(|</span><span class="n">e</span><span class="p">|</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nd">anyhow!</span><span class="p">(</span><span class="s">"無效的地址格式: {}"</span><span class="p">,</span> <span class="n">e</span><span class="p">))</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 驗證網路</span>
    <span class="k">if</span> <span class="o">!</span><span class="n">address</span><span class="nf">.is_valid_for_network</span><span class="p">(</span><span class="n">network</span><span class="p">)</span> <span class="p">{</span>
        <span class="nn">anyhow</span><span class="p">::</span><span class="nd">bail!</span><span class="p">(</span><span class="s">"地址與網路不匹配"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 驗證金額</span>
    <span class="k">if</span> <span class="n">amount_sat</span> <span class="o">&lt;</span> <span class="mi">546</span> <span class="p">{</span>
        <span class="nn">anyhow</span><span class="p">::</span><span class="nd">bail!</span><span class="p">(</span><span class="s">"金額低於粉塵限制"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="n">amount_sat</span> <span class="o">&gt;</span> <span class="mi">21_000_000</span> <span class="o">*</span> <span class="mi">100_000_000</span> <span class="p">{</span>
        <span class="nn">anyhow</span><span class="p">::</span><span class="nd">bail!</span><span class="p">(</span><span class="s">"金額超過 Bitcoin 總供應量"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="n">amount</span> <span class="o">=</span> <span class="nn">Amount</span><span class="p">::</span><span class="nf">from_sat</span><span class="p">(</span><span class="n">amount_sat</span><span class="p">);</span>
    <span class="nf">Ok</span><span class="p">((</span><span class="n">address</span><span class="nf">.assume_checked</span><span class="p">(),</span> <span class="n">amount</span><span class="p">))</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="33-錯誤處理">3.3 錯誤處理</h3>

<p>適當的錯誤處理對於安全和用戶體驗都很重要。使用結構化的錯誤類型，並確保不在錯誤消息中洩露敏感信息。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">thiserror</span><span class="p">::</span><span class="n">Error</span><span class="p">;</span>

<span class="nd">#[derive(Error,</span> <span class="nd">Debug)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">WalletError</span> <span class="p">{</span>
    <span class="nd">#[error(</span><span class="s">"餘額不足"</span><span class="nd">)]</span>
    <span class="n">InsufficientFunds</span> <span class="p">{</span>
        <span class="n">required</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
        <span class="n">available</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="p">},</span>

    <span class="nd">#[error(</span><span class="s">"無效的接收地址"</span><span class="nd">)]</span>
    <span class="n">InvalidAddress</span><span class="p">,</span>

    <span class="nd">#[error(</span><span class="s">"網路連接失敗"</span><span class="nd">)]</span>
    <span class="nf">NetworkError</span><span class="p">(</span><span class="nd">#[from]</span> <span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="n">Error</span><span class="p">),</span>

    <span class="nd">#[error(</span><span class="s">"交易構建失敗"</span><span class="nd">)]</span>
    <span class="nf">TransactionError</span><span class="p">(</span><span class="nb">String</span><span class="p">),</span>
<span class="p">}</span>

<span class="c1">// 在日誌中只顯示安全的信息</span>
<span class="k">impl</span> <span class="n">WalletError</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">safe_message</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="p">{</span>
        <span class="k">match</span> <span class="k">self</span> <span class="p">{</span>
            <span class="k">Self</span><span class="p">::</span><span class="n">InsufficientFunds</span> <span class="p">{</span> <span class="o">..</span> <span class="p">}</span> <span class="k">=&gt;</span> <span class="s">"餘額不足"</span><span class="p">,</span>
            <span class="k">Self</span><span class="p">::</span><span class="n">InvalidAddress</span> <span class="k">=&gt;</span> <span class="s">"地址驗證失敗"</span><span class="p">,</span>
            <span class="k">Self</span><span class="p">::</span><span class="nf">NetworkError</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="s">"網路連接問題"</span><span class="p">,</span>
            <span class="k">Self</span><span class="p">::</span><span class="nf">TransactionError</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="s">"交易處理失敗"</span><span class="p">,</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="4-完整錢包應用範例">4. 完整錢包應用範例</h2>

<h3 id="41-架構設計">4.1 架構設計</h3>

<p>一個生產級的錢包應用需要考慮許多面向：用戶界面、狀態管理、錯誤處理、日誌記錄等。這裡我們展示一個簡化但完整的命令列錢包。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
</pre></td><td class="rouge-code"><pre><span class="k">pub</span> <span class="k">struct</span> <span class="n">WalletApp</span> <span class="p">{</span>
    <span class="n">wallet</span><span class="p">:</span> <span class="n">Wallet</span><span class="o">&lt;</span><span class="n">SqliteDatabase</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">blockchain</span><span class="p">:</span> <span class="n">ElectrumBlockchain</span><span class="p">,</span>
    <span class="n">network</span><span class="p">:</span> <span class="n">Network</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">WalletApp</span> <span class="p">{</span>
    <span class="cd">/// 創建新錢包</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">create</span><span class="p">(</span>
        <span class="n">name</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span>
        <span class="n">mnemonic</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span>
        <span class="n">passphrase</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span>
        <span class="n">network</span><span class="p">:</span> <span class="n">Network</span><span class="p">,</span>
        <span class="n">data_dir</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Path</span><span class="p">,</span>
    <span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// 驗證助記詞</span>
        <span class="k">let</span> <span class="n">mnemonic</span> <span class="o">=</span> <span class="nn">Mnemonic</span><span class="p">::</span><span class="nf">parse</span><span class="p">(</span><span class="n">mnemonic</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 派生密鑰</span>
        <span class="k">let</span> <span class="n">seed</span> <span class="o">=</span> <span class="n">mnemonic</span><span class="nf">.to_seed</span><span class="p">(</span><span class="n">passphrase</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">xprv</span> <span class="o">=</span> <span class="nn">Xpriv</span><span class="p">::</span><span class="nf">new_master</span><span class="p">(</span><span class="n">network</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">seed</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 構建描述符</span>
        <span class="k">let</span> <span class="n">coin_type</span> <span class="o">=</span> <span class="k">if</span> <span class="n">network</span> <span class="o">==</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span> <span class="p">{</span> <span class="s">"0"</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="s">"1"</span> <span class="p">};</span>
        <span class="k">let</span> <span class="n">external</span> <span class="o">=</span> <span class="nd">format!</span><span class="p">(</span><span class="s">"wpkh({}/84'/{}'/ 0'/0/*)"</span><span class="p">,</span> <span class="n">xprv</span><span class="p">,</span> <span class="n">coin_type</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">internal</span> <span class="o">=</span> <span class="nd">format!</span><span class="p">(</span><span class="s">"wpkh({}/84'/{}'/0'/1/*)"</span><span class="p">,</span> <span class="n">xprv</span><span class="p">,</span> <span class="n">coin_type</span><span class="p">);</span>

        <span class="c1">// 創建數據庫</span>
        <span class="k">let</span> <span class="n">db_path</span> <span class="o">=</span> <span class="n">data_dir</span><span class="nf">.join</span><span class="p">(</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{}.db"</span><span class="p">,</span> <span class="n">name</span><span class="p">));</span>
        <span class="k">let</span> <span class="n">database</span> <span class="o">=</span> <span class="nn">SqliteDatabase</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;</span><span class="n">db_path</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 創建錢包</span>
        <span class="k">let</span> <span class="n">wallet</span> <span class="o">=</span> <span class="nn">Wallet</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;</span><span class="n">external</span><span class="p">,</span> <span class="nf">Some</span><span class="p">(</span><span class="o">&amp;</span><span class="n">internal</span><span class="p">),</span> <span class="n">network</span><span class="p">,</span> <span class="n">database</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 連接區塊鏈</span>
        <span class="k">let</span> <span class="n">electrum_url</span> <span class="o">=</span> <span class="k">match</span> <span class="n">network</span> <span class="p">{</span>
            <span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span> <span class="k">=&gt;</span> <span class="s">"ssl://electrum.blockstream.info:60002"</span><span class="p">,</span>
            <span class="n">_</span> <span class="k">=&gt;</span> <span class="s">"ssl://electrum.blockstream.info:60002"</span><span class="p">,</span>
        <span class="p">};</span>
        <span class="k">let</span> <span class="n">blockchain</span> <span class="o">=</span> <span class="nn">ElectrumBlockchain</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span>
            <span class="nn">electrum_client</span><span class="p">::</span><span class="nn">Client</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">electrum_url</span><span class="p">)</span><span class="o">?</span>
        <span class="p">);</span>

        <span class="nf">Ok</span><span class="p">(</span><span class="k">Self</span> <span class="p">{</span> <span class="n">wallet</span><span class="p">,</span> <span class="n">blockchain</span><span class="p">,</span> <span class="n">network</span> <span class="p">})</span>
    <span class="p">}</span>

    <span class="cd">/// 同步錢包</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">sync</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">self</span><span class="py">.wallet</span><span class="nf">.sync</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="py">.blockchain</span><span class="p">,</span> <span class="nn">SyncOptions</span><span class="p">::</span><span class="nf">default</span><span class="p">())</span><span class="o">?</span><span class="p">;</span>
        <span class="nf">Ok</span><span class="p">(())</span>
    <span class="p">}</span>

    <span class="cd">/// 獲取餘額</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">balance</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Balance</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">b</span> <span class="o">=</span> <span class="k">self</span><span class="py">.wallet</span><span class="nf">.get_balance</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="n">Balance</span> <span class="p">{</span>
            <span class="n">confirmed</span><span class="p">:</span> <span class="n">b</span><span class="py">.confirmed</span><span class="p">,</span>
            <span class="n">pending</span><span class="p">:</span> <span class="n">b</span><span class="py">.untrusted_pending</span> <span class="o">+</span> <span class="n">b</span><span class="py">.trusted_pending</span><span class="p">,</span>
        <span class="p">})</span>
    <span class="p">}</span>

    <span class="cd">/// 獲取新地址</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new_address</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Address</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="nf">Ok</span><span class="p">(</span><span class="k">self</span><span class="py">.wallet</span><span class="nf">.get_address</span><span class="p">(</span><span class="nn">AddressIndex</span><span class="p">::</span><span class="n">New</span><span class="p">)</span><span class="o">?</span><span class="py">.address</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="cd">/// 發送交易</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">send</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">self</span><span class="p">,</span>
        <span class="n">recipient</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Address</span><span class="p">,</span>
        <span class="n">amount</span><span class="p">:</span> <span class="n">Amount</span><span class="p">,</span>
        <span class="n">fee_rate</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Txid</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// 驗證</span>
        <span class="k">if</span> <span class="o">!</span><span class="n">recipient</span><span class="nf">.is_valid_for_network</span><span class="p">(</span><span class="k">self</span><span class="py">.network</span><span class="p">)</span> <span class="p">{</span>
            <span class="nn">anyhow</span><span class="p">::</span><span class="nd">bail!</span><span class="p">(</span><span class="s">"地址網路不匹配"</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="c1">// 構建交易</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">self</span><span class="py">.wallet</span><span class="nf">.build_tx</span><span class="p">();</span>
        <span class="n">builder</span>
            <span class="nf">.add_recipient</span><span class="p">(</span><span class="n">recipient</span><span class="nf">.script_pubkey</span><span class="p">(),</span> <span class="n">amount</span><span class="nf">.to_sat</span><span class="p">())</span>
            <span class="nf">.fee_rate</span><span class="p">(</span><span class="nn">FeeRate</span><span class="p">::</span><span class="nf">from_sat_per_vb</span><span class="p">(</span><span class="n">fee_rate</span><span class="p">))</span>
            <span class="nf">.enable_rbf</span><span class="p">();</span>

        <span class="k">let</span> <span class="p">(</span><span class="k">mut</span> <span class="n">psbt</span><span class="p">,</span> <span class="n">_</span><span class="p">)</span> <span class="o">=</span> <span class="n">builder</span><span class="nf">.finish</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 簽名</span>
        <span class="k">self</span><span class="py">.wallet</span><span class="nf">.sign</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">psbt</span><span class="p">,</span> <span class="nn">SignOptions</span><span class="p">::</span><span class="nf">default</span><span class="p">())</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 廣播</span>
        <span class="k">let</span> <span class="n">tx</span> <span class="o">=</span> <span class="n">psbt</span><span class="nf">.extract_tx</span><span class="p">();</span>
        <span class="k">self</span><span class="py">.blockchain</span><span class="nf">.broadcast</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tx</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

        <span class="nf">Ok</span><span class="p">(</span><span class="n">tx</span><span class="nf">.compute_txid</span><span class="p">())</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">Balance</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">confirmed</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">pending</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="42-命令列介面">4.2 命令列介面</h3>

<p>使用 <code class="language-plaintext highlighter-rouge">clap</code> crate 可以輕鬆創建命令列介面：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">clap</span><span class="p">::{</span><span class="n">Parser</span><span class="p">,</span> <span class="n">Subcommand</span><span class="p">};</span>

<span class="nd">#[derive(Parser)]</span>
<span class="nd">#[command(name</span> <span class="nd">=</span> <span class="s">"btc-wallet"</span><span class="nd">)]</span>
<span class="nd">#[command(about</span> <span class="nd">=</span> <span class="s">"簡單的 Bitcoin 錢包"</span><span class="nd">)]</span>
<span class="k">struct</span> <span class="n">Cli</span> <span class="p">{</span>
    <span class="nd">#[command(subcommand)]</span>
    <span class="n">command</span><span class="p">:</span> <span class="n">Commands</span><span class="p">,</span>

    <span class="nd">#[arg(long,</span> <span class="nd">default_value</span> <span class="nd">=</span> <span class="s">"testnet"</span><span class="nd">)]</span>
    <span class="n">network</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>

    <span class="nd">#[arg(long,</span> <span class="nd">default_value</span> <span class="nd">=</span> <span class="s">"~/.btc-wallet"</span><span class="nd">)]</span>
    <span class="n">data_dir</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
<span class="p">}</span>

<span class="nd">#[derive(Subcommand)]</span>
<span class="k">enum</span> <span class="n">Commands</span> <span class="p">{</span>
    <span class="cd">/// 創建新錢包</span>
    <span class="n">Create</span> <span class="p">{</span> <span class="n">name</span><span class="p">:</span> <span class="nb">String</span> <span class="p">},</span>

    <span class="cd">/// 從助記詞恢復</span>
    <span class="n">Restore</span> <span class="p">{</span> <span class="n">name</span><span class="p">:</span> <span class="nb">String</span> <span class="p">},</span>

    <span class="cd">/// 顯示餘額</span>
    <span class="n">Balance</span> <span class="p">{</span> <span class="n">name</span><span class="p">:</span> <span class="nb">String</span> <span class="p">},</span>

    <span class="cd">/// 獲取新地址</span>
    <span class="n">Address</span> <span class="p">{</span> <span class="n">name</span><span class="p">:</span> <span class="nb">String</span> <span class="p">},</span>

    <span class="cd">/// 發送 Bitcoin</span>
    <span class="nb">Send</span> <span class="p">{</span>
        <span class="n">name</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
        <span class="n">to</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
        <span class="n">amount</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
        <span class="nd">#[arg(default_value</span> <span class="nd">=</span> <span class="s">"10"</span><span class="nd">)]</span>
        <span class="n">fee_rate</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span>
    <span class="p">},</span>

    <span class="cd">/// 列出交易</span>
    <span class="n">History</span> <span class="p">{</span> <span class="n">name</span><span class="p">:</span> <span class="nb">String</span> <span class="p">},</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">cli</span> <span class="o">=</span> <span class="nn">Cli</span><span class="p">::</span><span class="nf">parse</span><span class="p">();</span>

    <span class="k">match</span> <span class="n">cli</span><span class="py">.command</span> <span class="p">{</span>
        <span class="nn">Commands</span><span class="p">::</span><span class="n">Create</span> <span class="p">{</span> <span class="n">name</span> <span class="p">}</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="c1">// 生成助記詞並創建錢包</span>
            <span class="k">let</span> <span class="n">mnemonic</span> <span class="o">=</span> <span class="nn">Mnemonic</span><span class="p">::</span><span class="nf">generate</span><span class="p">(</span><span class="mi">12</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
            <span class="nd">println!</span><span class="p">(</span><span class="s">"您的助記詞（請安全備份）:</span><span class="se">\n</span><span class="s">{}"</span><span class="p">,</span> <span class="n">mnemonic</span><span class="p">);</span>

            <span class="k">let</span> <span class="n">wallet</span> <span class="o">=</span> <span class="nn">WalletApp</span><span class="p">::</span><span class="nf">create</span><span class="p">(</span>
                <span class="o">&amp;</span><span class="n">name</span><span class="p">,</span>
                <span class="o">&amp;</span><span class="n">mnemonic</span><span class="nf">.to_string</span><span class="p">(),</span>
                <span class="s">""</span><span class="p">,</span>
                <span class="nf">parse_network</span><span class="p">(</span><span class="o">&amp;</span><span class="n">cli</span><span class="py">.network</span><span class="p">)</span><span class="o">?</span><span class="p">,</span>
                <span class="nn">Path</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;</span><span class="n">cli</span><span class="py">.data_dir</span><span class="p">),</span>
            <span class="p">)</span><span class="o">?</span><span class="p">;</span>

            <span class="k">let</span> <span class="n">address</span> <span class="o">=</span> <span class="n">wallet</span><span class="nf">.new_address</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
            <span class="nd">println!</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">錢包已創建"</span><span class="p">);</span>
            <span class="nd">println!</span><span class="p">(</span><span class="s">"您的第一個地址: {}"</span><span class="p">,</span> <span class="n">address</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="nn">Commands</span><span class="p">::</span><span class="n">Balance</span> <span class="p">{</span> <span class="n">name</span> <span class="p">}</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">wallet</span> <span class="o">=</span> <span class="nf">load_wallet</span><span class="p">(</span><span class="o">&amp;</span><span class="n">name</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">cli</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
            <span class="n">wallet</span><span class="nf">.sync</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
            <span class="k">let</span> <span class="n">balance</span> <span class="o">=</span> <span class="n">wallet</span><span class="nf">.balance</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
            <span class="nd">println!</span><span class="p">(</span><span class="s">"已確認: {} sat"</span><span class="p">,</span> <span class="n">balance</span><span class="py">.confirmed</span><span class="p">);</span>
            <span class="nd">println!</span><span class="p">(</span><span class="s">"待確認: {} sat"</span><span class="p">,</span> <span class="n">balance</span><span class="py">.pending</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="c1">// ... 其他命令 ...</span>
    <span class="p">}</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="5-實戰專案支付處理器">5. 實戰專案：支付處理器</h2>

<p>讓我們構建一個簡單的支付處理器，展示如何將所學知識應用到實際場景。</p>

<h3 id="51-設計">5.1 設計</h3>

<p>支付處理器需要：</p>
<ol>
  <li>為每筆訂單生成唯一的支付地址</li>
  <li>監控地址的入帳</li>
  <li>確認付款後通知商家</li>
</ol>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="k">pub</span> <span class="k">struct</span> <span class="n">PaymentProcessor</span> <span class="p">{</span>
    <span class="n">wallet</span><span class="p">:</span> <span class="n">WalletApp</span><span class="p">,</span>
    <span class="n">pending_payments</span><span class="p">:</span> <span class="n">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span> <span class="n">PaymentRequest</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">PaymentRequest</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">order_id</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">address</span><span class="p">:</span> <span class="n">Address</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">amount</span><span class="p">:</span> <span class="n">Amount</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">expires_at</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">callback_url</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">PaymentConfirmation</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">order_id</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">txid</span><span class="p">:</span> <span class="n">Txid</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">amount</span><span class="p">:</span> <span class="n">Amount</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">confirmations</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="52-創建支付請求">5.2 創建支付請求</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="rouge-code"><pre><span class="k">impl</span> <span class="n">PaymentProcessor</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">create_payment</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span>
        <span class="n">order_id</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
        <span class="n">amount</span><span class="p">:</span> <span class="n">Amount</span><span class="p">,</span>
        <span class="n">callback_url</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
        <span class="n">ttl_seconds</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">PaymentRequest</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// 生成新地址</span>
        <span class="k">let</span> <span class="n">address</span> <span class="o">=</span> <span class="k">self</span><span class="py">.wallet</span><span class="nf">.new_address</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 計算過期時間</span>
        <span class="k">let</span> <span class="n">now</span> <span class="o">=</span> <span class="nn">SystemTime</span><span class="p">::</span><span class="nf">now</span><span class="p">()</span>
            <span class="nf">.duration_since</span><span class="p">(</span><span class="n">UNIX_EPOCH</span><span class="p">)</span><span class="o">?</span>
            <span class="nf">.as_secs</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">expires_at</span> <span class="o">=</span> <span class="n">now</span> <span class="o">+</span> <span class="n">ttl_seconds</span><span class="p">;</span>

        <span class="k">let</span> <span class="n">request</span> <span class="o">=</span> <span class="n">PaymentRequest</span> <span class="p">{</span>
            <span class="n">order_id</span><span class="p">:</span> <span class="n">order_id</span><span class="nf">.clone</span><span class="p">(),</span>
            <span class="n">address</span><span class="p">:</span> <span class="n">address</span><span class="nf">.clone</span><span class="p">(),</span>
            <span class="n">amount</span><span class="p">,</span>
            <span class="n">expires_at</span><span class="p">,</span>
            <span class="n">callback_url</span><span class="p">,</span>
        <span class="p">};</span>

        <span class="c1">// 存儲待處理支付</span>
        <span class="k">self</span><span class="py">.pending_payments</span><span class="nf">.insert</span><span class="p">(</span><span class="n">order_id</span><span class="p">,</span> <span class="n">request</span><span class="nf">.clone</span><span class="p">());</span>

        <span class="nf">Ok</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="53-監控支付">5.3 監控支付</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
</pre></td><td class="rouge-code"><pre><span class="k">impl</span> <span class="n">PaymentProcessor</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">async</span> <span class="k">fn</span> <span class="nf">monitor_payments</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">interval</span> <span class="o">=</span> <span class="nn">tokio</span><span class="p">::</span><span class="nn">time</span><span class="p">::</span><span class="nf">interval</span><span class="p">(</span><span class="nn">Duration</span><span class="p">::</span><span class="nf">from_secs</span><span class="p">(</span><span class="mi">30</span><span class="p">));</span>

        <span class="k">loop</span> <span class="p">{</span>
            <span class="n">interval</span><span class="nf">.tick</span><span class="p">()</span><span class="k">.await</span><span class="p">;</span>

            <span class="c1">// 同步錢包</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="o">=</span> <span class="k">self</span><span class="py">.wallet</span><span class="nf">.sync</span><span class="p">()</span> <span class="p">{</span>
                <span class="nd">eprintln!</span><span class="p">(</span><span class="s">"同步錯誤: {}"</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
                <span class="k">continue</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="c1">// 檢查每個待處理支付</span>
            <span class="k">let</span> <span class="n">now</span> <span class="o">=</span> <span class="nn">SystemTime</span><span class="p">::</span><span class="nf">now</span><span class="p">()</span>
                <span class="nf">.duration_since</span><span class="p">(</span><span class="n">UNIX_EPOCH</span><span class="p">)</span>
                <span class="nf">.unwrap</span><span class="p">()</span>
                <span class="nf">.as_secs</span><span class="p">();</span>

            <span class="k">let</span> <span class="k">mut</span> <span class="n">completed</span> <span class="o">=</span> <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">expired</span> <span class="o">=</span> <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

            <span class="k">for</span> <span class="p">(</span><span class="n">order_id</span><span class="p">,</span> <span class="n">request</span><span class="p">)</span> <span class="k">in</span> <span class="o">&amp;</span><span class="k">self</span><span class="py">.pending_payments</span> <span class="p">{</span>
                <span class="c1">// 檢查過期</span>
                <span class="k">if</span> <span class="n">now</span> <span class="o">&gt;</span> <span class="n">request</span><span class="py">.expires_at</span> <span class="p">{</span>
                    <span class="n">expired</span><span class="nf">.push</span><span class="p">(</span><span class="n">order_id</span><span class="nf">.clone</span><span class="p">());</span>
                    <span class="k">continue</span><span class="p">;</span>
                <span class="p">}</span>

                <span class="c1">// 檢查是否收到付款</span>
                <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">confirmation</span><span class="p">)</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.check_payment</span><span class="p">(</span><span class="n">request</span><span class="p">)</span> <span class="p">{</span>
                    <span class="k">if</span> <span class="n">confirmation</span><span class="py">.confirmations</span> <span class="o">&gt;=</span> <span class="mi">1</span> <span class="p">{</span>
                        <span class="k">self</span><span class="nf">.notify_payment</span><span class="p">(</span><span class="o">&amp;</span><span class="n">request</span><span class="py">.callback_url</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">confirmation</span><span class="p">)</span><span class="k">.await</span><span class="p">;</span>
                        <span class="n">completed</span><span class="nf">.push</span><span class="p">(</span><span class="n">order_id</span><span class="nf">.clone</span><span class="p">());</span>
                    <span class="p">}</span>
                <span class="p">}</span>
            <span class="p">}</span>

            <span class="c1">// 清理已完成和過期的支付</span>
            <span class="k">for</span> <span class="n">id</span> <span class="k">in</span> <span class="n">completed</span> <span class="p">{</span>
                <span class="k">self</span><span class="py">.pending_payments</span><span class="nf">.remove</span><span class="p">(</span><span class="o">&amp;</span><span class="n">id</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="k">for</span> <span class="n">id</span> <span class="k">in</span> <span class="n">expired</span> <span class="p">{</span>
                <span class="k">self</span><span class="py">.pending_payments</span><span class="nf">.remove</span><span class="p">(</span><span class="o">&amp;</span><span class="n">id</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">check_payment</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">request</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PaymentRequest</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="n">PaymentConfirmation</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// 查詢地址的交易</span>
        <span class="c1">// 檢查是否有符合金額的入帳</span>
        <span class="c1">// 返回確認信息</span>
        <span class="nb">None</span>
    <span class="p">}</span>

    <span class="k">async</span> <span class="k">fn</span> <span class="nf">notify_payment</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">url</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span> <span class="n">confirmation</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PaymentConfirmation</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 發送 HTTP 回調通知商家</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="6-測試策略">6. 測試策略</h2>

<h3 id="61-單元測試">6.1 單元測試</h3>

<p>單元測試應該覆蓋所有核心邏輯，不依賴外部服務。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
</pre></td><td class="rouge-code"><pre><span class="nd">#[cfg(test)]</span>
<span class="k">mod</span> <span class="n">tests</span> <span class="p">{</span>
    <span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">test_address_generation</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">mnemonic</span> <span class="o">=</span> <span class="s">"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"</span><span class="p">;</span>
        <span class="c1">// 這是一個已知的測試助記詞，可以驗證派生結果</span>

        <span class="k">let</span> <span class="n">seed</span> <span class="o">=</span> <span class="nn">Mnemonic</span><span class="p">::</span><span class="nf">parse</span><span class="p">(</span><span class="n">mnemonic</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">()</span><span class="nf">.to_seed</span><span class="p">(</span><span class="s">""</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">xprv</span> <span class="o">=</span> <span class="nn">Xpriv</span><span class="p">::</span><span class="nf">new_master</span><span class="p">(</span><span class="nn">Network</span><span class="p">::</span><span class="n">Testnet</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">seed</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

        <span class="c1">// 驗證派生的地址符合預期</span>
    <span class="p">}</span>

    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">test_amount_validation</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 有效金額</span>
        <span class="nd">assert!</span><span class="p">(</span><span class="nf">validate_amount</span><span class="p">(</span><span class="mi">1000</span><span class="p">,</span> <span class="mi">546</span><span class="p">,</span> <span class="mi">1_000_000</span><span class="p">)</span><span class="nf">.is_ok</span><span class="p">());</span>

        <span class="c1">// 低於粉塵限制</span>
        <span class="nd">assert!</span><span class="p">(</span><span class="nf">validate_amount</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">546</span><span class="p">,</span> <span class="mi">1_000_000</span><span class="p">)</span><span class="nf">.is_err</span><span class="p">());</span>

        <span class="c1">// 超過上限</span>
        <span class="nd">assert!</span><span class="p">(</span><span class="nf">validate_amount</span><span class="p">(</span><span class="mi">10_000_000</span><span class="p">,</span> <span class="mi">546</span><span class="p">,</span> <span class="mi">1_000_000</span><span class="p">)</span><span class="nf">.is_err</span><span class="p">());</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="62-regtest-整合測試">6.2 Regtest 整合測試</h3>

<p>對於需要實際區塊鏈互動的測試，使用 regtest 網路。Regtest 是一個本地測試網路，你可以隨時生成區塊，非常適合測試。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="c"># 啟動 regtest 節點</span>
bitcoind <span class="nt">-regtest</span> <span class="nt">-daemon</span>

<span class="c"># 生成區塊</span>
bitcoin-cli <span class="nt">-regtest</span> generatetoaddress 101 &lt;your-address&gt;
</pre></td></tr></tbody></table></code></pre></div></div>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="nd">#[cfg(test)]</span>
<span class="k">mod</span> <span class="n">integration_tests</span> <span class="p">{</span>
    <span class="nd">#[test]</span>
    <span class="nd">#[ignore]</span>  <span class="c1">// 需要 regtest 環境</span>
    <span class="k">fn</span> <span class="nf">test_full_transaction_flow</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 1. 連接到 regtest 節點</span>
        <span class="c1">// 2. 生成一些區塊以獲得可花費的 UTXO</span>
        <span class="c1">// 3. 創建並發送交易</span>
        <span class="c1">// 4. 生成區塊確認交易</span>
        <span class="c1">// 5. 驗證結果</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="7-總結">7. 總結</h2>

<p>本系列帶你從零開始學習使用 Rust 開發 Bitcoin 應用。我們涵蓋了：</p>

<p><strong>第一篇</strong>：Rust 環境設置、rust-bitcoin 基礎類型、密碼學原語</p>

<p><strong>第二篇</strong>：HD 錢包、地址生成、UTXO 模型、交易構建</p>

<p><strong>第三篇</strong>：Bitcoin Script、多簽、ECDSA 和 Schnorr 簽名、Miniscript</p>

<p><strong>第四篇</strong>：節點互動、BDK 錢包開發、安全最佳實踐</p>

<h3 id="進一步學習">進一步學習</h3>

<p>掌握了這些基礎後，你可以繼續探索：</p>

<ul>
  <li><strong>Taproot 深入</strong>：MAST、腳本樹、MuSig2</li>
  <li><strong>閃電網路</strong>：使用 LDK（Lightning Development Kit）</li>
  <li><strong>隱私技術</strong>：CoinJoin、PayJoin、Silent Payments</li>
  <li><strong>Layer 2</strong>：狀態通道、Rollups</li>
</ul>

<p>Bitcoin 是一個不斷發展的生態系統。保持學習，關注 BIP 提案和社區討論，你會發現這個領域有無限的可能性。</p>

<hr />

<h2 id="參考資源">參考資源</h2>

<h3 id="官方文檔">官方文檔</h3>
<ul>
  <li><a href="https://docs.rs/bitcoin">rust-bitcoin</a></li>
  <li><a href="https://docs.rs/bdk">BDK</a></li>
  <li><a href="https://docs.rs/lightning">LDK</a></li>
</ul>

<h3 id="學習資源">學習資源</h3>
<ul>
  <li><a href="https://developer.bitcoin.org/">Bitcoin Developer Guide</a></li>
  <li><a href="https://learnmeabitcoin.com/">Learn Me a Bitcoin</a></li>
  <li><a href="https://github.com/bitcoinbook/bitcoinbook">Mastering Bitcoin</a></li>
</ul>

<h3 id="社群">社群</h3>
<ul>
  <li><a href="https://github.com/rust-bitcoin">rust-bitcoin GitHub</a></li>
  <li><a href="https://discord.gg/dstn4dQ">BDK Discord</a></li>
  <li><a href="https://bitcoin.stackexchange.com/">Bitcoin StackExchange</a></li>
</ul>

<hr />

<p>恭喜完成 Rust Bitcoin 開發入門系列！你現在有了構建 Bitcoin 應用的堅實基礎。實踐是最好的學習方式——開始構建你的第一個專案吧！</p>]]></content><author><name>Cypherpunks Taiwan</name></author><category term="技術教學" /><category term="Bitcoin" /><category term="Rust" /><category term="Rust" /><category term="Bitcoin" /><category term="RPC" /><category term="Electrum" /><category term="BDK" /><category term="錢包開發" /><category term="節點互動" /><summary type="html"><![CDATA[這是 Rust Bitcoin 開發入門系列的最後一篇。本篇探討如何構建完整的 Bitcoin 應用，包括與節點互動、錢包開發和實際部署考量。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" /><media:content medium="image" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Cypherpunks Taiwan 重啟：我們為何再次聚集？</title><link href="https://cypherpunks-core.github.io/news/community/2025/03/24/Cypherpunks-Taiwan-%E9%87%8D%E5%95%9F-%E6%88%91%E5%80%91%E7%82%BA%E4%BD%95%E5%86%8D%E6%AC%A1%E8%81%9A%E9%9B%86/" rel="alternate" type="text/html" title="Cypherpunks Taiwan 重啟：我們為何再次聚集？" /><published>2025-03-24T00:00:00+00:00</published><updated>2025-03-24T00:00:00+00:00</updated><id>https://cypherpunks-core.github.io/news/community/2025/03/24/Cypherpunks%20Taiwan%20%E9%87%8D%E5%95%9F%EF%BC%9A%E6%88%91%E5%80%91%E7%82%BA%E4%BD%95%E5%86%8D%E6%AC%A1%E8%81%9A%E9%9B%86%EF%BC%9F</id><content type="html" xml:base="https://cypherpunks-core.github.io/news/community/2025/03/24/Cypherpunks-Taiwan-%E9%87%8D%E5%95%9F-%E6%88%91%E5%80%91%E7%82%BA%E4%BD%95%E5%86%8D%E6%AC%A1%E8%81%9A%E9%9B%86/"><![CDATA[<p>從 2019 到現在，世界與網路都變了很多，但<strong>我們對隱私的渴望沒有改變</strong>。在數位身份、鏈上足跡與 AI 大模型全面滲透的時代，<strong>Cypherpunks 的核心信念——Privacy is necessary for an open society in the electronic age</strong>，從未如此重要。這些年來，我們見證了<a href="/tags/bitcoin">[比特幣](/tags/bitcoin)</a>的主流化、Web3 的爆炸成長，也見證了過度中心化平台對個人自由與資訊掌控權的反撲。是時候重新聚集了。</p>

<h2 id="為什麼重啟-cypherpunks-taiwan">為什麼重啟 Cypherpunks Taiwan？</h2>

<p>Cypherpunks Taiwan 並不是為了「追逐熱點」，我們想建立的，是一個屬於台灣技術社群的節點——一個可以討論<a href="/tags/cryptography">[密碼學](/tags/cryptography)</a>、隱私工具、抗審查機制、去中心化協議的地方，一個能吸引真正關心自由與技術的你加入的社群。</p>

<p>這一次，我們的目標不只是討論技術，更希望拓展邊界、<strong>吸引對抗中心化壓力的潛在同盟者</strong>，不論你是否來自<a href="/tags/blockchain">[區塊鏈](/tags/blockchain)</a>產業，只要你在思考如何用科技解決問題、保護自由，那你就是我們想認識的對象。</p>

<h2 id="我們想討論的是這些問題">我們想討論的，是這些問題：</h2>

<ul>
  <li>在現代網路架構下，<strong>隱私還有多少選項？</strong></li>
  <li>去中心化的理想，<strong>如何與現實世界的運營模式協作？</strong></li>
  <li>面對鏈上永久紀錄與實名制風潮，<strong>我們還能怎麼設計新的技術解法？</strong></li>
  <li>「做自由的工具」與「成為主流產品」之間，有沒有第三條路？</li>
</ul>

<h2 id="與我們一起分享這些問題的講者們">與我們一起分享這些問題的講者們</h2>

<h3 id="dr-awesome-doge--founder-cypherpunks-taiwan">Dr. Awesome Doge | Founder @Cypherpunks Taiwan</h3>

<p>Cypherpunks Taiwan 創辦人，自 2014 年開始投身比特幣與加密貨幣領域，早期即深度參與去中心化技術的推廣與社群建設。身為全球最大中文加密社群「比特幣中文社團」的共同創辦人之一，成功凝聚來自全球華語圈的愛好者與開發者，截至目前社團成員已超過 16.9 萬人，成為華語世界最具影響力、最活躍的加密社群之一</p>

<h3 id="max-wuco-founder--hackmd">Max Wu｜Co-Founder @ HackMD</h3>

<p>HackMD 共同創辦人暨 CEO，在網路產業與開源社群擁有豐富經驗。HackMD 是一個協作式 Markdown 編輯器，促進團隊與個人之間的知識共享，最近推出的文件夾功能更進一步提升了使用者體驗。</p>

<p>🔗 <a href="https://hackmd.io/@MaxWu/HackMD-and-open-source">延伸閱讀</a></p>

<h3 id="林旅強richard-linco-founder--開源社">林旅強（Richard Lin）｜Co-Founder @ 開源社</h3>

<p>資深開源推動者，曾任 COSCUP 志工，並於中研院自由軟體鑄造場深耕多年。2014 年聯合創辦中國開源社，後任職華為近八年專注開源與開發者生態建設，現為 <a href="http://01.AI">01.AI</a>（創辦人李開復）開源暨開發者關係負責人。</p>

<h3 id="李佳憲neil-lee執行董事--echox">李佳憲（Neil Lee）｜執行董事 @ EchoX</h3>

<p>LeadBest Consulting Group 共同創辦人與前執行長，長期推動數位轉型、Web3 發展與開源創新。過去曾創辦時間軸科技、協助建立 friDay 購物等平台，並活躍於敏捷文化與去中心化應用的落地實踐。</p>

<p>🔗 <a href="https://www.facebook.com/chiahsienl">延伸閱讀</a></p>

<h2 id="邀請你加入cypherpunks-小聚--make-privacy-great-again">邀請你加入：#Cypherpunks 小聚 — Make Privacy Great Again</h2>

<p>我們將在 Tempo House 辦一場開放的技術小聚，邀請過去在台灣推動開源、去中心化與隱私技術的講者現身分享，也會安排足夠的交流時間，讓我們彼此認識、重新建立這個社群的連結。</p>

<h3 id="活動資訊">活動資訊</h3>

<p>📅 <strong>時間</strong>：3 月 31 日（一）18:00 - 22:30<br />
📍 <strong>地點</strong>：Tempo House 二樓（台北市中山區建國北路二段 12 號）<br />
🔗 <strong>報名</strong>：<a href="https://www.meetup.com/taipei-bitcoin-meetup-group/events/306823746/">活動報名連結</a></p>

<p>我們相信，<strong>Cypherpunks write code</strong>——但更重要的是，他們也聚在一起，互相認識，互相支持。</p>

<p>這場重啟，會是我們的第一步。等你來。</p>]]></content><author><name>Cypherpunks Taiwan</name></author><category term="news" /><category term="community" /><category term="cypherpunks" /><category term="privacy" /><category term="decentralization" /><category term="technology" /><category term="community" /><summary type="html"><![CDATA[Cypherpunks Taiwan 重啟，探討在數位身份、鏈上足跡與 AI 大模型全面滲透的時代，如何保護隱私與自由。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cypherpunks-core.github.io/img/150.jpg" /><media:content medium="image" url="https://cypherpunks-core.github.io/img/150.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Rust Bitcoin 開發入門（三）：腳本與簽名</title><link href="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/rust/2025/03/24/rust-bitcoin-tutorial-3-scripts-signatures/" rel="alternate" type="text/html" title="Rust Bitcoin 開發入門（三）：腳本與簽名" /><published>2025-03-24T00:00:00+00:00</published><updated>2025-03-24T00:00:00+00:00</updated><id>https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/rust/2025/03/24/rust-bitcoin-tutorial-3-scripts-signatures</id><content type="html" xml:base="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/rust/2025/03/24/rust-bitcoin-tutorial-3-scripts-signatures/"><![CDATA[<p>這是 Rust Bitcoin 開發入門系列的第三篇。本篇深入探討 Bitcoin Script 編程和各種簽名機制的 Rust 實現。</p>

<p><strong>系列文章導航：</strong></p>
<ul>
  <li><a href="/2025/03/22/rust-bitcoin-tutorial-1-environment-basics/">第一篇：環境設置與基礎概念</a></li>
  <li><a href="/2025/03/23/rust-bitcoin-tutorial-2-addresses-transactions/">第二篇：地址生成與交易構建</a></li>
  <li><strong>第三篇：腳本與簽名</strong>（本篇）</li>
  <li><a href="/2025/03/25/rust-bitcoin-tutorial-4-advanced-applications/">第四篇：進階應用與整合</a></li>
</ul>

<hr />

<h2 id="1-理解-bitcoin-script">1. 理解 Bitcoin Script</h2>

<h3 id="11-什麼是-bitcoin-script">1.1 什麼是 Bitcoin Script</h3>

<p>Bitcoin Script 是 Bitcoin 的程式語言，用於定義資金的花費條件。每一筆 Bitcoin 輸出都被一個腳本「鎖定」，而花費這筆輸出需要提供一個能夠「解鎖」它的腳本。</p>

<p>Script 的設計有幾個重要特點。首先，它是基於堆疊（stack）的語言，類似於 Forth。操作數被推入堆疊，操作碼則從堆疊中取出操作數並將結果推回。其次，它故意不是圖靈完備的——沒有循環結構，執行時間是可預測的。這確保了節點可以快速驗證交易，不會被惡意的無限循環攻擊。</p>

<p>理解 Script 對於 Bitcoin 開發很重要，因為它是所有智能合約功能的基礎。從簡單的單簽名支付到複雜的多重簽名、時間鎖和條件支付，都是透過 Script 實現的。</p>

<h3 id="12-腳本執行模型">1.2 腳本執行模型</h3>

<p>當驗證一筆交易時，節點會執行以下過程：</p>

<ol>
  <li>將花費者提供的解鎖腳本（scriptSig 或 witness）執行，結果留在堆疊上</li>
  <li>然後執行被花費輸出的鎖定腳本（scriptPubKey）</li>
  <li>如果執行完成後堆疊頂部是「真」（非零值），且沒有發生錯誤，則驗證通過</li>
</ol>

<p>這種「先解鎖，後驗證」的模型讓花費者可以提供滿足條件的數據（如簽名），然後由鎖定腳本驗證這些數據是否正確。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">script</span><span class="p">::{</span><span class="n">Script</span><span class="p">,</span> <span class="n">ScriptBuf</span><span class="p">,</span> <span class="n">Builder</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">opcodes</span><span class="p">::</span><span class="nn">all</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">script_execution_demo</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// P2PKH 的鎖定腳本：</span>
    <span class="c1">// OP_DUP OP_HASH160 &lt;pubkey_hash&gt; OP_EQUALVERIFY OP_CHECKSIG</span>

    <span class="c1">// 執行過程：</span>
    <span class="c1">// 1. 解鎖腳本推入：&lt;signature&gt; &lt;pubkey&gt;</span>
    <span class="c1">// 2. OP_DUP：複製堆疊頂部的 pubkey</span>
    <span class="c1">// 3. OP_HASH160：對複製的 pubkey 計算 hash</span>
    <span class="c1">// 4. 推入 pubkey_hash</span>
    <span class="c1">// 5. OP_EQUALVERIFY：比較兩個 hash，不相等則失敗</span>
    <span class="c1">// 6. OP_CHECKSIG：使用 signature 和 pubkey 驗證簽名</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="13-使用-rust-構建腳本">1.3 使用 Rust 構建腳本</h3>

<p>rust-bitcoin 提供了多種構建腳本的方式。<code class="language-plaintext highlighter-rouge">Builder</code> 類型讓你可以逐步添加操作碼和數據：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">script</span><span class="p">::{</span><span class="n">Script</span><span class="p">,</span> <span class="n">ScriptBuf</span><span class="p">,</span> <span class="n">Builder</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">opcodes</span><span class="p">::</span><span class="nn">all</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">build_scripts</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 使用 Builder 構建 P2PKH 腳本</span>
    <span class="k">let</span> <span class="n">pubkey_hash</span> <span class="o">=</span> <span class="nn">hex</span><span class="p">::</span><span class="nf">decode</span><span class="p">(</span><span class="s">"89abcdefabbaabbaabbaabbaabbaabbaabbaabba"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="k">let</span> <span class="n">p2pkh_script</span> <span class="o">=</span> <span class="nn">Builder</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_DUP</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_HASH160</span><span class="p">)</span>
        <span class="nf">.push_slice</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pubkey_hash</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_EQUALVERIFY</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_CHECKSIG</span><span class="p">)</span>
        <span class="nf">.into_script</span><span class="p">();</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"Script: {}"</span><span class="p">,</span> <span class="n">p2pkh_script</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"ASM: {}"</span><span class="p">,</span> <span class="n">p2pkh_script</span><span class="nf">.to_asm_string</span><span class="p">());</span>

    <span class="c1">// 也可以使用便捷函數</span>
    <span class="k">let</span> <span class="n">pubkey_hash</span> <span class="o">=</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">PubkeyHash</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span>
        <span class="s">"89abcdefabbaabbaabbaabbaabbaabbaabbaabba"</span>
    <span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">p2pkh_shortcut</span> <span class="o">=</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new_p2pkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pubkey_hash</span><span class="p">);</span>

    <span class="c1">// 兩種方式產生相同的腳本</span>
    <span class="nd">assert_eq!</span><span class="p">(</span><span class="n">p2pkh_script</span><span class="p">,</span> <span class="n">p2pkh_shortcut</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>你也可以解析腳本來檢查其結構：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">parse_script</span><span class="p">(</span><span class="n">script</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Script</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="n">instruction</span> <span class="k">in</span> <span class="n">script</span><span class="nf">.instructions</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">match</span> <span class="n">instruction</span> <span class="p">{</span>
            <span class="nf">Ok</span><span class="p">(</span><span class="nn">bitcoin</span><span class="p">::</span><span class="nn">script</span><span class="p">::</span><span class="nn">Instruction</span><span class="p">::</span><span class="nf">Op</span><span class="p">(</span><span class="n">op</span><span class="p">))</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="nd">println!</span><span class="p">(</span><span class="s">"操作碼: {:?}"</span><span class="p">,</span> <span class="n">op</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="nf">Ok</span><span class="p">(</span><span class="nn">bitcoin</span><span class="p">::</span><span class="nn">script</span><span class="p">::</span><span class="nn">Instruction</span><span class="p">::</span><span class="nf">PushBytes</span><span class="p">(</span><span class="n">data</span><span class="p">))</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="nd">println!</span><span class="p">(</span><span class="s">"數據: {}"</span><span class="p">,</span> <span class="nn">hex</span><span class="p">::</span><span class="nf">encode</span><span class="p">(</span><span class="n">data</span><span class="nf">.as_bytes</span><span class="p">()));</span>
            <span class="p">}</span>
            <span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="nd">println!</span><span class="p">(</span><span class="s">"解析錯誤: {}"</span><span class="p">,</span> <span class="n">e</span><span class="p">),</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="2-標準腳本類型">2. 標準腳本類型</h2>

<h3 id="21-p2pk-和-p2pkh">2.1 P2PK 和 P2PKH</h3>

<p><strong>P2PK（Pay-to-Public-Key）</strong>是最早的腳本類型，直接將公鑰嵌入腳本中。解鎖只需要提供簽名。這種格式現在已經不常用，因為它暴露了公鑰，而且腳本較長。</p>

<p><strong>P2PKH（Pay-to-Public-Key-Hash）</strong>改進了這個設計，只在腳本中存儲公鑰的雜湊值。解鎖時需要同時提供公鑰和簽名。這有兩個好處：腳本更短，而且在資金被花費之前，公鑰不會暴露。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">standard_scripts</span><span class="p">(</span><span class="n">pubkey</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PublicKey</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// P2PK：&lt;pubkey&gt; OP_CHECKSIG</span>
    <span class="k">let</span> <span class="n">p2pk</span> <span class="o">=</span> <span class="nn">Builder</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.push_key</span><span class="p">(</span><span class="n">pubkey</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_CHECKSIG</span><span class="p">)</span>
        <span class="nf">.into_script</span><span class="p">();</span>

    <span class="c1">// P2PKH：OP_DUP OP_HASH160 &lt;pubkey_hash&gt; OP_EQUALVERIFY OP_CHECKSIG</span>
    <span class="k">let</span> <span class="n">p2pkh</span> <span class="o">=</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new_p2pkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pubkey</span><span class="nf">.pubkey_hash</span><span class="p">());</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"P2PK 大小: {} bytes"</span><span class="p">,</span> <span class="n">p2pk</span><span class="nf">.len</span><span class="p">());</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"P2PKH 大小: {} bytes"</span><span class="p">,</span> <span class="n">p2pkh</span><span class="nf">.len</span><span class="p">());</span>
    <span class="c1">// P2PKH 較短，因為 pubkey_hash 只有 20 bytes，而公鑰有 33 bytes</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="22-p2sh">2.2 P2SH</h3>

<p><strong>P2SH（Pay-to-Script-Hash）</strong>是一個強大的抽象，允許支付到任意腳本的雜湊值。發送者不需要知道腳本的內容——他們只需支付到一個以 3 開頭的地址。只有在花費時，完整的腳本才會被揭示。</p>

<p>這種設計有幾個優點。首先，複雜的腳本（如多重簽名）可以用短小的地址表示，減少發送者的負擔。其次，腳本的複雜性成本由花費者承擔，而不是發送者。第三，它為腳本升級提供了靈活性——只要雜湊值相同，底層腳本可以改變。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">p2sh_demo</span><span class="p">(</span><span class="n">inner_script</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Script</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// P2SH：OP_HASH160 &lt;script_hash&gt; OP_EQUAL</span>
    <span class="k">let</span> <span class="n">p2sh</span> <span class="o">=</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new_p2sh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inner_script</span><span class="nf">.script_hash</span><span class="p">());</span>

    <span class="c1">// 解鎖 P2SH 需要：</span>
    <span class="c1">// 1. 滿足內部腳本的數據</span>
    <span class="c1">// 2. 內部腳本本身</span>

    <span class="c1">// 例如，對於包裝的 P2PKH：</span>
    <span class="c1">// scriptSig: &lt;sig&gt; &lt;pubkey&gt; &lt;serialized_p2pkh_script&gt;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="23-segwit-腳本p2wpkh-和-p2wsh">2.3 SegWit 腳本（P2WPKH 和 P2WSH）</h3>

<p>SegWit（Segregated Witness）升級引入了新的腳本類型，將簽名數據移到交易的「見證」部分。這解決了交易可延展性問題，並降低了費用。</p>

<p><strong>P2WPKH</strong> 是 P2PKH 的 SegWit 版本。鎖定腳本非常簡短：<code class="language-plaintext highlighter-rouge">OP_0 &lt;20-byte-pubkey-hash&gt;</code>。<code class="language-plaintext highlighter-rouge">OP_0</code> 表示見證版本 0。解鎖數據（簽名和公鑰）放在 witness 欄位中。</p>

<p><strong>P2WSH</strong> 是 P2SH 的 SegWit 版本，使用 32-byte 的腳本雜湊：<code class="language-plaintext highlighter-rouge">OP_0 &lt;32-byte-script-hash&gt;</code>。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">segwit_scripts</span><span class="p">(</span><span class="n">pubkey</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PublicKey</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// P2WPKH</span>
    <span class="k">let</span> <span class="n">wpkh</span> <span class="o">=</span> <span class="n">pubkey</span><span class="nf">.wpubkey_hash</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">p2wpkh</span> <span class="o">=</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new_p2wpkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wpkh</span><span class="p">);</span>

    <span class="c1">// P2WSH（包裝多簽腳本）</span>
    <span class="k">let</span> <span class="n">multisig_script</span> <span class="o">=</span> <span class="nf">create_multisig_script</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="o">&amp;</span><span class="p">[</span><span class="n">pubkey1</span><span class="p">,</span> <span class="n">pubkey2</span><span class="p">,</span> <span class="n">pubkey3</span><span class="p">]);</span>
    <span class="k">let</span> <span class="n">wsh</span> <span class="o">=</span> <span class="n">multisig_script</span><span class="nf">.wscript_hash</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">p2wsh</span> <span class="o">=</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new_p2wsh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wsh</span><span class="p">);</span>

    <span class="c1">// SegWit 腳本的特點是非常簡短</span>
    <span class="c1">// 複雜度在 witness 中，費用較低</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="24-taprootp2tr">2.4 Taproot（P2TR）</h3>

<p>Taproot 是 Bitcoin 腳本能力的最新重大升級。P2TR 腳本的格式是 <code class="language-plaintext highlighter-rouge">OP_1 &lt;32-byte-tweaked-pubkey&gt;</code>，其中 <code class="language-plaintext highlighter-rouge">OP_1</code> 表示見證版本 1。</p>

<p>Taproot 的核心創新是「tweaked key」。輸出公鑰是內部公鑰加上一個調整值（tweak），這個調整值可以承諾（commit）到一棵腳本樹。這意味著：</p>

<ul>
  <li>如果所有參與者同意，可以使用密鑰路徑（key path）花費——只需一個簽名</li>
  <li>如果需要使用特定條件，可以使用腳本路徑（script path）——揭示並執行該腳本</li>
  <li>未使用的腳本永遠不會暴露，保護了隱私</li>
</ul>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">taproot_scripts</span><span class="p">(</span><span class="n">secp</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Secp256k1</span><span class="o">&lt;</span><span class="nn">secp256k1</span><span class="p">::</span><span class="n">All</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">internal_key</span> <span class="o">=</span> <span class="nn">XOnlyPublicKey</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="s">"..."</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 簡單的 P2TR（無腳本樹）</span>
    <span class="k">let</span> <span class="n">p2tr</span> <span class="o">=</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new_p2tr</span><span class="p">(</span><span class="n">secp</span><span class="p">,</span> <span class="n">internal_key</span><span class="p">,</span> <span class="nb">None</span><span class="p">);</span>

    <span class="c1">// 帶腳本樹的 P2TR</span>
    <span class="k">let</span> <span class="n">script_a</span> <span class="o">=</span> <span class="nn">Builder</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.push_x_only_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">internal_key</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_CHECKSIG</span><span class="p">)</span>
        <span class="nf">.into_script</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">taproot</span> <span class="o">=</span> <span class="nn">TaprootBuilder</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.add_leaf</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">script_a</span><span class="p">)</span><span class="o">?</span>
        <span class="nf">.finalize</span><span class="p">(</span><span class="n">secp</span><span class="p">,</span> <span class="n">internal_key</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="k">let</span> <span class="n">tweaked_key</span> <span class="o">=</span> <span class="n">taproot</span><span class="nf">.output_key</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">p2tr_with_scripts</span> <span class="o">=</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new_p2tr_tweaked</span><span class="p">(</span><span class="n">tweaked_key</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="3-多重簽名">3. 多重簽名</h2>

<h3 id="31-理解多重簽名">3.1 理解多重簽名</h3>

<p>多重簽名（multisig）是 Bitcoin 最重要的腳本功能之一，要求多個密鑰中的一部分來授權交易。常見的配置包括 2-of-3（三個密鑰中任意兩個）和 3-of-5。</p>

<p>多重簽名有許多應用場景。對於個人用戶，它提供了備份和安全性——即使丟失一個密鑰，資金仍然可以取回；即使一個密鑰被盜，攻擊者也無法單獨花費資金。對於組織，它實現了共同控制——需要多人同意才能移動資金。</p>

<p>傳統的多簽使用 <code class="language-plaintext highlighter-rouge">OP_CHECKMULTISIG</code> 操作碼，但這有一些缺點：公鑰數量和門檻在腳本中可見，費用隨公鑰數量線性增加。Taproot 時代的多簽有更好的選擇，我們稍後會討論。</p>

<h3 id="32-構建傳統多簽腳本">3.2 構建傳統多簽腳本</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">create_multisig_script</span><span class="p">(</span><span class="n">m</span><span class="p">:</span> <span class="nb">u8</span><span class="p">,</span> <span class="n">pubkeys</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="n">PublicKey</span><span class="p">])</span> <span class="k">-&gt;</span> <span class="n">ScriptBuf</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">n</span> <span class="o">=</span> <span class="n">pubkeys</span><span class="nf">.len</span><span class="p">()</span> <span class="k">as</span> <span class="nb">u8</span><span class="p">;</span>
    <span class="nd">assert!</span><span class="p">(</span><span class="n">m</span> <span class="o">&lt;=</span> <span class="n">n</span> <span class="o">&amp;&amp;</span> <span class="n">n</span> <span class="o">&lt;=</span> <span class="mi">20</span><span class="p">,</span> <span class="s">"無效的 M-of-N 參數"</span><span class="p">);</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">builder</span> <span class="o">=</span> <span class="nn">Builder</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span><span class="nf">.push_int</span><span class="p">(</span><span class="n">m</span> <span class="k">as</span> <span class="nb">i64</span><span class="p">);</span>

    <span class="k">for</span> <span class="n">pubkey</span> <span class="k">in</span> <span class="n">pubkeys</span> <span class="p">{</span>
        <span class="n">builder</span> <span class="o">=</span> <span class="n">builder</span><span class="nf">.push_key</span><span class="p">(</span><span class="n">pubkey</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">builder</span>
        <span class="nf">.push_int</span><span class="p">(</span><span class="n">n</span> <span class="k">as</span> <span class="nb">i64</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_CHECKMULTISIG</span><span class="p">)</span>
        <span class="nf">.into_script</span><span class="p">()</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">multisig_demo</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 創建三個公鑰</span>
    <span class="k">let</span> <span class="n">pubkeys</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">PublicKey</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span>
        <span class="nn">PublicKey</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="s">"02..."</span><span class="p">)</span><span class="o">?</span><span class="p">,</span>
        <span class="nn">PublicKey</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="s">"02..."</span><span class="p">)</span><span class="o">?</span><span class="p">,</span>
        <span class="nn">PublicKey</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="s">"02..."</span><span class="p">)</span><span class="o">?</span><span class="p">,</span>
    <span class="p">];</span>

    <span class="c1">// 2-of-3 多簽</span>
    <span class="k">let</span> <span class="n">multisig</span> <span class="o">=</span> <span class="nf">create_multisig_script</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pubkeys</span><span class="p">);</span>

    <span class="c1">// 通常包裝在 P2WSH 中以節省費用</span>
    <span class="k">let</span> <span class="n">p2wsh</span> <span class="o">=</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new_p2wsh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">multisig</span><span class="nf">.wscript_hash</span><span class="p">());</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"多簽腳本: {}"</span><span class="p">,</span> <span class="n">multisig</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"P2WSH 腳本: {}"</span><span class="p">,</span> <span class="n">p2wsh</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="33-花費多簽">3.3 花費多簽</h3>

<p>花費多簽交易需要提供足夠數量的有效簽名。對於傳統的 <code class="language-plaintext highlighter-rouge">OP_CHECKMULTISIG</code>，還需要注意一個著名的 bug：操作碼會額外消耗一個堆疊元素（通常使用 <code class="language-plaintext highlighter-rouge">OP_0</code> 佔位）。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">spend_multisig</span><span class="p">(</span>
    <span class="n">tx</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Transaction</span><span class="p">,</span>
    <span class="n">input_index</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">multisig_script</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Script</span><span class="p">,</span>
    <span class="n">signatures</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;&gt;</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 傳統多簽的解鎖腳本：</span>
    <span class="c1">// OP_0 &lt;sig1&gt; &lt;sig2&gt; ... &lt;redeemScript&gt;</span>

    <span class="k">let</span> <span class="k">mut</span> <span class="n">witness</span> <span class="o">=</span> <span class="nn">Witness</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

    <span class="c1">// OP_0（CHECKMULTISIG bug 的佔位符）</span>
    <span class="n">witness</span><span class="nf">.push</span><span class="p">(</span><span class="o">&amp;</span><span class="p">[]);</span>

    <span class="c1">// 添加簽名</span>
    <span class="k">for</span> <span class="n">sig</span> <span class="k">in</span> <span class="n">signatures</span> <span class="p">{</span>
        <span class="n">witness</span><span class="nf">.push</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sig</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 對於 P2WSH，需要添加腳本</span>
    <span class="n">witness</span><span class="nf">.push</span><span class="p">(</span><span class="n">multisig_script</span><span class="nf">.as_bytes</span><span class="p">());</span>

    <span class="n">tx</span><span class="py">.input</span><span class="p">[</span><span class="n">input_index</span><span class="p">]</span><span class="py">.witness</span> <span class="o">=</span> <span class="n">witness</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="4-簽名機制詳解">4. 簽名機制詳解</h2>

<h3 id="41-ecdsa-簽名">4.1 ECDSA 簽名</h3>

<p>ECDSA（Elliptic Curve Digital Signature Algorithm）是 Bitcoin 最初使用的簽名演算法。簽名過程包括：</p>

<ol>
  <li>計算要簽名的消息（交易數據的特定哈希）</li>
  <li>使用私鑰和隨機數生成簽名</li>
  <li>簽名由兩個數值 (r, s) 組成，以 DER 格式編碼</li>
</ol>

<p>簽名哈希（sighash）的計算方式取決於腳本類型。傳統腳本使用原始的 sighash 演算法，而 SegWit 使用 BIP143 定義的新演算法，後者包含了被花費 UTXO 的金額，防止了某些類型的攻擊。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">sighash</span><span class="p">::{</span><span class="n">SighashCache</span><span class="p">,</span> <span class="n">EcdsaSighashType</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">ecdsa_signing</span><span class="p">(</span>
    <span class="n">tx</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Transaction</span><span class="p">,</span>
    <span class="n">input_index</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">utxo_script</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Script</span><span class="p">,</span>
    <span class="n">utxo_amount</span><span class="p">:</span> <span class="n">Amount</span><span class="p">,</span>
    <span class="n">private_key</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PrivateKey</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">secp</span> <span class="o">=</span> <span class="nn">Secp256k1</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">public_key</span> <span class="o">=</span> <span class="n">private_key</span><span class="nf">.public_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">);</span>

    <span class="c1">// 對於 P2WPKH，scriptCode 是對應的 P2PKH 腳本</span>
    <span class="k">let</span> <span class="n">script_code</span> <span class="o">=</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new_p2pkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">public_key</span><span class="nf">.pubkey_hash</span><span class="p">());</span>

    <span class="c1">// 計算 BIP143 sighash</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">sighash_cache</span> <span class="o">=</span> <span class="nn">SighashCache</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">tx</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">sighash</span> <span class="o">=</span> <span class="n">sighash_cache</span><span class="nf">.p2wpkh_signature_hash</span><span class="p">(</span>
        <span class="n">input_index</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">script_code</span><span class="p">,</span>
        <span class="n">utxo_amount</span><span class="p">,</span>
        <span class="nn">EcdsaSighashType</span><span class="p">::</span><span class="n">All</span><span class="p">,</span>
    <span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 創建簽名</span>
    <span class="k">let</span> <span class="n">message</span> <span class="o">=</span> <span class="nn">Message</span><span class="p">::</span><span class="nf">from_digest_slice</span><span class="p">(</span><span class="n">sighash</span><span class="nf">.as_byte_array</span><span class="p">())</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">signature</span> <span class="o">=</span> <span class="n">secp</span><span class="nf">.sign_ecdsa</span><span class="p">(</span><span class="o">&amp;</span><span class="n">message</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">private_key</span><span class="py">.inner</span><span class="p">);</span>

    <span class="c1">// DER 編碼 + sighash type</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">sig_bytes</span> <span class="o">=</span> <span class="n">signature</span><span class="nf">.serialize_der</span><span class="p">()</span><span class="nf">.to_vec</span><span class="p">();</span>
    <span class="n">sig_bytes</span><span class="nf">.push</span><span class="p">(</span><span class="nn">EcdsaSighashType</span><span class="p">::</span><span class="n">All</span><span class="nf">.to_u32</span><span class="p">()</span> <span class="k">as</span> <span class="nb">u8</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">(</span><span class="n">sig_bytes</span><span class="p">)</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="42-sighash-類型">4.2 Sighash 類型</h3>

<p>Sighash 類型控制簽名涵蓋交易的哪些部分，提供了靈活的簽名選項：</p>

<p><strong>SIGHASH_ALL（0x01）</strong>：最常見，簽名涵蓋所有輸入和輸出。任何改變都會使簽名無效。</p>

<p><strong>SIGHASH_NONE（0x02）</strong>：只簽名輸入，不簽輸出。簽名者同意花費這些輸入，但不關心資金去向。這很危險，因為任何人都可以修改輸出。</p>

<p><strong>SIGHASH_SINGLE（0x03）</strong>：簽名所有輸入和同一索引的輸出。用於某些特殊的多方協議。</p>

<p><strong>ANYONECANPAY（0x80）</strong>：可以與上述任何類型組合。只簽名當前輸入，允許其他人添加更多輸入。這可以用於眾籌——多人可以各自添加輸入到同一筆交易。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">sighash_types_demo</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 不同 sighash 類型的用例</span>

    <span class="c1">// ALL：標準支付</span>
    <span class="c1">// "我同意花費這些輸入，將資金發送到這些輸出"</span>

    <span class="c1">// NONE：簽名授權</span>
    <span class="c1">// "我授權花費這些輸入，任何人可以決定去向"</span>

    <span class="c1">// SINGLE：交換</span>
    <span class="c1">// "我願意用我的輸入換取這個特定的輸出"</span>

    <span class="c1">// ALL|ANYONECANPAY：眾籌</span>
    <span class="c1">// "如果達成這個輸出，我願意貢獻我的輸入"</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="43-schnorr-簽名">4.3 Schnorr 簽名</h3>

<p>Taproot 引入了 Schnorr 簽名（BIP340），它比 ECDSA 有多個優點：</p>

<p><strong>更小的簽名</strong>：64 bytes，而 ECDSA 是 71-72 bytes。</p>

<p><strong>線性性</strong>：多個簽名可以聚合成一個。這意味著 n-of-n 多簽可以表現得像單簽，大大提高隱私和效率。</p>

<p><strong>批量驗證</strong>：多個簽名可以同時驗證，比逐個驗證更快。</p>

<p><strong>更簡單的安全證明</strong>：Schnorr 的數學結構更簡潔。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">secp256k1</span><span class="p">::{</span><span class="n">Keypair</span><span class="p">,</span> <span class="n">XOnlyPublicKey</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">schnorr_signing</span><span class="p">(</span>
    <span class="n">tx</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Transaction</span><span class="p">,</span>
    <span class="n">input_index</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">prevouts</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="n">TxOut</span><span class="p">],</span>
    <span class="n">keypair</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Keypair</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">secp</span> <span class="o">=</span> <span class="nn">Secp256k1</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

    <span class="c1">// 計算 Taproot sighash</span>
    <span class="k">let</span> <span class="n">prevouts</span> <span class="o">=</span> <span class="nn">Prevouts</span><span class="p">::</span><span class="nf">All</span><span class="p">(</span><span class="n">prevouts</span><span class="p">);</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">sighash_cache</span> <span class="o">=</span> <span class="nn">SighashCache</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">tx</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">sighash</span> <span class="o">=</span> <span class="n">sighash_cache</span><span class="nf">.taproot_key_spend_signature_hash</span><span class="p">(</span>
        <span class="n">input_index</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">prevouts</span><span class="p">,</span>
        <span class="nn">TapSighashType</span><span class="p">::</span><span class="nb">Default</span><span class="p">,</span>
    <span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// Schnorr 簽名</span>
    <span class="k">let</span> <span class="n">msg</span> <span class="o">=</span> <span class="nn">Message</span><span class="p">::</span><span class="nf">from_digest_slice</span><span class="p">(</span><span class="n">sighash</span><span class="nf">.as_byte_array</span><span class="p">())</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">signature</span> <span class="o">=</span> <span class="n">secp</span><span class="nf">.sign_schnorr</span><span class="p">(</span><span class="o">&amp;</span><span class="n">msg</span><span class="p">,</span> <span class="n">keypair</span><span class="p">);</span>

    <span class="c1">// Schnorr 簽名固定 64 bytes</span>
    <span class="nf">Ok</span><span class="p">(</span><span class="n">signature</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.to_vec</span><span class="p">())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="44-taproot-簽名的兩種路徑">4.4 Taproot 簽名的兩種路徑</h3>

<p>Taproot 交易可以通過兩種方式花費：</p>

<p><strong>密鑰路徑（Key Path）</strong>：如果你控制內部密鑰（或聚合密鑰），只需提供一個 Schnorr 簽名。這是最有效率的方式，witness 只包含一個 64-byte 簽名。</p>

<p><strong>腳本路徑（Script Path）</strong>：揭示並執行腳本樹中的一個腳本。witness 包含：</p>
<ol>
  <li>滿足腳本的數據（如簽名）</li>
  <li>腳本本身</li>
  <li>Control block（包含內部公鑰和 Merkle 證明）</li>
</ol>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">taproot_spending_demo</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Key path spending</span>
    <span class="c1">// witness: [schnorr_signature]</span>
    <span class="c1">// 最簡潔，64 bytes</span>

    <span class="c1">// Script path spending</span>
    <span class="c1">// witness: [script_args...] [script] [control_block]</span>
    <span class="c1">// 較大，但允許複雜條件</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="5-miniscript">5. Miniscript</h2>

<h3 id="51-為什麼需要-miniscript">5.1 為什麼需要 Miniscript</h3>

<p>直接編寫 Bitcoin Script 是容易出錯的。腳本可能看起來正確但有微妙的 bug——例如，忘記了 <code class="language-plaintext highlighter-rouge">OP_CHECKMULTISIG</code> 的佔位 bug，或者條件分支不完整。更糟糕的是，很難分析一個腳本的所有可能花費方式。</p>

<p>Miniscript 解決了這些問題。它是 Bitcoin Script 的一個子集，具有以下特點：</p>

<p><strong>可組合性</strong>：你可以安全地組合不同的條件，而不用擔心它們之間的交互。</p>

<p><strong>可分析性</strong>：給定一個 Miniscript，你可以自動分析：所有可能的花費方式、每種方式的 witness 大小、涉及的公鑰和時間鎖。</p>

<p><strong>編譯優化</strong>：從高階策略語言編譯為最優的 Script。</p>

<h3 id="52-策略語言">5.2 策略語言</h3>

<p>Miniscript 使用一種簡潔的策略語言來描述花費條件：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">pk(KEY)</code> - 需要 KEY 的簽名</li>
  <li><code class="language-plaintext highlighter-rouge">older(N)</code> - 相對時間鎖，N 個區塊後</li>
  <li><code class="language-plaintext highlighter-rouge">after(N)</code> - 絕對時間鎖</li>
  <li><code class="language-plaintext highlighter-rouge">sha256(H)</code> - 需要 SHA256 原像</li>
  <li><code class="language-plaintext highlighter-rouge">and(A, B)</code> - 需要 A 和 B 同時滿足</li>
  <li><code class="language-plaintext highlighter-rouge">or(A, B)</code> - A 或 B 其中之一</li>
  <li><code class="language-plaintext highlighter-rouge">thresh(k, A, B, C...)</code> - k-of-n 條件</li>
</ul>

<p>例如，<code class="language-plaintext highlighter-rouge">or(pk(Alice), and(pk(Bob), older(144)))</code> 表示：Alice 可以立即花費，或者 Bob 在 144 個區塊（約 1 天）後可以花費。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">miniscript</span><span class="p">::</span><span class="nn">policy</span><span class="p">::</span><span class="n">Concrete</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">miniscript</span><span class="p">::</span><span class="n">Segwitv0</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">miniscript_demo</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">alice</span> <span class="o">=</span> <span class="s">"02alice..."</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">bob</span> <span class="o">=</span> <span class="s">"02bob..."</span><span class="p">;</span>

    <span class="c1">// 定義策略</span>
    <span class="k">let</span> <span class="n">policy_str</span> <span class="o">=</span> <span class="nd">format!</span><span class="p">(</span>
        <span class="s">"or(pk({}),and(pk({}),older(144)))"</span><span class="p">,</span>
        <span class="n">alice</span><span class="p">,</span> <span class="n">bob</span>
    <span class="p">);</span>

    <span class="c1">// 解析策略</span>
    <span class="k">let</span> <span class="n">policy</span> <span class="o">=</span> <span class="nn">Concrete</span><span class="p">::</span><span class="o">&lt;</span><span class="n">PublicKey</span><span class="o">&gt;</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="o">&amp;</span><span class="n">policy_str</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 編譯為 Miniscript</span>
    <span class="k">let</span> <span class="n">miniscript</span> <span class="o">=</span> <span class="n">policy</span><span class="py">.compile</span><span class="p">::</span><span class="o">&lt;</span><span class="n">Segwitv0</span><span class="o">&gt;</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 獲取 Script</span>
    <span class="k">let</span> <span class="n">script</span> <span class="o">=</span> <span class="n">miniscript</span><span class="nf">.encode</span><span class="p">();</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"策略: {}"</span><span class="p">,</span> <span class="n">policy</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"Miniscript: {}"</span><span class="p">,</span> <span class="n">miniscript</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"Script 大小: {} bytes"</span><span class="p">,</span> <span class="n">script</span><span class="nf">.len</span><span class="p">());</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="53-描述符descriptors">5.3 描述符（Descriptors）</h3>

<p>描述符是錢包軟體用來描述地址生成方式的標準格式。它們結合了腳本類型和密鑰資訊：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">pkh(KEY)</code> - P2PKH 地址</li>
  <li><code class="language-plaintext highlighter-rouge">wpkh(KEY)</code> - P2WPKH 地址</li>
  <li><code class="language-plaintext highlighter-rouge">sh(wpkh(KEY))</code> - P2SH-P2WPKH</li>
  <li><code class="language-plaintext highlighter-rouge">tr(KEY)</code> - 簡單 P2TR</li>
  <li><code class="language-plaintext highlighter-rouge">wsh(multi(2,KEY1,KEY2,KEY3))</code> - 2-of-3 P2WSH 多簽</li>
</ul>

<p>描述符可以包含 HD 派生路徑，允許錢包批量生成地址：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>wpkh([fingerprint/84'/0'/0']xpub.../0/*)
</pre></td></tr></tbody></table></code></pre></div></div>

<p>這表示：使用 BIP84 路徑派生的擴展公鑰，生成接收地址（/0/*）。</p>

<hr />

<h2 id="6-進階腳本技巧">6. 進階腳本技巧</h2>

<h3 id="61-時間鎖">6.1 時間鎖</h3>

<p>時間鎖是 Bitcoin 腳本的重要功能，允許資金只能在特定時間後被花費。</p>

<p><strong>CHECKLOCKTIMEVERIFY（CLTV）</strong>實現絕對時間鎖。值小於 5 億被解釋為區塊高度，大於等於 5 億被解釋為 Unix 時間戳。</p>

<p><strong>CHECKSEQUENCEVERIFY（CSV）</strong>實現相對時間鎖，從 UTXO 被創建開始計算。這對於閃電網路等協議至關重要。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">timelock_script</span><span class="p">(</span><span class="n">pubkey</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PublicKey</span><span class="p">,</span> <span class="n">blocks</span><span class="p">:</span> <span class="nb">i64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">ScriptBuf</span> <span class="p">{</span>
    <span class="c1">// &lt;blocks&gt; OP_CSV OP_DROP &lt;pubkey&gt; OP_CHECKSIG</span>
    <span class="nn">Builder</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.push_int</span><span class="p">(</span><span class="n">blocks</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_CSV</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_DROP</span><span class="p">)</span>
        <span class="nf">.push_key</span><span class="p">(</span><span class="n">pubkey</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_CHECKSIG</span><span class="p">)</span>
        <span class="nf">.into_script</span><span class="p">()</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="62-htlc哈希時間鎖合約">6.2 HTLC（哈希時間鎖合約）</h3>

<p>HTLC 是閃電網路和原子交換的基礎。它允許有條件的支付：如果接收者在時限內揭示原像（preimage），可以領取資金；否則發送者可以取回。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">htlc_script</span><span class="p">(</span>
    <span class="n">recipient_pubkey</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">XOnlyPublicKey</span><span class="p">,</span>
    <span class="n">sender_pubkey</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">XOnlyPublicKey</span><span class="p">,</span>
    <span class="n">payment_hash</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="nb">u8</span><span class="p">],</span>
    <span class="n">timeout</span><span class="p">:</span> <span class="nb">i64</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="n">ScriptBuf</span> <span class="p">{</span>
    <span class="c1">// IF</span>
    <span class="c1">//   OP_SHA256 &lt;hash&gt; OP_EQUALVERIFY &lt;recipient&gt; OP_CHECKSIG</span>
    <span class="c1">// ELSE</span>
    <span class="c1">//   &lt;timeout&gt; OP_CLTV OP_DROP &lt;sender&gt; OP_CHECKSIG</span>
    <span class="c1">// ENDIF</span>

    <span class="nn">Builder</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_IF</span><span class="p">)</span>
            <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_SHA256</span><span class="p">)</span>
            <span class="nf">.push_slice</span><span class="p">(</span><span class="n">payment_hash</span><span class="p">)</span>
            <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_EQUALVERIFY</span><span class="p">)</span>
            <span class="nf">.push_x_only_key</span><span class="p">(</span><span class="n">recipient_pubkey</span><span class="p">)</span>
            <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_CHECKSIG</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_ELSE</span><span class="p">)</span>
            <span class="nf">.push_int</span><span class="p">(</span><span class="n">timeout</span><span class="p">)</span>
            <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_CLTV</span><span class="p">)</span>
            <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_DROP</span><span class="p">)</span>
            <span class="nf">.push_x_only_key</span><span class="p">(</span><span class="n">sender_pubkey</span><span class="p">)</span>
            <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_CHECKSIG</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_ENDIF</span><span class="p">)</span>
        <span class="nf">.into_script</span><span class="p">()</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="63-taproot-腳本樹">6.3 Taproot 腳本樹</h3>

<p>Taproot 允許將多個腳本組織成 Merkle 樹。每個葉子是一個獨立的花費條件。當使用某個腳本花費時，只需揭示該腳本和 Merkle 證明，其他腳本保持隱藏。</p>

<p>這種設計特別適合有多個備用花費方式的場景。例如，一個「金庫」可能有：</p>
<ul>
  <li>密鑰路徑：擁有者的正常花費</li>
  <li>腳本 A：緊急恢復密鑰</li>
  <li>腳本 B：延遲後的繼承人</li>
</ul>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">taproot_tree_demo</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">secp</span> <span class="o">=</span> <span class="nn">Secp256k1</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">internal_key</span> <span class="o">=</span> <span class="nn">XOnlyPublicKey</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="s">"..."</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 創建腳本</span>
    <span class="k">let</span> <span class="n">emergency_script</span> <span class="o">=</span> <span class="nf">create_emergency_script</span><span class="p">(</span><span class="o">&amp;</span><span class="n">emergency_key</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">inheritance_script</span> <span class="o">=</span> <span class="nf">create_inheritance_script</span><span class="p">(</span><span class="o">&amp;</span><span class="n">heir_key</span><span class="p">,</span> <span class="mi">52560</span><span class="p">);</span> <span class="c1">// ~1年</span>

    <span class="c1">// 構建樹</span>
    <span class="k">let</span> <span class="n">taproot</span> <span class="o">=</span> <span class="nn">TaprootBuilder</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.add_leaf</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">emergency_script</span><span class="p">)</span><span class="o">?</span>
        <span class="nf">.add_leaf</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">inheritance_script</span><span class="p">)</span><span class="o">?</span>
        <span class="nf">.finalize</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">,</span> <span class="n">internal_key</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 正常使用：key path（單簽名）</span>
    <span class="c1">// 緊急情況：script path A</span>
    <span class="c1">// 繼承：script path B（需要等待）</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="7-實戰完整的-htlc-交易">7. 實戰：完整的 HTLC 交易</h2>

<p>讓我們將所有概念結合起來，實現一個完整的 HTLC 交易流程。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">htlc_transaction_flow</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">secp</span> <span class="o">=</span> <span class="nn">Secp256k1</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

    <span class="c1">// 參與者</span>
    <span class="k">let</span> <span class="n">sender</span> <span class="o">=</span> <span class="nn">Keypair</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="nn">rand</span><span class="p">::</span><span class="nf">thread_rng</span><span class="p">());</span>
    <span class="k">let</span> <span class="n">recipient</span> <span class="o">=</span> <span class="nn">Keypair</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="nn">rand</span><span class="p">::</span><span class="nf">thread_rng</span><span class="p">());</span>

    <span class="c1">// 創建 preimage 和 hash</span>
    <span class="k">let</span> <span class="n">preimage</span> <span class="o">=</span> <span class="s">b"this is the secret"</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">payment_hash</span> <span class="o">=</span> <span class="nn">sha256</span><span class="p">::</span><span class="nn">Hash</span><span class="p">::</span><span class="nf">hash</span><span class="p">(</span><span class="n">preimage</span><span class="p">);</span>

    <span class="c1">// 構建 HTLC Taproot</span>

    <span class="c1">// 成功路徑</span>
    <span class="k">let</span> <span class="n">success_script</span> <span class="o">=</span> <span class="nn">Builder</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_SHA256</span><span class="p">)</span>
        <span class="nf">.push_slice</span><span class="p">(</span><span class="n">payment_hash</span><span class="nf">.as_byte_array</span><span class="p">())</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_EQUALVERIFY</span><span class="p">)</span>
        <span class="nf">.push_x_only_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">recipient</span><span class="nf">.x_only_public_key</span><span class="p">()</span><span class="na">.0</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_CHECKSIG</span><span class="p">)</span>
        <span class="nf">.into_script</span><span class="p">();</span>

    <span class="c1">// 退款路徑（100 區塊後）</span>
    <span class="k">let</span> <span class="n">refund_script</span> <span class="o">=</span> <span class="nn">Builder</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.push_int</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_CSV</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_DROP</span><span class="p">)</span>
        <span class="nf">.push_x_only_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sender</span><span class="nf">.x_only_public_key</span><span class="p">()</span><span class="na">.0</span><span class="p">)</span>
        <span class="nf">.push_opcode</span><span class="p">(</span><span class="n">OP_CHECKSIG</span><span class="p">)</span>
        <span class="nf">.into_script</span><span class="p">();</span>

    <span class="c1">// 構建 Taproot（sender 作為內部密鑰）</span>
    <span class="k">let</span> <span class="n">taproot</span> <span class="o">=</span> <span class="nn">TaprootBuilder</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
        <span class="nf">.add_leaf</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">success_script</span><span class="nf">.clone</span><span class="p">())</span><span class="o">?</span>
        <span class="nf">.add_leaf</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">refund_script</span><span class="nf">.clone</span><span class="p">())</span><span class="o">?</span>
        <span class="nf">.finalize</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">,</span> <span class="n">sender</span><span class="nf">.x_only_public_key</span><span class="p">()</span><span class="na">.0</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="k">let</span> <span class="n">htlc_address</span> <span class="o">=</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">p2tr_tweaked</span><span class="p">(</span>
        <span class="n">taproot</span><span class="nf">.output_key</span><span class="p">(),</span>
        <span class="nn">Network</span><span class="p">::</span><span class="n">Testnet</span>
    <span class="p">);</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"HTLC 地址: {}"</span><span class="p">,</span> <span class="n">htlc_address</span><span class="p">);</span>

    <span class="c1">// ... 創建資金交易 ...</span>

    <span class="c1">// 成功花費（知道 preimage）</span>
    <span class="c1">// witness: [signature] [preimage] [script] [control_block]</span>

    <span class="c1">// 退款花費（超時後）</span>
    <span class="c1">// witness: [signature] [script] [control_block]</span>
    <span class="c1">// 並且 nSequence &gt;= 100</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="8-總結">8. 總結</h2>

<p>本篇深入探討了 Bitcoin Script 和簽名機制。我們學習了：</p>

<ul>
  <li><strong>Script 基礎</strong>：堆疊執行模型、操作碼、腳本構建</li>
  <li><strong>標準腳本類型</strong>：從 P2PKH 到 Taproot 的演進</li>
  <li><strong>多重簽名</strong>：傳統多簽和 Taproot 時代的選擇</li>
  <li><strong>簽名機制</strong>：ECDSA、Schnorr 和各種 sighash 類型</li>
  <li><strong>Miniscript</strong>：安全、可分析的腳本開發</li>
  <li><strong>進階技巧</strong>：時間鎖、HTLC、Taproot 腳本樹</li>
</ul>

<p>關鍵要點：</p>
<ul>
  <li>Script 定義了資金的花費條件</li>
  <li>Schnorr 簽名提供了更小的簽名和聚合能力</li>
  <li>Taproot 結合了效率和靈活性</li>
  <li>Miniscript 使腳本開發更安全</li>
</ul>

<p>下一篇將探討進階應用，包括與 Bitcoin 節點互動、完整的錢包開發和實際部署考量。</p>

<hr />

<h2 id="參考資源">參考資源</h2>

<h3 id="bip-文檔">BIP 文檔</h3>
<ul>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki">BIP 340: Schnorr Signatures</a></li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki">BIP 341: Taproot</a></li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki">BIP 342: Tapscript</a></li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki">BIP 380-386: Descriptors</a></li>
</ul>

<h3 id="miniscript-資源">Miniscript 資源</h3>
<ul>
  <li><a href="https://bitcoin.sipa.be/miniscript/">Miniscript 官網</a></li>
  <li><a href="https://github.com/rust-bitcoin/rust-miniscript">rust-miniscript</a></li>
</ul>]]></content><author><name>Cypherpunks Taiwan</name></author><category term="技術教學" /><category term="Bitcoin" /><category term="Rust" /><category term="Rust" /><category term="Bitcoin" /><category term="Script" /><category term="簽名" /><category term="ECDSA" /><category term="Schnorr" /><category term="Taproot" /><category term="Miniscript" /><summary type="html"><![CDATA[這是 Rust Bitcoin 開發入門系列的第三篇。本篇深入探討 Bitcoin Script 編程和各種簽名機制的 Rust 實現。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" /><media:content medium="image" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Rust Bitcoin 開發入門（二）：地址生成與交易構建</title><link href="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/rust/2025/03/23/rust-bitcoin-tutorial-2-addresses-transactions/" rel="alternate" type="text/html" title="Rust Bitcoin 開發入門（二）：地址生成與交易構建" /><published>2025-03-23T00:00:00+00:00</published><updated>2025-03-23T00:00:00+00:00</updated><id>https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/rust/2025/03/23/rust-bitcoin-tutorial-2-addresses-transactions</id><content type="html" xml:base="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/rust/2025/03/23/rust-bitcoin-tutorial-2-addresses-transactions/"><![CDATA[<p>這是 Rust Bitcoin 開發入門系列的第二篇。本篇深入探討 Bitcoin 地址的生成機制和交易的構建過程。</p>

<p><strong>系列文章導航：</strong></p>
<ul>
  <li><a href="/2025/03/22/rust-bitcoin-tutorial-1-environment-basics/">第一篇：環境設置與基礎概念</a></li>
  <li><strong>第二篇：地址生成與交易構建</strong>（本篇）</li>
  <li><a href="/2025/03/24/rust-bitcoin-tutorial-3-scripts-signatures/">第三篇：腳本與簽名</a></li>
  <li><a href="/2025/03/25/rust-bitcoin-tutorial-4-advanced-applications/">第四篇：進階應用與整合</a></li>
</ul>

<hr />

<h2 id="1-hd-錢包深入理解">1. HD 錢包深入理解</h2>

<h3 id="11-為什麼需要-hd-錢包">1.1 為什麼需要 HD 錢包</h3>

<p>在 Bitcoin 早期，用戶需要為每筆交易手動生成新的私鑰，並分別備份每一個私鑰。這種方式有明顯的問題：備份繁瑣、容易丟失，而且如果使用舊備份恢復錢包，可能會遺漏新生成的地址中的資金。</p>

<p>BIP32 提出了層級確定性（Hierarchical Deterministic，HD）錢包的概念，徹底改變了這個情況。HD 錢包從一個「種子」衍生出所有的私鑰，這意味著你只需要備份一次種子，就能恢復整個錢包的所有地址——包括未來創建的地址。</p>

<p>這個設計的數學基礎是單向函數和哈希函數的特性：從種子可以確定性地計算出無限多個子密鑰，但從子密鑰無法反推種子。這提供了安全性和便利性的完美平衡。</p>

<h3 id="12-bip32-層級派生">1.2 BIP32 層級派生</h3>

<p>BIP32 定義了密鑰派生的具體方法。每個密鑰都可以派生出子密鑰，形成一個樹狀結構。派生路徑用斜線分隔的數字表示，例如 <code class="language-plaintext highlighter-rouge">m/44'/0'/0'/0/0</code>。</p>

<p>路徑中的每個數字代表一層派生：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">m</code> 是主密鑰（master key），從種子直接生成</li>
  <li>數字後面的撇號（’）表示「硬化派生」（hardened derivation）</li>
  <li>沒有撇號的數字表示「普通派生」（normal derivation）</li>
</ul>

<p>硬化派生和普通派生的區別至關重要。普通派生允許從父公鑰派生子公鑰，這意味著如果攻擊者獲得了擴展公鑰（xpub），他可以計算出所有非硬化路徑下的子公鑰。更危險的是，如果攻擊者同時獲得了任何一個子私鑰和父公鑰，他就能推算出父私鑰。</p>

<p>硬化派生解決了這個問題。它的派生過程需要父私鑰參與，因此即使攻擊者獲得了擴展公鑰，也無法推導出硬化路徑下的子密鑰。這就是為什麼 purpose、coin type 和 account 層級總是使用硬化派生。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">bip32</span><span class="p">::{</span><span class="n">Xpriv</span><span class="p">,</span> <span class="n">Xpub</span><span class="p">,</span> <span class="n">DerivationPath</span><span class="p">,</span> <span class="n">ChildNumber</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="n">Network</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">secp256k1</span><span class="p">::</span><span class="n">Secp256k1</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">hd_derivation_demo</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">secp</span> <span class="o">=</span> <span class="nn">Secp256k1</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

    <span class="c1">// 從種子創建主私鑰</span>
    <span class="k">let</span> <span class="n">seed</span> <span class="o">=</span> <span class="nn">hex</span><span class="p">::</span><span class="nf">decode</span><span class="p">(</span>
        <span class="s">"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"</span>
    <span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="k">let</span> <span class="n">master_xpriv</span> <span class="o">=</span> <span class="nn">Xpriv</span><span class="p">::</span><span class="nf">new_master</span><span class="p">(</span><span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">seed</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">master_xpub</span> <span class="o">=</span> <span class="nn">Xpub</span><span class="p">::</span><span class="nf">from_priv</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">master_xpriv</span><span class="p">);</span>

    <span class="c1">// 硬化派生：m/0'</span>
    <span class="c1">// 注意 index 使用 0，但指定為 Hardened</span>
    <span class="k">let</span> <span class="n">child_hardened</span> <span class="o">=</span> <span class="n">master_xpriv</span><span class="nf">.derive_priv</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="n">secp</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="p">[</span><span class="nn">ChildNumber</span><span class="p">::</span><span class="n">Hardened</span> <span class="p">{</span> <span class="n">index</span><span class="p">:</span> <span class="mi">0</span> <span class="p">}]</span>
    <span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 普通派生：m/0</span>
    <span class="k">let</span> <span class="n">child_normal</span> <span class="o">=</span> <span class="n">master_xpriv</span><span class="nf">.derive_priv</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="n">secp</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="p">[</span><span class="nn">ChildNumber</span><span class="p">::</span><span class="n">Normal</span> <span class="p">{</span> <span class="n">index</span><span class="p">:</span> <span class="mi">0</span> <span class="p">}]</span>
    <span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 使用派生路徑字串</span>
    <span class="k">let</span> <span class="n">path</span> <span class="o">=</span> <span class="nn">DerivationPath</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="s">"m/84'/0'/0'/0/0"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">derived</span> <span class="o">=</span> <span class="n">master_xpriv</span><span class="nf">.derive_priv</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">path</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="13-bip39-助記詞">1.3 BIP39 助記詞</h3>

<p>雖然種子是一串隨機的位元組，但人類很難記憶或抄寫這樣的數據。BIP39 提出了助記詞（mnemonic）的概念，將種子編碼為一組英文單詞。</p>

<p>助記詞的生成過程是這樣的：首先生成一定長度的隨機熵（128 到 256 位），然後計算熵的 SHA256 校驗和，取校驗和的前幾位附加到熵的末尾。最後將這個組合數據分割成 11 位一組，每組對應 BIP39 字典中的一個單詞。</p>

<p>熵的長度決定了助記詞的數量。128 位熵產生 12 個單詞，256 位熵產生 24 個單詞。更長的助記詞提供更高的安全性，但 12 個單詞（128 位熵）在實際使用中已經足夠安全。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bip39</span><span class="p">::{</span><span class="n">Mnemonic</span><span class="p">,</span> <span class="n">Language</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">mnemonic_demo</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 生成 12 字助記詞</span>
    <span class="k">let</span> <span class="n">mnemonic</span> <span class="o">=</span> <span class="nn">Mnemonic</span><span class="p">::</span><span class="nf">generate_in</span><span class="p">(</span><span class="nn">Language</span><span class="p">::</span><span class="n">English</span><span class="p">,</span> <span class="mi">12</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"助記詞: {}"</span><span class="p">,</span> <span class="n">mnemonic</span><span class="p">);</span>

    <span class="c1">// 從助記詞生成種子</span>
    <span class="c1">// 可以使用可選的密碼短語增加安全性</span>
    <span class="k">let</span> <span class="n">passphrase</span> <span class="o">=</span> <span class="s">""</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">seed</span> <span class="o">=</span> <span class="n">mnemonic</span><span class="nf">.to_seed</span><span class="p">(</span><span class="n">passphrase</span><span class="p">);</span>

    <span class="c1">// 種子是 512 位（64 bytes）</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"種子長度: {} bytes"</span><span class="p">,</span> <span class="n">seed</span><span class="nf">.len</span><span class="p">());</span>

    <span class="c1">// 驗證現有助記詞</span>
    <span class="k">let</span> <span class="n">phrase</span> <span class="o">=</span> <span class="s">"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">parsed</span> <span class="o">=</span> <span class="nn">Mnemonic</span><span class="p">::</span><span class="nf">parse_in</span><span class="p">(</span><span class="nn">Language</span><span class="p">::</span><span class="n">English</span><span class="p">,</span> <span class="n">phrase</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 獲取原始熵</span>
    <span class="k">let</span> <span class="n">entropy</span> <span class="o">=</span> <span class="n">parsed</span><span class="nf">.to_entropy</span><span class="p">();</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"熵: {}"</span><span class="p">,</span> <span class="nn">hex</span><span class="p">::</span><span class="nf">encode</span><span class="p">(</span><span class="o">&amp;</span><span class="n">entropy</span><span class="p">));</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>密碼短語（passphrase）是一個重要但經常被忽視的功能。它在從助記詞生成種子時作為額外的輸入。不同的密碼短語會生成完全不同的種子，從而產生完全不同的錢包。這提供了額外的安全層：即使攻擊者獲得了你的助記詞，沒有密碼短語他也無法存取你的資金。這也可以用來創建「誘餌錢包」——使用空密碼短語的錢包放少量資金，真正的資金存放在有密碼短語的錢包中。</p>

<h3 id="14-標準派生路徑">1.4 標準派生路徑</h3>

<p>隨著時間推移，社區制定了多個 BIP 來標準化派生路徑，確保不同錢包軟體之間的互操作性。</p>

<p><strong>BIP44</strong> 定義了傳統地址（P2PKH）的路徑格式：<code class="language-plaintext highlighter-rouge">m/44'/coin'/account'/change/index</code>。這裡 44 是 purpose，表示遵循 BIP44 標準。coin 是幣種代碼（Bitcoin 是 0，Testnet 是 1）。account 允許用戶在同一個錢包中維護多個獨立的帳戶。change 為 0 表示外部鏈（用於接收付款），為 1 表示內部鏈（用於找零）。index 是地址的序號。</p>

<p><strong>BIP49</strong> 為 P2SH-P2WPKH（兼容 SegWit）地址定義了 purpose 49。</p>

<p><strong>BIP84</strong> 為原生 SegWit（P2WPKH）地址定義了 purpose 84。</p>

<p><strong>BIP86</strong> 為 Taproot（P2TR）地址定義了 purpose 86。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">bip32</span><span class="p">::{</span><span class="n">Xpriv</span><span class="p">,</span> <span class="n">DerivationPath</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::{</span><span class="n">Network</span><span class="p">,</span> <span class="n">Address</span><span class="p">,</span> <span class="n">PublicKey</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">secp256k1</span><span class="p">::</span><span class="n">Secp256k1</span><span class="p">;</span>

<span class="k">struct</span> <span class="n">HDWallet</span> <span class="p">{</span>
    <span class="n">master_xpriv</span><span class="p">:</span> <span class="n">Xpriv</span><span class="p">,</span>
    <span class="n">network</span><span class="p">:</span> <span class="n">Network</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">HDWallet</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">derive_address</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="p">,</span> <span class="n">purpose</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">account</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">is_change</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span> <span class="n">index</span><span class="p">:</span> <span class="nb">u32</span><span class="p">)</span>
        <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Address</span><span class="o">&gt;</span>
    <span class="p">{</span>
        <span class="k">let</span> <span class="n">secp</span> <span class="o">=</span> <span class="nn">Secp256k1</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">coin</span> <span class="o">=</span> <span class="k">if</span> <span class="k">self</span><span class="py">.network</span> <span class="o">==</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span> <span class="p">{</span> <span class="mi">0</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="mi">1</span> <span class="p">};</span>
        <span class="k">let</span> <span class="n">change</span> <span class="o">=</span> <span class="k">if</span> <span class="n">is_change</span> <span class="p">{</span> <span class="mi">1</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="mi">0</span> <span class="p">};</span>

        <span class="k">let</span> <span class="n">path</span> <span class="o">=</span> <span class="nn">DerivationPath</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span>
            <span class="s">"m/{}'/{}'/{}'/{}'/{}"</span><span class="p">,</span>
            <span class="n">purpose</span><span class="p">,</span> <span class="n">coin</span><span class="p">,</span> <span class="n">account</span><span class="p">,</span> <span class="n">change</span><span class="p">,</span> <span class="n">index</span>
        <span class="p">))</span><span class="o">?</span><span class="p">;</span>

        <span class="k">let</span> <span class="n">derived_xpriv</span> <span class="o">=</span> <span class="k">self</span><span class="py">.master_xpriv</span><span class="nf">.derive_priv</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">path</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">public_key</span> <span class="o">=</span> <span class="nn">PublicKey</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span>
            <span class="n">derived_xpriv</span><span class="nf">.to_priv</span><span class="p">()</span><span class="nf">.public_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">)</span>
        <span class="p">);</span>

        <span class="k">let</span> <span class="n">address</span> <span class="o">=</span> <span class="k">match</span> <span class="n">purpose</span> <span class="p">{</span>
            <span class="mi">44</span> <span class="k">=&gt;</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">p2pkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">public_key</span><span class="p">,</span> <span class="k">self</span><span class="py">.network</span><span class="p">),</span>
            <span class="mi">49</span> <span class="k">=&gt;</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">p2shwpkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">public_key</span><span class="p">,</span> <span class="k">self</span><span class="py">.network</span><span class="p">),</span>
            <span class="mi">84</span> <span class="k">=&gt;</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">p2wpkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">public_key</span><span class="p">,</span> <span class="k">self</span><span class="py">.network</span><span class="p">),</span>
            <span class="mi">86</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="k">let</span> <span class="n">internal_key</span> <span class="o">=</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">key</span><span class="p">::</span><span class="nn">UntweakedPublicKey</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span>
                    <span class="n">derived_xpriv</span><span class="nf">.to_priv</span><span class="p">()</span><span class="nf">.public_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">)</span>
                <span class="p">);</span>
                <span class="nn">Address</span><span class="p">::</span><span class="nf">p2tr</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">,</span> <span class="n">internal_key</span><span class="p">,</span> <span class="nb">None</span><span class="p">,</span> <span class="k">self</span><span class="py">.network</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="n">_</span> <span class="k">=&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nd">bail!</span><span class="p">(</span><span class="s">"不支援的 purpose: {}"</span><span class="p">,</span> <span class="n">purpose</span><span class="p">),</span>
        <span class="p">};</span>

        <span class="nf">Ok</span><span class="p">(</span><span class="n">address</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="2-地址類型詳解">2. 地址類型詳解</h2>

<h3 id="21-地址的本質">2.1 地址的本質</h3>

<p>Bitcoin 地址不是存儲資金的「帳戶」，而是指定誰可以花費資金的「條件」的編碼形式。當你發送 Bitcoin 到一個地址時，實際上是在創建一個輸出，這個輸出被一個特定的腳本鎖定。地址是這個鎖定腳本的便於人類閱讀的表示。</p>

<p>不同的地址類型對應不同的腳本模式。理解這些差異對於開發者來說很重要，因為它們影響交易大小、費用和功能。</p>

<h3 id="22-p2pkhpay-to-public-key-hash">2.2 P2PKH（Pay-to-Public-Key-Hash）</h3>

<p>P2PKH 是最原始的地址類型，以數字 1 開頭。它的鎖定腳本要求提供一個公鑰和對應的簽名，且公鑰的雜湊值必須匹配地址中編碼的值。</p>

<p>這種設計有一個隱私優點：在資金被花費之前，公鑰不會出現在區塊鏈上，只有公鑰的雜湊值是可見的。這為抵禦某些理論上的量子計算攻擊提供了一定保護——即使量子電腦能從公鑰推導私鑰，在公鑰暴露之前，資金仍然是安全的。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::{</span><span class="n">Address</span><span class="p">,</span> <span class="n">PublicKey</span><span class="p">,</span> <span class="n">Network</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">p2pkh_demo</span><span class="p">(</span><span class="n">public_key</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PublicKey</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">address</span> <span class="o">=</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">p2pkh</span><span class="p">(</span><span class="n">public_key</span><span class="p">,</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span><span class="p">);</span>
    <span class="c1">// 格式：1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2</span>

    <span class="c1">// 對應的 scriptPubKey：</span>
    <span class="c1">// OP_DUP OP_HASH160 &lt;pubkey_hash&gt; OP_EQUALVERIFY OP_CHECKSIG</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="23-p2shpay-to-script-hash">2.3 P2SH（Pay-to-Script-Hash）</h3>

<p>P2SH 以數字 3 開頭，允許支付到任意腳本的雜湊值，而不是特定的公鑰雜湊。這種靈活性使得複雜的腳本（如多重簽名）可以使用簡短的地址表示。</p>

<p>P2SH 的一個常見用途是包裝 SegWit 腳本（P2SH-P2WPKH 和 P2SH-P2WSH），為不支援原生 SegWit 地址的舊錢包提供兼容性。發送者不需要知道底層使用的是什麼腳本——他們只需支付到這個以 3 開頭的地址即可。</p>

<h3 id="24-p2wpkh原生-segwit">2.4 P2WPKH（原生 SegWit）</h3>

<p>P2WPKH 是 SegWit 升級引入的原生見證地址類型，以 bc1q 開頭。它的功能與 P2PKH 類似，但簽名數據被移到了交易的「見證」部分，不計入傳統的區塊大小限制。</p>

<p>這帶來了多個好處。首先是費用節省：見證數據的費用權重只有非見證數據的四分之一，典型的 P2WPKH 交易比 P2PKH 交易便宜約 38%。其次是解決了交易可延展性（malleability）問題，這對於閃電網路等二層協議至關重要。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">p2wpkh_demo</span><span class="p">(</span><span class="n">public_key</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PublicKey</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">address</span> <span class="o">=</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">p2wpkh</span><span class="p">(</span><span class="n">public_key</span><span class="p">,</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span><span class="p">);</span>
    <span class="c1">// 格式：bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4</span>

    <span class="c1">// scriptPubKey 很簡短：</span>
    <span class="c1">// OP_0 &lt;20-byte-pubkey-hash&gt;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="25-p2trtaproot">2.5 P2TR（Taproot）</h3>

<p>P2TR 是 2021 年啟用的 Taproot 升級引入的地址類型，以 bc1p 開頭。它代表了 Bitcoin 腳本能力的重大進步。</p>

<p>Taproot 地址可以通過兩種方式花費：密鑰路徑（key path）和腳本路徑（script path）。密鑰路徑是最簡單的情況——只需提供一個 Schnorr 簽名。腳本路徑允許使用複雜的條件，但這些條件被隱藏在 Merkle 樹中，只有在使用特定條件時才會揭示。</p>

<p>這種設計的隱私優勢是巨大的。無論底層腳本多複雜，如果所有參與者同意（使用密鑰路徑），交易看起來就像一個普通的單簽名交易。觀察者無法區分簡單支付和複雜的多方協議。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">p2tr_demo</span><span class="p">(</span><span class="n">secp</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Secp256k1</span><span class="o">&lt;</span><span class="nn">secp256k1</span><span class="p">::</span><span class="n">All</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">private_key</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PrivateKey</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">internal_key</span> <span class="o">=</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">key</span><span class="p">::</span><span class="nn">UntweakedPublicKey</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span>
        <span class="n">private_key</span><span class="nf">.public_key</span><span class="p">(</span><span class="n">secp</span><span class="p">)</span>
    <span class="p">);</span>

    <span class="c1">// 只有密鑰路徑，沒有腳本樹</span>
    <span class="k">let</span> <span class="n">address</span> <span class="o">=</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">p2tr</span><span class="p">(</span><span class="n">secp</span><span class="p">,</span> <span class="n">internal_key</span><span class="p">,</span> <span class="nb">None</span><span class="p">,</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span><span class="p">);</span>
    <span class="c1">// 格式：bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="26-地址解析與驗證">2.6 地址解析與驗證</h3>

<p>在處理用戶輸入的地址時，驗證非常重要。無效的地址會導致資金永久丟失。rust-bitcoin 提供了完整的地址解析和驗證功能。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::{</span><span class="n">Address</span><span class="p">,</span> <span class="n">Network</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">str</span><span class="p">::</span><span class="n">FromStr</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">validate_address</span><span class="p">(</span><span class="n">addr_str</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span> <span class="n">expected_network</span><span class="p">:</span> <span class="n">Network</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Address</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 解析地址（這會驗證格式和校驗和）</span>
    <span class="k">let</span> <span class="n">address</span> <span class="o">=</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="n">addr_str</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 驗證網路</span>
    <span class="k">if</span> <span class="o">!</span><span class="n">address</span><span class="nf">.is_valid_for_network</span><span class="p">(</span><span class="n">expected_network</span><span class="p">)</span> <span class="p">{</span>
        <span class="nn">anyhow</span><span class="p">::</span><span class="nd">bail!</span><span class="p">(</span><span class="s">"地址網路不匹配"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// assume_checked 表示我們已經驗證過了</span>
    <span class="nf">Ok</span><span class="p">(</span><span class="n">address</span><span class="nf">.assume_checked</span><span class="p">())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="3-理解-utxo-模型">3. 理解 UTXO 模型</h2>

<h3 id="31-utxo-vs-帳戶模型">3.1 UTXO vs 帳戶模型</h3>

<p>Bitcoin 使用 UTXO（Unspent Transaction Output，未花費交易輸出）模型，這與以太坊使用的帳戶模型有本質區別。</p>

<p>在帳戶模型中，每個地址有一個餘額，交易就是從一個帳戶減少餘額、向另一個帳戶增加餘額。這類似於傳統銀行帳戶的運作方式。</p>

<p>在 UTXO 模型中，沒有「餘額」的概念。取而代之的是一組獨立的「硬幣」——每個 UTXO 是之前某筆交易創建的一個輸出，它有特定的金額和花費條件。你的「餘額」是所有屬於你的 UTXO 的金額總和。</p>

<p>當你發送 Bitcoin 時，你必須選擇一個或多個 UTXO 作為輸入，完全消耗它們，然後創建新的輸出。如果輸入總額超過你想發送的金額，你需要創建一個「找零」輸出，將多餘的資金發回給自己。</p>

<p>這個模型有幾個重要的含義。首先，每個 UTXO 只能被花費一次——這是 Bitcoin 如何防止雙重支付的。其次，交易的隱私性受到 UTXO 選擇的影響——如果你合併多個 UTXO，觀察者可以推斷它們可能屬於同一個人。第三，UTXO 的數量會影響未來交易的費用——更多的輸入意味著更大的交易和更高的費用。</p>

<h3 id="32-選幣策略">3.2 選幣策略</h3>

<p>當構建交易時，選擇使用哪些 UTXO 是一個重要的決策。不同的選幣策略有不同的權衡。</p>

<p>最簡單的策略是「最大優先」——優先選擇金額最大的 UTXO。這通常能最小化所需的輸入數量，從而減少交易費用。但它也有缺點：小額 UTXO 可能永遠不會被使用，隨著時間推移，你會累積大量「粉塵」。</p>

<p>「先進先出」（FIFO）策略按 UTXO 的年齡排序，優先使用最老的。這有助於維持 UTXO 集的「健康」，避免碎片化。</p>

<p>更複雜的策略會考慮隱私因素，避免合併來自不同來源的 UTXO，或者使用「隨機」選擇來增加觀察者的分析難度。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
</pre></td><td class="rouge-code"><pre><span class="k">struct</span> <span class="n">UTXOSelector</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">UTXOSelector</span> <span class="p">{</span>
    <span class="k">fn</span> <span class="nf">select_largest_first</span><span class="p">(</span>
        <span class="n">utxos</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="n">UTXO</span><span class="p">],</span>
        <span class="n">target</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
        <span class="n">fee_rate</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">UTXO</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">sorted</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">_</span><span class="o">&gt;</span> <span class="o">=</span> <span class="n">utxos</span><span class="nf">.to_vec</span><span class="p">();</span>
        <span class="n">sorted</span><span class="nf">.sort_by</span><span class="p">(|</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">|</span> <span class="n">b</span><span class="py">.amount</span><span class="nf">.cmp</span><span class="p">(</span><span class="o">&amp;</span><span class="n">a</span><span class="py">.amount</span><span class="p">));</span>

        <span class="k">let</span> <span class="k">mut</span> <span class="n">selected</span> <span class="o">=</span> <span class="nn">Vec</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">total</span> <span class="o">=</span> <span class="mi">0u64</span><span class="p">;</span>

        <span class="k">for</span> <span class="n">utxo</span> <span class="k">in</span> <span class="n">sorted</span> <span class="p">{</span>
            <span class="c1">// 估算這個輸入的費用</span>
            <span class="k">let</span> <span class="n">input_fee</span> <span class="o">=</span> <span class="k">Self</span><span class="p">::</span><span class="nf">estimate_input_fee</span><span class="p">(</span><span class="n">fee_rate</span><span class="p">);</span>

            <span class="c1">// 跳過粉塵 UTXO</span>
            <span class="k">if</span> <span class="n">utxo</span><span class="py">.amount</span> <span class="o">&lt;</span> <span class="n">input_fee</span> <span class="p">{</span>
                <span class="k">continue</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="n">selected</span><span class="nf">.push</span><span class="p">(</span><span class="n">utxo</span><span class="p">);</span>
            <span class="n">total</span> <span class="o">+=</span> <span class="n">utxo</span><span class="py">.amount</span><span class="p">;</span>

            <span class="c1">// 檢查是否已經夠了</span>
            <span class="k">let</span> <span class="n">required</span> <span class="o">=</span> <span class="n">target</span> <span class="o">+</span> <span class="k">Self</span><span class="p">::</span><span class="nf">estimate_total_fee</span><span class="p">(</span><span class="o">&amp;</span><span class="n">selected</span><span class="p">,</span> <span class="n">fee_rate</span><span class="p">);</span>
            <span class="k">if</span> <span class="n">total</span> <span class="o">&gt;=</span> <span class="n">required</span> <span class="p">{</span>
                <span class="k">return</span> <span class="nf">Some</span><span class="p">(</span><span class="n">selected</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="nb">None</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">estimate_input_fee</span><span class="p">(</span><span class="n">fee_rate</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u64</span> <span class="p">{</span>
        <span class="mi">68</span> <span class="o">*</span> <span class="n">fee_rate</span>  <span class="c1">// P2WPKH 輸入約 68 vbytes</span>
    <span class="p">}</span>

    <span class="k">fn</span> <span class="nf">estimate_total_fee</span><span class="p">(</span><span class="n">inputs</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="n">UTXO</span><span class="p">],</span> <span class="n">fee_rate</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u64</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">base</span> <span class="o">=</span> <span class="mi">10</span> <span class="o">*</span> <span class="n">fee_rate</span><span class="p">;</span>  <span class="c1">// 基礎開銷</span>
        <span class="k">let</span> <span class="n">outputs</span> <span class="o">=</span> <span class="mi">31</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">fee_rate</span><span class="p">;</span>  <span class="c1">// 兩個輸出</span>
        <span class="k">let</span> <span class="n">inputs_fee</span> <span class="o">=</span> <span class="n">inputs</span><span class="nf">.len</span><span class="p">()</span> <span class="k">as</span> <span class="nb">u64</span> <span class="o">*</span> <span class="mi">68</span> <span class="o">*</span> <span class="n">fee_rate</span><span class="p">;</span>
        <span class="n">base</span> <span class="o">+</span> <span class="n">outputs</span> <span class="o">+</span> <span class="n">inputs_fee</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="4-交易結構詳解">4. 交易結構詳解</h2>

<h3 id="41-交易的組成">4.1 交易的組成</h3>

<p>一筆 Bitcoin 交易由幾個部分組成：</p>

<p><strong>版本號（Version）</strong>指示交易遵循的規則。版本 2 啟用了 BIP68 定義的相對時間鎖功能。</p>

<p><strong>輸入（Inputs）</strong>是這筆交易花費的 UTXO 列表。每個輸入包含：</p>
<ul>
  <li>前一筆交易的 ID（txid）和輸出索引（vout），用於識別被花費的 UTXO</li>
  <li>簽名腳本（scriptSig），對於 SegWit 交易這裡通常是空的</li>
  <li>序列號（sequence），用於相對時間鎖和 RBF（Replace-By-Fee）信號</li>
</ul>

<p><strong>輸出（Outputs）</strong>是這筆交易創建的新 UTXO。每個輸出包含：</p>
<ul>
  <li>金額（以 satoshi 為單位）</li>
  <li>鎖定腳本（scriptPubKey），定義誰可以花費這個輸出</li>
</ul>

<p><strong>見證（Witness）</strong>是 SegWit 引入的新欄位，包含每個輸入的簽名和其他驗證數據。</p>

<p><strong>鎖定時間（LockTime）</strong>指定這筆交易可以被包含到區塊的最早時間（區塊高度或時間戳）。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::{</span>
    <span class="n">Transaction</span><span class="p">,</span> <span class="n">TxIn</span><span class="p">,</span> <span class="n">TxOut</span><span class="p">,</span> <span class="n">OutPoint</span><span class="p">,</span> <span class="n">Txid</span><span class="p">,</span> <span class="n">Sequence</span><span class="p">,</span> <span class="n">Witness</span><span class="p">,</span>
    <span class="n">ScriptBuf</span><span class="p">,</span> <span class="n">Amount</span><span class="p">,</span> <span class="nn">transaction</span><span class="p">::</span><span class="n">Version</span><span class="p">,</span> <span class="nn">absolute</span><span class="p">::</span><span class="n">LockTime</span><span class="p">,</span>
<span class="p">};</span>

<span class="k">fn</span> <span class="nf">create_transaction_structure</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="n">Transaction</span> <span class="p">{</span>
    <span class="c1">// 創建輸入</span>
    <span class="k">let</span> <span class="n">input</span> <span class="o">=</span> <span class="n">TxIn</span> <span class="p">{</span>
        <span class="n">previous_output</span><span class="p">:</span> <span class="n">OutPoint</span> <span class="p">{</span>
            <span class="n">txid</span><span class="p">:</span> <span class="nn">Txid</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="s">"..."</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">(),</span>
            <span class="n">vout</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="n">script_sig</span><span class="p">:</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new</span><span class="p">(),</span>  <span class="c1">// SegWit 交易為空</span>
        <span class="n">sequence</span><span class="p">:</span> <span class="nn">Sequence</span><span class="p">::</span><span class="n">ENABLE_RBF_NO_LOCKTIME</span><span class="p">,</span>
        <span class="n">witness</span><span class="p">:</span> <span class="nn">Witness</span><span class="p">::</span><span class="nf">new</span><span class="p">(),</span>
    <span class="p">};</span>

    <span class="c1">// 創建輸出</span>
    <span class="k">let</span> <span class="n">output</span> <span class="o">=</span> <span class="n">TxOut</span> <span class="p">{</span>
        <span class="n">value</span><span class="p">:</span> <span class="nn">Amount</span><span class="p">::</span><span class="nf">from_sat</span><span class="p">(</span><span class="mi">50_000</span><span class="p">),</span>
        <span class="n">script_pubkey</span><span class="p">:</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new_p2wpkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wpkh</span><span class="p">),</span>
    <span class="p">};</span>

    <span class="n">Transaction</span> <span class="p">{</span>
        <span class="n">version</span><span class="p">:</span> <span class="nn">Version</span><span class="p">::</span><span class="n">TWO</span><span class="p">,</span>
        <span class="n">lock_time</span><span class="p">:</span> <span class="nn">LockTime</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
        <span class="n">input</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">input</span><span class="p">],</span>
        <span class="n">output</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">output</span><span class="p">],</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="42-交易大小與費用">4.2 交易大小與費用</h3>

<p>Bitcoin 的交易費用是按交易大小計算的，但 SegWit 引入了「權重」的概念來更精確地衡量交易對區塊空間的消耗。</p>

<p>交易權重的計算方式是：非見證數據的位元組數乘以 4，加上見證數據的位元組數。然後將權重除以 4 得到「虛擬大小」（vsize），這是計算費用時使用的單位。</p>

<p>這個設計給了見證數據 75% 的「折扣」，鼓勵使用 SegWit，因為見證數據不會永久存儲在每個節點的 UTXO 集中。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="k">struct</span> <span class="n">FeeCalculator</span><span class="p">;</span>

<span class="k">impl</span> <span class="n">FeeCalculator</span> <span class="p">{</span>
    <span class="c1">// P2WPKH 輸入的重量：約 271 WU（68 vbytes）</span>
    <span class="c1">// P2TR 輸入的重量：約 229 WU（58 vbytes）</span>
    <span class="c1">// P2WPKH 輸出的重量：124 WU（31 vbytes）</span>
    <span class="c1">// P2TR 輸出的重量：172 WU（43 vbytes）</span>

    <span class="k">fn</span> <span class="nf">calculate_fee</span><span class="p">(</span><span class="n">tx</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Transaction</span><span class="p">,</span> <span class="n">fee_rate</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u64</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">weight</span> <span class="o">=</span> <span class="n">tx</span><span class="nf">.weight</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">vsize</span> <span class="o">=</span> <span class="n">weight</span><span class="nf">.to_vbytes_ceil</span><span class="p">();</span>
        <span class="n">vsize</span> <span class="o">*</span> <span class="n">fee_rate</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="43-rbfreplace-by-fee">4.3 RBF（Replace-By-Fee）</h3>

<p>RBF 是一種允許未確認交易被替換的機制。當一筆交易的確認時間比預期長（因為費用設得太低），發送者可以廣播一個新版本，支付更高的費用。</p>

<p>要啟用 RBF，至少一個輸入的序列號必須小於 0xFFFFFFFE。rust-bitcoin 提供了便利的常量來設置這個值。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="n">Sequence</span><span class="p">;</span>

<span class="c1">// 啟用 RBF，不使用鎖定時間</span>
<span class="k">let</span> <span class="n">sequence</span> <span class="o">=</span> <span class="nn">Sequence</span><span class="p">::</span><span class="n">ENABLE_RBF_NO_LOCKTIME</span><span class="p">;</span>

<span class="c1">// 最大序列號（禁用 RBF）</span>
<span class="k">let</span> <span class="n">no_rbf</span> <span class="o">=</span> <span class="nn">Sequence</span><span class="p">::</span><span class="n">MAX</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="5-構建並簽名交易">5. 構建並簽名交易</h2>

<h3 id="51-簽名過程">5.1 簽名過程</h3>

<p>簽名一筆 Bitcoin 交易涉及多個步驟。首先需要計算「簽名哈希」（sighash），這是對交易數據的一個特定摘要，簽名者需要對這個摘要簽名。</p>

<p>對於 SegWit 交易，簽名哈希的計算遵循 BIP143 的規定，這與傳統交易不同。主要區別是 BIP143 的 sighash 包含每個輸入被花費的 UTXO 的金額，這消除了某些類型的攻擊。</p>

<p>簽名類型（sighash type）指定了簽名涵蓋交易的哪些部分：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">SIGHASH_ALL</code>（最常見）：簽名涵蓋所有輸入和輸出</li>
  <li><code class="language-plaintext highlighter-rouge">SIGHASH_NONE</code>：只簽名輸入，允許任何人修改輸出</li>
  <li><code class="language-plaintext highlighter-rouge">SIGHASH_SINGLE</code>：簽名一個輸入和對應的輸出</li>
  <li>可以與 <code class="language-plaintext highlighter-rouge">ANYONECANPAY</code> 組合，只簽名當前輸入，允許添加更多輸入</li>
</ul>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">sighash</span><span class="p">::{</span><span class="n">SighashCache</span><span class="p">,</span> <span class="n">EcdsaSighashType</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">secp256k1</span><span class="p">::{</span><span class="n">Secp256k1</span><span class="p">,</span> <span class="n">Message</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">sign_p2wpkh_input</span><span class="p">(</span>
    <span class="n">tx</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Transaction</span><span class="p">,</span>
    <span class="n">input_index</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
    <span class="n">utxo_amount</span><span class="p">:</span> <span class="n">Amount</span><span class="p">,</span>
    <span class="n">private_key</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PrivateKey</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">secp</span> <span class="o">=</span> <span class="nn">Secp256k1</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">public_key</span> <span class="o">=</span> <span class="n">private_key</span><span class="nf">.public_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">);</span>

    <span class="c1">// P2WPKH 的 scriptCode 是對應 P2PKH 的 scriptPubKey</span>
    <span class="k">let</span> <span class="n">script_code</span> <span class="o">=</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new_p2pkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">public_key</span><span class="nf">.pubkey_hash</span><span class="p">());</span>

    <span class="c1">// 計算簽名哈希</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">sighash_cache</span> <span class="o">=</span> <span class="nn">SighashCache</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;*</span><span class="n">tx</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">sighash</span> <span class="o">=</span> <span class="n">sighash_cache</span><span class="nf">.p2wpkh_signature_hash</span><span class="p">(</span>
        <span class="n">input_index</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">script_code</span><span class="p">,</span>
        <span class="n">utxo_amount</span><span class="p">,</span>
        <span class="nn">EcdsaSighashType</span><span class="p">::</span><span class="n">All</span><span class="p">,</span>
    <span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 創建簽名</span>
    <span class="k">let</span> <span class="n">message</span> <span class="o">=</span> <span class="nn">Message</span><span class="p">::</span><span class="nf">from_digest_slice</span><span class="p">(</span><span class="n">sighash</span><span class="nf">.as_byte_array</span><span class="p">())</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">signature</span> <span class="o">=</span> <span class="n">secp</span><span class="nf">.sign_ecdsa</span><span class="p">(</span><span class="o">&amp;</span><span class="n">message</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">private_key</span><span class="py">.inner</span><span class="p">);</span>

    <span class="c1">// 組裝 witness：[signature, pubkey]</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">sig_bytes</span> <span class="o">=</span> <span class="n">signature</span><span class="nf">.serialize_der</span><span class="p">()</span><span class="nf">.to_vec</span><span class="p">();</span>
    <span class="n">sig_bytes</span><span class="nf">.push</span><span class="p">(</span><span class="nn">EcdsaSighashType</span><span class="p">::</span><span class="n">All</span><span class="nf">.to_u32</span><span class="p">()</span> <span class="k">as</span> <span class="nb">u8</span><span class="p">);</span>

    <span class="n">tx</span><span class="py">.input</span><span class="p">[</span><span class="n">input_index</span><span class="p">]</span><span class="py">.witness</span><span class="nf">.push</span><span class="p">(</span><span class="n">sig_bytes</span><span class="p">);</span>
    <span class="n">tx</span><span class="py">.input</span><span class="p">[</span><span class="n">input_index</span><span class="p">]</span><span class="py">.witness</span><span class="nf">.push</span><span class="p">(</span><span class="n">public_key</span><span class="nf">.to_bytes</span><span class="p">());</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="52-psbt部分簽名交易">5.2 PSBT（部分簽名交易）</h3>

<p>PSBT（Partially Signed Bitcoin Transaction）是 BIP174 定義的標準格式，用於在多方之間傳遞待簽名的交易。它對於多重簽名、硬體錢包和其他需要多步驟簽名的場景非常有用。</p>

<p>PSBT 包含了簽名者需要的所有資訊：未簽名的交易、每個輸入被花費的 UTXO 資訊、派生路徑等。簽名者可以添加自己的簽名，然後將 PSBT 傳遞給下一個簽名者或組合者。</p>

<p>一個典型的 PSBT 工作流程是：</p>
<ol>
  <li><strong>創建者</strong>創建一個基礎的 PSBT，包含未簽名交易</li>
  <li><strong>更新者</strong>添加 UTXO 資訊、派生路徑等元數據</li>
  <li><strong>簽名者</strong>為他們控制的輸入添加簽名</li>
  <li><strong>組合者</strong>將多個部分簽名的 PSBT 合併</li>
  <li><strong>最終化者</strong>完成所有輸入的腳本，生成可廣播的交易</li>
  <li><strong>廣播者</strong>將交易廣播到網路</li>
</ol>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">psbt</span><span class="p">::</span><span class="n">Psbt</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">psbt_workflow</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// 創建未簽名交易</span>
    <span class="k">let</span> <span class="n">unsigned_tx</span> <span class="o">=</span> <span class="nf">create_unsigned_transaction</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 創建 PSBT</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">psbt</span> <span class="o">=</span> <span class="nn">Psbt</span><span class="p">::</span><span class="nf">from_unsigned_tx</span><span class="p">(</span><span class="n">unsigned_tx</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 添加 UTXO 資訊（簽名者需要這些）</span>
    <span class="n">psbt</span><span class="py">.inputs</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="py">.witness_utxo</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="n">TxOut</span> <span class="p">{</span>
        <span class="n">value</span><span class="p">:</span> <span class="nn">Amount</span><span class="p">::</span><span class="nf">from_sat</span><span class="p">(</span><span class="mi">100_000</span><span class="p">),</span>
        <span class="n">script_pubkey</span><span class="p">:</span> <span class="n">sender_script</span><span class="p">,</span>
    <span class="p">});</span>

    <span class="c1">// 序列化為 Base64（用於傳輸）</span>
    <span class="k">let</span> <span class="n">psbt_base64</span> <span class="o">=</span> <span class="n">psbt</span><span class="nf">.to_string</span><span class="p">();</span>

    <span class="c1">// 簽名者解析並簽名</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">psbt</span> <span class="o">=</span> <span class="nn">Psbt</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="o">&amp;</span><span class="n">psbt_base64</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="c1">// ... 添加簽名 ...</span>

    <span class="c1">// 最終化並提取交易</span>
    <span class="n">psbt</span><span class="nf">.finalize_mut</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"最終化失敗"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">signed_tx</span> <span class="o">=</span> <span class="n">psbt</span><span class="nf">.extract_tx</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="6-實戰完整的錢包實現">6. 實戰：完整的錢包實現</h2>

<p>下面是一個簡單但完整的錢包實現，展示了從地址生成到交易廣播的整個流程。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::{</span>
    <span class="n">Network</span><span class="p">,</span> <span class="n">Address</span><span class="p">,</span> <span class="n">PrivateKey</span><span class="p">,</span> <span class="n">PublicKey</span><span class="p">,</span>
    <span class="n">Transaction</span><span class="p">,</span> <span class="n">TxIn</span><span class="p">,</span> <span class="n">TxOut</span><span class="p">,</span> <span class="n">OutPoint</span><span class="p">,</span> <span class="n">Txid</span><span class="p">,</span> <span class="n">Sequence</span><span class="p">,</span> <span class="n">Witness</span><span class="p">,</span>
    <span class="n">ScriptBuf</span><span class="p">,</span> <span class="n">Amount</span><span class="p">,</span>
    <span class="nn">sighash</span><span class="p">::{</span><span class="n">SighashCache</span><span class="p">,</span> <span class="n">EcdsaSighashType</span><span class="p">},</span>
    <span class="nn">transaction</span><span class="p">::</span><span class="n">Version</span><span class="p">,</span>
    <span class="nn">absolute</span><span class="p">::</span><span class="n">LockTime</span><span class="p">,</span>
    <span class="nn">secp256k1</span><span class="p">::</span><span class="n">Secp256k1</span><span class="p">,</span>
<span class="p">};</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">Wallet</span> <span class="p">{</span>
    <span class="n">private_key</span><span class="p">:</span> <span class="n">PrivateKey</span><span class="p">,</span>
    <span class="n">public_key</span><span class="p">:</span> <span class="n">PublicKey</span><span class="p">,</span>
    <span class="n">address</span><span class="p">:</span> <span class="n">Address</span><span class="p">,</span>
    <span class="n">network</span><span class="p">:</span> <span class="n">Network</span><span class="p">,</span>
    <span class="n">secp</span><span class="p">:</span> <span class="n">Secp256k1</span><span class="o">&lt;</span><span class="nn">secp256k1</span><span class="p">::</span><span class="n">All</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">pub</span> <span class="k">struct</span> <span class="n">UTXO</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="n">txid</span><span class="p">:</span> <span class="n">Txid</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">vout</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span>
    <span class="k">pub</span> <span class="n">amount</span><span class="p">:</span> <span class="n">Amount</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">impl</span> <span class="n">Wallet</span> <span class="p">{</span>
    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">wif</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">secp</span> <span class="o">=</span> <span class="nn">Secp256k1</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">private_key</span> <span class="o">=</span> <span class="nn">PrivateKey</span><span class="p">::</span><span class="nf">from_wif</span><span class="p">(</span><span class="n">wif</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">public_key</span> <span class="o">=</span> <span class="n">private_key</span><span class="nf">.public_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">network</span> <span class="o">=</span> <span class="n">private_key</span><span class="py">.network</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">address</span> <span class="o">=</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">p2wpkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">public_key</span><span class="p">,</span> <span class="n">network</span><span class="p">);</span>

        <span class="nf">Ok</span><span class="p">(</span><span class="k">Self</span> <span class="p">{</span>
            <span class="n">private_key</span><span class="p">,</span>
            <span class="n">public_key</span><span class="p">,</span>
            <span class="n">address</span><span class="p">,</span>
            <span class="n">network</span><span class="p">,</span>
            <span class="n">secp</span><span class="p">,</span>
        <span class="p">})</span>
    <span class="p">}</span>

    <span class="k">pub</span> <span class="k">fn</span> <span class="nf">create_and_sign_transaction</span><span class="p">(</span>
        <span class="o">&amp;</span><span class="k">self</span><span class="p">,</span>
        <span class="n">utxos</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">UTXO</span><span class="o">&gt;</span><span class="p">,</span>
        <span class="n">recipient</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Address</span><span class="p">,</span>
        <span class="n">amount</span><span class="p">:</span> <span class="n">Amount</span><span class="p">,</span>
        <span class="n">fee_rate</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span>
    <span class="p">)</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="n">Transaction</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// 計算總輸入金額</span>
        <span class="k">let</span> <span class="n">total_input</span><span class="p">:</span> <span class="n">Amount</span> <span class="o">=</span> <span class="n">utxos</span><span class="nf">.iter</span><span class="p">()</span><span class="nf">.map</span><span class="p">(|</span><span class="n">u</span><span class="p">|</span> <span class="n">u</span><span class="py">.amount</span><span class="p">)</span><span class="nf">.sum</span><span class="p">();</span>

        <span class="c1">// 估算費用</span>
        <span class="k">let</span> <span class="n">estimated_size</span> <span class="o">=</span> <span class="mi">10</span> <span class="o">+</span> <span class="mi">68</span> <span class="o">*</span> <span class="n">utxos</span><span class="nf">.len</span><span class="p">()</span> <span class="o">+</span> <span class="mi">31</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">fee</span> <span class="o">=</span> <span class="nn">Amount</span><span class="p">::</span><span class="nf">from_sat</span><span class="p">(</span><span class="n">estimated_size</span> <span class="k">as</span> <span class="nb">u64</span> <span class="o">*</span> <span class="n">fee_rate</span><span class="p">);</span>

        <span class="c1">// 計算找零</span>
        <span class="k">let</span> <span class="n">change</span> <span class="o">=</span> <span class="n">total_input</span><span class="nf">.checked_sub</span><span class="p">(</span><span class="n">amount</span> <span class="o">+</span> <span class="n">fee</span><span class="p">)</span>
            <span class="nf">.ok_or_else</span><span class="p">(||</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nd">anyhow!</span><span class="p">(</span><span class="s">"餘額不足"</span><span class="p">))</span><span class="o">?</span><span class="p">;</span>

        <span class="c1">// 構建輸入</span>
        <span class="k">let</span> <span class="n">inputs</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">TxIn</span><span class="o">&gt;</span> <span class="o">=</span> <span class="n">utxos</span><span class="nf">.iter</span><span class="p">()</span><span class="nf">.map</span><span class="p">(|</span><span class="n">utxo</span><span class="p">|</span> <span class="n">TxIn</span> <span class="p">{</span>
            <span class="n">previous_output</span><span class="p">:</span> <span class="n">OutPoint</span> <span class="p">{</span>
                <span class="n">txid</span><span class="p">:</span> <span class="n">utxo</span><span class="py">.txid</span><span class="p">,</span>
                <span class="n">vout</span><span class="p">:</span> <span class="n">utxo</span><span class="py">.vout</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="n">script_sig</span><span class="p">:</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new</span><span class="p">(),</span>
            <span class="n">sequence</span><span class="p">:</span> <span class="nn">Sequence</span><span class="p">::</span><span class="n">ENABLE_RBF_NO_LOCKTIME</span><span class="p">,</span>
            <span class="n">witness</span><span class="p">:</span> <span class="nn">Witness</span><span class="p">::</span><span class="nf">new</span><span class="p">(),</span>
        <span class="p">})</span><span class="nf">.collect</span><span class="p">();</span>

        <span class="c1">// 構建輸出</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">outputs</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">TxOut</span> <span class="p">{</span>
            <span class="n">value</span><span class="p">:</span> <span class="n">amount</span><span class="p">,</span>
            <span class="n">script_pubkey</span><span class="p">:</span> <span class="n">recipient</span><span class="nf">.script_pubkey</span><span class="p">(),</span>
        <span class="p">}];</span>

        <span class="c1">// 只有當找零超過粉塵限制時才添加找零輸出</span>
        <span class="k">if</span> <span class="n">change</span> <span class="o">&gt;</span> <span class="nn">Amount</span><span class="p">::</span><span class="nf">from_sat</span><span class="p">(</span><span class="mi">546</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">outputs</span><span class="nf">.push</span><span class="p">(</span><span class="n">TxOut</span> <span class="p">{</span>
                <span class="n">value</span><span class="p">:</span> <span class="n">change</span><span class="p">,</span>
                <span class="n">script_pubkey</span><span class="p">:</span> <span class="k">self</span><span class="py">.address</span><span class="nf">.script_pubkey</span><span class="p">(),</span>
            <span class="p">});</span>
        <span class="p">}</span>

        <span class="c1">// 創建交易</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">tx</span> <span class="o">=</span> <span class="n">Transaction</span> <span class="p">{</span>
            <span class="n">version</span><span class="p">:</span> <span class="nn">Version</span><span class="p">::</span><span class="n">TWO</span><span class="p">,</span>
            <span class="n">lock_time</span><span class="p">:</span> <span class="nn">LockTime</span><span class="p">::</span><span class="n">ZERO</span><span class="p">,</span>
            <span class="n">input</span><span class="p">:</span> <span class="n">inputs</span><span class="p">,</span>
            <span class="n">output</span><span class="p">:</span> <span class="n">outputs</span><span class="p">,</span>
        <span class="p">};</span>

        <span class="c1">// 簽名每個輸入</span>
        <span class="k">let</span> <span class="n">script_code</span> <span class="o">=</span> <span class="nn">ScriptBuf</span><span class="p">::</span><span class="nf">new_p2pkh</span><span class="p">(</span><span class="o">&amp;</span><span class="k">self</span><span class="py">.public_key</span><span class="nf">.pubkey_hash</span><span class="p">());</span>

        <span class="k">for</span> <span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">utxo</span><span class="p">)</span> <span class="k">in</span> <span class="n">utxos</span><span class="nf">.iter</span><span class="p">()</span><span class="nf">.enumerate</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">let</span> <span class="k">mut</span> <span class="n">sighash_cache</span> <span class="o">=</span> <span class="nn">SighashCache</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tx</span><span class="p">);</span>
            <span class="k">let</span> <span class="n">sighash</span> <span class="o">=</span> <span class="n">sighash_cache</span><span class="nf">.p2wpkh_signature_hash</span><span class="p">(</span>
                <span class="n">i</span><span class="p">,</span>
                <span class="o">&amp;</span><span class="n">script_code</span><span class="p">,</span>
                <span class="n">utxo</span><span class="py">.amount</span><span class="p">,</span>
                <span class="nn">EcdsaSighashType</span><span class="p">::</span><span class="n">All</span><span class="p">,</span>
            <span class="p">)</span><span class="o">?</span><span class="p">;</span>

            <span class="k">let</span> <span class="n">message</span> <span class="o">=</span> <span class="nn">secp256k1</span><span class="p">::</span><span class="nn">Message</span><span class="p">::</span><span class="nf">from_digest_slice</span><span class="p">(</span>
                <span class="n">sighash</span><span class="nf">.as_byte_array</span><span class="p">()</span>
            <span class="p">)</span><span class="o">?</span><span class="p">;</span>
            <span class="k">let</span> <span class="n">signature</span> <span class="o">=</span> <span class="k">self</span><span class="py">.secp</span><span class="nf">.sign_ecdsa</span><span class="p">(</span><span class="o">&amp;</span><span class="n">message</span><span class="p">,</span> <span class="o">&amp;</span><span class="k">self</span><span class="py">.private_key.inner</span><span class="p">);</span>

            <span class="k">let</span> <span class="k">mut</span> <span class="n">sig_bytes</span> <span class="o">=</span> <span class="n">signature</span><span class="nf">.serialize_der</span><span class="p">()</span><span class="nf">.to_vec</span><span class="p">();</span>
            <span class="n">sig_bytes</span><span class="nf">.push</span><span class="p">(</span><span class="nn">EcdsaSighashType</span><span class="p">::</span><span class="n">All</span><span class="nf">.to_u32</span><span class="p">()</span> <span class="k">as</span> <span class="nb">u8</span><span class="p">);</span>

            <span class="n">tx</span><span class="py">.input</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="py">.witness</span><span class="nf">.push</span><span class="p">(</span><span class="n">sig_bytes</span><span class="p">);</span>
            <span class="n">tx</span><span class="py">.input</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="py">.witness</span><span class="nf">.push</span><span class="p">(</span><span class="k">self</span><span class="py">.public_key</span><span class="nf">.to_bytes</span><span class="p">());</span>
        <span class="p">}</span>

        <span class="nf">Ok</span><span class="p">(</span><span class="n">tx</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>這個實現展示了 Bitcoin 交易的核心概念：UTXO 選擇、費用計算、輸出創建和簽名生成。在生產環境中，你還需要添加錯誤處理、UTXO 查詢（通過節點 RPC 或區塊瀏覽器 API）、以及交易廣播功能。</p>

<hr />

<h2 id="7-練習題">7. 練習題</h2>

<h3 id="練習-1實現地址間隔掃描">練習 1：實現地址間隔掃描</h3>

<p>創建一個函數，使用「地址間隔」策略生成 HD 錢包地址。這是錢包恢復的標準方法：連續檢查地址直到遇到一定數量的未使用地址。</p>

<h3 id="練習-2交易解析器">練習 2：交易解析器</h3>

<p>實現一個函數，解析原始交易的十六進制表示，提取並顯示所有欄位：版本、輸入、輸出、見證數據和鎖定時間。</p>

<h3 id="練習-3utxo-合併">練習 3：UTXO 合併</h3>

<p>創建一個函數，將多個小額 UTXO 合併成一個大額 UTXO。這在費率低的時候做可以節省未來交易的費用。考慮何時合併是經濟的（費用節省超過合併成本）。</p>

<hr />

<h2 id="8-總結">8. 總結</h2>

<p>本篇深入探討了 Bitcoin 地址和交易的技術細節。我們了解了 HD 錢包如何讓單一種子衍生出無限地址，不同地址類型的特性和用途，UTXO 模型如何運作，以及如何構建和簽名真實的交易。</p>

<p>關鍵要點：</p>
<ul>
  <li>HD 錢包（BIP32/39/44/84）是現代錢包的標準</li>
  <li>不同地址類型有不同的費用和功能特性</li>
  <li>UTXO 選擇策略影響交易費用和隱私</li>
  <li>SegWit 和 Taproot 提供更低的費用和更好的隱私</li>
  <li>PSBT 是多方簽名工作流程的標準格式</li>
</ul>

<p>下一篇將深入探討 Bitcoin Script 編程和各種簽名機制。</p>

<hr />

<h2 id="參考資源">參考資源</h2>

<h3 id="bip-文檔">BIP 文檔</h3>
<ul>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki">BIP 32: HD Wallets</a></li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki">BIP 39: Mnemonic Code</a></li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki">BIP 44: Multi-Account Hierarchy</a></li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki">BIP 84: Native SegWit</a></li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki">BIP 174: PSBT</a></li>
</ul>

<h3 id="工具">工具</h3>
<ul>
  <li><a href="https://iancoleman.io/bip39/">Ian Coleman BIP39 Tool</a></li>
  <li><a href="https://coinb.in/">Bitcoin Transaction Builder</a></li>
</ul>]]></content><author><name>Cypherpunks Taiwan</name></author><category term="技術教學" /><category term="Bitcoin" /><category term="Rust" /><category term="Rust" /><category term="Bitcoin" /><category term="地址" /><category term="交易" /><category term="HD錢包" /><category term="BIP32" /><category term="BIP39" /><category term="BIP44" /><summary type="html"><![CDATA[這是 Rust Bitcoin 開發入門系列的第二篇。本篇深入探討 Bitcoin 地址的生成機制和交易的構建過程。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" /><media:content medium="image" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Rust Bitcoin 開發入門（一）：環境設置與基礎概念</title><link href="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/rust/2025/03/22/rust-bitcoin-tutorial-1-environment-basics/" rel="alternate" type="text/html" title="Rust Bitcoin 開發入門（一）：環境設置與基礎概念" /><published>2025-03-22T00:00:00+00:00</published><updated>2025-03-22T00:00:00+00:00</updated><id>https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/rust/2025/03/22/rust-bitcoin-tutorial-1-environment-basics</id><content type="html" xml:base="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/rust/2025/03/22/rust-bitcoin-tutorial-1-environment-basics/"><![CDATA[<p>這是 Rust Bitcoin 開發入門系列的第一篇。本系列將帶你從零開始使用 Rust 語言開發 Bitcoin 應用，從基礎設置到進階應用。</p>

<p><strong>系列文章導航：</strong></p>
<ul>
  <li><strong>第一篇：環境設置與基礎概念</strong>（本篇）</li>
  <li><a href="/2025/03/23/rust-bitcoin-tutorial-2-addresses-transactions/">第二篇：地址生成與交易構建</a></li>
  <li><a href="/2025/03/24/rust-bitcoin-tutorial-3-scripts-signatures/">第三篇：腳本與簽名</a></li>
  <li><a href="/2025/03/25/rust-bitcoin-tutorial-4-advanced-applications/">第四篇：進階應用與整合</a></li>
</ul>

<hr />

<h2 id="1-為什麼選擇-rust">1. 為什麼選擇 Rust？</h2>

<h3 id="11-rust-在-bitcoin-開發中的優勢">1.1 Rust 在 Bitcoin 開發中的優勢</h3>

<p>當我們思考 Bitcoin 開發的語言選擇時，必須考慮這個領域的特殊需求。Bitcoin 是一個處理真實價值的系統，任何程式錯誤都可能導致不可逆的財務損失。與普通的網頁應用不同，Bitcoin 程式碼沒有「修復後重新部署」的奢侈——一旦交易廣播到網路並被確認，就永遠無法撤銷。</p>

<p>Rust 語言的設計理念與 Bitcoin 開發的需求高度契合。首先是記憶體安全。傳統的 C/C++ 程式經常遭受緩衝區溢位（buffer overflow）、使用後釋放（use-after-free）等漏洞的困擾。這些漏洞在金融軟體中可能是災難性的。Rust 的所有權系統在編譯時就能捕捉這些問題，而不是等到程式在生產環境中崩潰。</p>

<p>其次是執行緒安全。現代 Bitcoin 應用常常需要同時處理多個操作——監聽新區塊、處理用戶請求、簽署交易等。Rust 的借用檢查器能在編譯時防止資料競爭（data races），這意味著多執行緒程式的正確性有編譯器的保證。</p>

<p>效能方面，Rust 提供了「零成本抽象」。你可以使用高階的程式設計概念（泛型、迭代器、閉包等），而編譯器會將其優化成與手寫底層程式碼相當的機器碼。這對於需要處理大量密碼學運算的 Bitcoin 應用非常重要。</p>

<p>最後，Rust 沒有垃圾收集器（GC）。這意味著程式的執行時機是可預測的，不會突然暫停來回收記憶體。對於需要快速回應的交易簽署或區塊處理來說，這種可預測性是必要的。</p>

<h3 id="12-rust-bitcoin-生態系統">1.2 Rust Bitcoin 生態系統</h3>

<p>Rust 的 Bitcoin 生態系統已經相當成熟，形成了層次分明的函式庫架構。</p>

<p>在最底層，我們有 <code class="language-plaintext highlighter-rouge">bitcoin_hashes</code> 和 <code class="language-plaintext highlighter-rouge">secp256k1</code>。<code class="language-plaintext highlighter-rouge">bitcoin_hashes</code> 提供了 Bitcoin 使用的所有哈希函數——SHA256、RIPEMD160、HASH160 等。<code class="language-plaintext highlighter-rouge">secp256k1</code> 則是 Bitcoin 使用的橢圓曲線密碼學函式庫的 Rust 綁定，用於私鑰/公鑰運算和數位簽名。</p>

<p>在其上是 <code class="language-plaintext highlighter-rouge">rust-bitcoin</code>，這是核心的協議函式庫。它定義了 Bitcoin 的所有資料結構——交易、區塊、地址、腳本等——以及它們的序列化和解析邏輯。幾乎所有 Rust Bitcoin 專案都會依賴這個函式庫。</p>

<p>更高層次的函式庫建立在 <code class="language-plaintext highlighter-rouge">rust-bitcoin</code> 之上。BDK（Bitcoin Development Kit）是一個完整的錢包開發框架，處理了 UTXO 管理、交易構建、硬體錢包整合等複雜問題。LDK（Lightning Development Kit）則提供了 Lightning Network 的實現，讓開發者可以將閃電網路功能整合到自己的應用中。</p>

<p>這種分層架構的好處是，你可以根據需求選擇適當的抽象層次。需要完全控制？直接使用 <code class="language-plaintext highlighter-rouge">rust-bitcoin</code>。想要快速開發錢包？使用 BDK。</p>

<hr />

<h2 id="2-環境設置">2. 環境設置</h2>

<h3 id="21-安裝-rust">2.1 安裝 Rust</h3>

<p>Rust 的安裝透過官方工具 <code class="language-plaintext highlighter-rouge">rustup</code> 進行，這是一個版本管理器，可以輕鬆切換 Rust 版本和安裝額外組件。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="c"># 安裝 Rustup（Rust 版本管理器）</span>
curl <span class="nt">--proto</span> <span class="s1">'=https'</span> <span class="nt">--tlsv1</span>.2 <span class="nt">-sSf</span> https://sh.rustup.rs | sh

<span class="c"># 按照提示完成安裝後，重新載入環境變數</span>
<span class="nb">source</span> <span class="nv">$HOME</span>/.cargo/env

<span class="c"># 驗證安裝</span>
rustc <span class="nt">--version</span>
cargo <span class="nt">--version</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>安裝完成後，建議安裝兩個實用工具：<code class="language-plaintext highlighter-rouge">clippy</code> 是一個 lint 工具，能指出程式碼中的常見問題和改進建議；<code class="language-plaintext highlighter-rouge">rustfmt</code> 則自動格式化程式碼，保持風格一致。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>rustup component add clippy
rustup component add rustfmt
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="22-創建專案">2.2 創建專案</h3>

<p>Rust 使用 Cargo 作為建構工具和套件管理器。創建新專案非常簡單：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>cargo new bitcoin-tutorial
<span class="nb">cd </span>bitcoin-tutorial
</pre></td></tr></tbody></table></code></pre></div></div>

<p>這會創建一個標準的 Rust 專案結構。<code class="language-plaintext highlighter-rouge">Cargo.toml</code> 是專案的設定檔，定義了專案名稱、版本和依賴關係。<code class="language-plaintext highlighter-rouge">src/main.rs</code> 是主程式檔案。</p>

<h3 id="23-配置依賴">2.3 配置依賴</h3>

<p>在 <code class="language-plaintext highlighter-rouge">Cargo.toml</code> 中添加 Bitcoin 開發所需的依賴。每個依賴都有其特定用途：</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="rouge-code"><pre><span class="k">[</span><span class="n">package</span><span class="k">]</span>
<span class="n">name</span> <span class="o">=</span><span class="w"> </span><span class="s">"bitcoin-tutorial"</span>
<span class="n">version</span> <span class="o">=</span><span class="w"> </span><span class="s">"0.1.0"</span>
<span class="n">edition</span> <span class="o">=</span><span class="w"> </span><span class="s">"2021"</span>

<span class="k">[</span><span class="n">dependencies</span><span class="k">]</span>
<span class="c"># Bitcoin 核心庫 - 提供所有 Bitcoin 資料結構和協議邏輯</span>
<span class="n">bitcoin</span> <span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">version</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"0.32"</span><span class="p">,</span><span class="w"> </span><span class="n">features</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="s">"serde"</span><span class="p">,</span> <span class="s">"rand-std"</span><span class="p">]</span><span class="w"> </span><span class="p">}</span>

<span class="c"># 橢圓曲線密碼學 - 用於簽名和金鑰操作</span>
<span class="n">secp256k1</span> <span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">version</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"0.29"</span><span class="p">,</span><span class="w"> </span><span class="n">features</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="s">"rand-std"</span><span class="p">,</span> <span class="s">"global-context"</span><span class="p">]</span><span class="w"> </span><span class="p">}</span>

<span class="c"># 序列化框架 - 用於 JSON 處理</span>
<span class="n">serde</span> <span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">version</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">"1.0"</span><span class="p">,</span><span class="w"> </span><span class="n">features</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="s">"derive"</span><span class="p">]</span><span class="w"> </span><span class="p">}</span>
<span class="n">serde_json</span> <span class="o">=</span><span class="w"> </span><span class="s">"1.0"</span>

<span class="c"># 十六進制編碼 - Bitcoin 資料常用十六進制表示</span>
<span class="n">hex</span> <span class="o">=</span><span class="w"> </span><span class="s">"0.4"</span>

<span class="c"># 錯誤處理 - 簡化錯誤傳播</span>
<span class="n">anyhow</span> <span class="o">=</span><span class="w"> </span><span class="s">"1.0"</span>
<span class="n">thiserror</span> <span class="o">=</span><span class="w"> </span><span class="s">"1.0"</span>

<span class="c"># BIP39 助記詞 - 人類可讀的種子格式</span>
<span class="n">bip39</span> <span class="o">=</span><span class="w"> </span><span class="s">"2.0"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">bitcoin</code> 函式庫的 <code class="language-plaintext highlighter-rouge">serde</code> feature 啟用了序列化支援，<code class="language-plaintext highlighter-rouge">rand-std</code> 則允許使用標準函式庫的隨機數生成器。<code class="language-plaintext highlighter-rouge">secp256k1</code> 的 <code class="language-plaintext highlighter-rouge">global-context</code> feature 提供了一個全域的上下文物件，避免在每次運算時都創建新的上下文。</p>

<hr />

<h2 id="3-rust-bitcoin-基礎">3. rust-bitcoin 基礎</h2>

<h3 id="31-理解金額amount">3.1 理解金額（Amount）</h3>

<p>在 Bitcoin 內部，所有金額都以「聰」（satoshi）計算。一個 Bitcoin 等於一億聰（100,000,000 satoshi）。這種設計避免了浮點數運算的精度問題——在處理金錢時，這種精度至關重要。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="n">Amount</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">amount_examples</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 從聰創建金額</span>
    <span class="k">let</span> <span class="n">amount_sat</span> <span class="o">=</span> <span class="nn">Amount</span><span class="p">::</span><span class="nf">from_sat</span><span class="p">(</span><span class="mi">100_000_000</span><span class="p">);</span>  <span class="c1">// 1 BTC</span>

    <span class="c1">// 從 BTC 創建金額（注意：可能失敗，因此返回 Result）</span>
    <span class="k">let</span> <span class="n">amount_btc</span> <span class="o">=</span> <span class="nn">Amount</span><span class="p">::</span><span class="nf">from_btc</span><span class="p">(</span><span class="mf">1.0</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// 兩者相等</span>
    <span class="nd">assert_eq!</span><span class="p">(</span><span class="n">amount_sat</span><span class="p">,</span> <span class="n">amount_btc</span><span class="p">);</span>

    <span class="c1">// 金額運算</span>
    <span class="k">let</span> <span class="n">fee</span> <span class="o">=</span> <span class="nn">Amount</span><span class="p">::</span><span class="nf">from_sat</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">total</span> <span class="o">=</span> <span class="n">amount_btc</span> <span class="o">+</span> <span class="n">fee</span><span class="p">;</span>

    <span class="c1">// 使用 checked_sub 避免溢位（金額不能為負）</span>
    <span class="k">let</span> <span class="n">remaining</span> <span class="o">=</span> <span class="n">amount_btc</span><span class="nf">.checked_sub</span><span class="p">(</span><span class="n">fee</span><span class="p">)</span>
        <span class="nf">.expect</span><span class="p">(</span><span class="s">"金額不足"</span><span class="p">);</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Amount</code> 類型防止了一個常見錯誤：混淆 BTC 和 satoshi 單位。編譯器會確保你在運算時使用相同的單位。</p>

<h3 id="32-網路類型network">3.2 網路類型（Network）</h3>

<p>Bitcoin 有多個網路，每個網路使用不同的地址前綴和協議參數：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="n">Network</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">network_examples</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">mainnet</span> <span class="o">=</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span><span class="p">;</span>   <span class="c1">// 正式網路，使用真實的 BTC</span>
    <span class="k">let</span> <span class="n">testnet</span> <span class="o">=</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Testnet</span><span class="p">;</span>   <span class="c1">// 測試網路，使用無價值的測試幣</span>
    <span class="k">let</span> <span class="n">signet</span> <span class="o">=</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Signet</span><span class="p">;</span>     <span class="c1">// 簽名測試網路，更穩定的測試環境</span>
    <span class="k">let</span> <span class="n">regtest</span> <span class="o">=</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Regtest</span><span class="p">;</span>   <span class="c1">// 本地回歸測試網路</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>在開發過程中，應該始終使用測試網路（testnet 或 signet），直到程式碼經過充分測試。在測試網路上犯錯不會有任何財務損失，但在主網上的錯誤可能是災難性的。</p>

<h3 id="33-哈希函數">3.3 哈希函數</h3>

<p>Bitcoin 大量使用哈希函數，每種函數都有特定用途：</p>

<p><strong>SHA256</strong> 是基礎的哈希函數，輸出 32 bytes。</p>

<p><strong>Double SHA256（SHA256d）</strong> 是對資料進行兩次 SHA256。Bitcoin 在許多地方使用這種雙重哈希，包括交易 ID、區塊雜湊等。這可能是為了防止某些理論上的攻擊（長度擴展攻擊），雖然對 SHA256 來說這可能是過度謹慎。</p>

<p><strong>RIPEMD160</strong> 是一個輸出 20 bytes 的哈希函數。</p>

<p><strong>HASH160</strong> 是 SHA256 後接 RIPEMD160，用於生成公鑰的雜湊值，進而生成地址。使用這個組合而不是單純的 SHA256 是為了得到更短的輸出（20 bytes vs 32 bytes），減少地址長度。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">hashes</span><span class="p">::{</span><span class="n">sha256</span><span class="p">,</span> <span class="n">sha256d</span><span class="p">,</span> <span class="n">ripemd160</span><span class="p">,</span> <span class="n">hash160</span><span class="p">,</span> <span class="n">Hash</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">hash_examples</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">data</span> <span class="o">=</span> <span class="s">b"Hello, Bitcoin!"</span><span class="p">;</span>

    <span class="c1">// SHA256</span>
    <span class="k">let</span> <span class="n">sha256_hash</span> <span class="o">=</span> <span class="nn">sha256</span><span class="p">::</span><span class="nn">Hash</span><span class="p">::</span><span class="nf">hash</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>

    <span class="c1">// Double SHA256（Bitcoin 標準）</span>
    <span class="k">let</span> <span class="n">sha256d_hash</span> <span class="o">=</span> <span class="nn">sha256d</span><span class="p">::</span><span class="nn">Hash</span><span class="p">::</span><span class="nf">hash</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>

    <span class="c1">// HASH160（用於地址生成）</span>
    <span class="k">let</span> <span class="n">hash160_result</span> <span class="o">=</span> <span class="nn">hash160</span><span class="p">::</span><span class="nn">Hash</span><span class="p">::</span><span class="nf">hash</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="34-secp256k1-橢圓曲線">3.4 secp256k1 橢圓曲線</h3>

<p>Bitcoin 使用 secp256k1 橢圓曲線進行公鑰密碼學。理解這個曲線的基本概念對於 Bitcoin 開發很重要。</p>

<p>私鑰本質上是一個 256 位的隨機數。這個數字必須在 1 到曲線的階（一個非常大的質數）之間。任何在這個範圍內的數字都是有效的私鑰。</p>

<p>公鑰是橢圓曲線上的一個點，由私鑰乘以曲線的生成點 G 得到：<code class="language-plaintext highlighter-rouge">P = k * G</code>，其中 k 是私鑰，P 是公鑰。這個運算是單向的——從公鑰無法推導出私鑰（這就是橢圓曲線離散對數問題）。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">secp256k1</span><span class="p">::{</span><span class="n">Secp256k1</span><span class="p">,</span> <span class="n">SecretKey</span><span class="p">,</span> <span class="n">PublicKey</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">key_generation</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 創建 secp256k1 上下文</span>
    <span class="k">let</span> <span class="n">secp</span> <span class="o">=</span> <span class="nn">Secp256k1</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

    <span class="c1">// 生成隨機私鑰</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">rng</span> <span class="o">=</span> <span class="nn">rand</span><span class="p">::</span><span class="nf">thread_rng</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">secret_key</span> <span class="o">=</span> <span class="nn">SecretKey</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">rng</span><span class="p">);</span>

    <span class="c1">// 從私鑰導出公鑰</span>
    <span class="k">let</span> <span class="n">public_key</span> <span class="o">=</span> <span class="nn">PublicKey</span><span class="p">::</span><span class="nf">from_secret_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">secret_key</span><span class="p">);</span>

    <span class="c1">// 公鑰有兩種序列化格式</span>
    <span class="k">let</span> <span class="n">compressed</span> <span class="o">=</span> <span class="n">public_key</span><span class="nf">.serialize</span><span class="p">();</span>      <span class="c1">// 33 bytes</span>
    <span class="k">let</span> <span class="n">uncompressed</span> <span class="o">=</span> <span class="n">public_key</span><span class="nf">.serialize_uncompressed</span><span class="p">();</span>  <span class="c1">// 65 bytes</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>壓縮公鑰只包含 x 座標和一個表示 y 座標正負的位元（因為給定 x，y 只有兩個可能值）。未壓縮公鑰包含完整的 x 和 y 座標。現代 Bitcoin 幾乎總是使用壓縮公鑰，因為它更短且同樣安全。</p>

<hr />

<h2 id="4-編碼與序列化">4. 編碼與序列化</h2>

<h3 id="41-十六進制編碼">4.1 十六進制編碼</h3>

<p>Bitcoin 資料在顯示時幾乎總是使用十六進制（hex）格式。這是因為二進位資料不方便閱讀或複製，而十六進制提供了一個緊湊的文字表示。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">hex_examples</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">data</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span><span class="mi">0xde</span><span class="p">,</span> <span class="mi">0xad</span><span class="p">,</span> <span class="mi">0xbe</span><span class="p">,</span> <span class="mi">0xef</span><span class="p">];</span>

    <span class="c1">// 編碼為十六進制字串</span>
    <span class="k">let</span> <span class="n">hex_string</span> <span class="o">=</span> <span class="nn">hex</span><span class="p">::</span><span class="nf">encode</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="p">);</span>  <span class="c1">// "deadbeef"</span>

    <span class="c1">// 從十六進制解碼</span>
    <span class="k">let</span> <span class="n">decoded</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nn">hex</span><span class="p">::</span><span class="nf">decode</span><span class="p">(</span><span class="s">"deadbeef"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="42-base58check-編碼">4.2 Base58Check 編碼</h3>

<p>傳統的 Bitcoin 地址使用 Base58Check 編碼。Base58 是 Base64 的變體，移除了容易混淆的字元（0、O、I、l），避免人工抄寫時的錯誤。</p>

<p>Base58Check 在 Base58 的基礎上增加了版本前綴和校驗和。版本前綴指示地址類型（P2PKH、P2SH 等）和網路（mainnet、testnet）。校驗和是資料的 double SHA256 的前 4 bytes，用於檢測輸入錯誤。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">hashes</span><span class="p">::{</span><span class="n">sha256d</span><span class="p">,</span> <span class="n">Hash</span><span class="p">};</span>
<span class="k">use</span> <span class="n">bs58</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">base58check_encode</span><span class="p">(</span><span class="n">version</span><span class="p">:</span> <span class="nb">u8</span><span class="p">,</span> <span class="n">payload</span><span class="p">:</span> <span class="o">&amp;</span><span class="p">[</span><span class="nb">u8</span><span class="p">])</span> <span class="k">-&gt;</span> <span class="nb">String</span> <span class="p">{</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">data</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">version</span><span class="p">];</span>
    <span class="n">data</span><span class="nf">.extend_from_slice</span><span class="p">(</span><span class="n">payload</span><span class="p">);</span>

    <span class="c1">// 計算校驗和</span>
    <span class="k">let</span> <span class="n">checksum</span> <span class="o">=</span> <span class="nn">sha256d</span><span class="p">::</span><span class="nn">Hash</span><span class="p">::</span><span class="nf">hash</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="p">);</span>
    <span class="n">data</span><span class="nf">.extend_from_slice</span><span class="p">(</span><span class="o">&amp;</span><span class="n">checksum</span><span class="p">[</span><span class="o">..</span><span class="mi">4</span><span class="p">]);</span>

    <span class="nn">bs58</span><span class="p">::</span><span class="nf">encode</span><span class="p">(</span><span class="n">data</span><span class="p">)</span><span class="nf">.into_string</span><span class="p">()</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>版本位元組決定了地址的前綴：mainnet P2PKH 地址以 ‘1’ 開頭（版本 0x00），P2SH 地址以 ‘3’ 開頭（版本 0x05）。</p>

<h3 id="43-bech32-編碼">4.3 Bech32 編碼</h3>

<p>SegWit 引入了新的地址格式 Bech32（BIP 173），Taproot 則使用改進版的 Bech32m（BIP 350）。這些格式有幾個優點：</p>

<p>首先，它們只使用小寫字母和數字，避免了大小寫混淆。其次，錯誤檢測能力更強，可以檢測到更多類型的輸入錯誤。第三，它們自帶人類可讀的前綴（HRP），使網路識別更容易——mainnet 地址以 “bc1” 開頭，testnet 以 “tb1” 開頭。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="n">Address</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">bech32_address_info</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// bc1q... 是 SegWit v0 地址（P2WPKH 或 P2WSH）</span>
    <span class="c1">// bc1p... 是 SegWit v1 地址（P2TR，Taproot）</span>

    <span class="c1">// Bech32 用於 v0，Bech32m 用於 v1+</span>
    <span class="c1">// 這個改變修復了 Bech32 的一個極端情況問題</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="44-bitcoin-共識編碼">4.4 Bitcoin 共識編碼</h3>

<p>Bitcoin 協議使用特定的二進位編碼格式，稱為「共識編碼」。理解這個格式對於解析和創建原始交易很重要。</p>

<p>Bitcoin 使用小端序（Little Endian）來編碼多位元組整數。這與網路協議常用的大端序不同。</p>

<p>變長整數（VarInt）是一種空間優化的編碼方式。小於 253 的數字只用 1 byte，較大的數字則根據大小使用 3、5 或 9 bytes。這種編碼在交易中大量使用（例如表示輸入/輸出數量）。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">encode_varint</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nb">u64</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">0xFD</span> <span class="p">{</span>
        <span class="nd">vec!</span><span class="p">[</span><span class="n">n</span> <span class="k">as</span> <span class="nb">u8</span><span class="p">]</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">n</span> <span class="o">&lt;=</span> <span class="mi">0xFFFF</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">v</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span><span class="mi">0xFD</span><span class="p">];</span>
        <span class="n">v</span><span class="nf">.extend</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="n">n</span> <span class="k">as</span> <span class="nb">u16</span><span class="p">)</span><span class="nf">.to_le_bytes</span><span class="p">());</span>
        <span class="n">v</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">n</span> <span class="o">&lt;=</span> <span class="mi">0xFFFFFFFF</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">v</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span><span class="mi">0xFE</span><span class="p">];</span>
        <span class="n">v</span><span class="nf">.extend</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="n">n</span> <span class="k">as</span> <span class="nb">u32</span><span class="p">)</span><span class="nf">.to_le_bytes</span><span class="p">());</span>
        <span class="n">v</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">let</span> <span class="k">mut</span> <span class="n">v</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span><span class="mi">0xFF</span><span class="p">];</span>
        <span class="n">v</span><span class="nf">.extend</span><span class="p">(</span><span class="o">&amp;</span><span class="n">n</span><span class="nf">.to_le_bytes</span><span class="p">());</span>
        <span class="n">v</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="5-錯誤處理">5. 錯誤處理</h2>

<h3 id="51-rust-的錯誤處理哲學">5.1 Rust 的錯誤處理哲學</h3>

<p>Rust 的錯誤處理與許多語言不同。它不使用例外（exceptions），而是透過類型系統明確表示可能失敗的操作。這迫使程式設計師在編譯時就考慮錯誤情況。</p>

<p><code class="language-plaintext highlighter-rouge">Result&lt;T, E&gt;</code> 類型表示一個可能成功（返回 <code class="language-plaintext highlighter-rouge">T</code>）或失敗（返回 <code class="language-plaintext highlighter-rouge">E</code>）的操作。<code class="language-plaintext highlighter-rouge">Option&lt;T&gt;</code> 則表示一個可能存在（<code class="language-plaintext highlighter-rouge">Some(T)</code>）或不存在（<code class="language-plaintext highlighter-rouge">None</code>）的值。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">anyhow</span><span class="p">::{</span><span class="n">Context</span><span class="p">,</span> <span class="nb">Result</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">thiserror</span><span class="p">::</span><span class="n">Error</span><span class="p">;</span>

<span class="c1">// 使用 thiserror 定義自訂錯誤類型</span>
<span class="nd">#[derive(Error,</span> <span class="nd">Debug)]</span>
<span class="k">pub</span> <span class="k">enum</span> <span class="n">BitcoinError</span> <span class="p">{</span>
    <span class="nd">#[error(</span><span class="s">"無效的私鑰: {0}"</span><span class="nd">)]</span>
    <span class="nf">InvalidPrivateKey</span><span class="p">(</span><span class="nb">String</span><span class="p">),</span>

    <span class="nd">#[error(</span><span class="s">"金額不足: 需要 {required} sat, 只有 {available} sat"</span><span class="nd">)]</span>
    <span class="n">InsufficientFunds</span> <span class="p">{</span> <span class="n">required</span><span class="p">:</span> <span class="nb">u64</span><span class="p">,</span> <span class="n">available</span><span class="p">:</span> <span class="nb">u64</span> <span class="p">},</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">thiserror</code> 庫幫助你定義結構化的錯誤類型，適合函式庫使用。<code class="language-plaintext highlighter-rouge">anyhow</code> 則提供了一個通用的錯誤類型，適合應用程式——它可以包裝任何錯誤並附加上下文資訊。</p>

<h3 id="52-實用的錯誤處理模式">5.2 實用的錯誤處理模式</h3>

<p>在 Bitcoin 開發中，錯誤處理特別重要。一個常見的模式是使用 <code class="language-plaintext highlighter-rouge">?</code> 運算符傳播錯誤，並用 <code class="language-plaintext highlighter-rouge">context()</code> 添加有意義的錯誤訊息：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">process_transaction</span><span class="p">(</span><span class="n">tx_hex</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">tx_bytes</span> <span class="o">=</span> <span class="nn">hex</span><span class="p">::</span><span class="nf">decode</span><span class="p">(</span><span class="n">tx_hex</span><span class="p">)</span>
        <span class="nf">.context</span><span class="p">(</span><span class="s">"無法解析交易十六進制"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="k">let</span> <span class="n">tx</span><span class="p">:</span> <span class="n">Transaction</span> <span class="o">=</span> <span class="nn">consensus</span><span class="p">::</span><span class="nf">deserialize</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tx_bytes</span><span class="p">)</span>
        <span class="nf">.context</span><span class="p">(</span><span class="s">"無效的交易格式"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 處理交易...</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>當這個函數失敗時，錯誤訊息會包含完整的上下文鏈，幫助診斷問題：「無法解析交易十六進制: invalid character ‘g’ at position 10」。</p>

<hr />

<h2 id="6-第一個程式生成地址">6. 第一個程式：生成地址</h2>

<h3 id="61-從隨機數生成地址">6.1 從隨機數生成地址</h3>

<p>現在讓我們把所有概念結合起來，寫一個完整的地址生成程式。這個程式展示了從隨機私鑰到 Bitcoin 地址的完整流程：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::{</span>
    <span class="n">Network</span><span class="p">,</span> <span class="n">Address</span><span class="p">,</span> <span class="n">PublicKey</span><span class="p">,</span> <span class="n">PrivateKey</span><span class="p">,</span>
    <span class="nn">secp256k1</span><span class="p">::</span><span class="n">Secp256k1</span><span class="p">,</span>
<span class="p">};</span>
<span class="k">use</span> <span class="nn">rand</span><span class="p">::</span><span class="n">thread_rng</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">generate_random_address</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">secp</span> <span class="o">=</span> <span class="nn">Secp256k1</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">rng</span> <span class="o">=</span> <span class="nf">thread_rng</span><span class="p">();</span>

    <span class="c1">// 生成隨機私鑰</span>
    <span class="k">let</span> <span class="n">secret_key</span> <span class="o">=</span> <span class="nn">secp256k1</span><span class="p">::</span><span class="nn">SecretKey</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">rng</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">private_key</span> <span class="o">=</span> <span class="nn">PrivateKey</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">secret_key</span><span class="p">,</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span><span class="p">);</span>

    <span class="c1">// 導出公鑰</span>
    <span class="k">let</span> <span class="n">public_key</span> <span class="o">=</span> <span class="n">private_key</span><span class="nf">.public_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">);</span>

    <span class="c1">// 生成不同類型的地址</span>
    <span class="k">let</span> <span class="n">p2pkh</span> <span class="o">=</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">p2pkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">public_key</span><span class="p">,</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">p2wpkh</span> <span class="o">=</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">p2wpkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">public_key</span><span class="p">,</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">p2shwpkh</span> <span class="o">=</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">p2shwpkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">public_key</span><span class="p">,</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span><span class="p">);</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"私鑰 (WIF): {}"</span><span class="p">,</span> <span class="n">private_key</span><span class="nf">.to_wif</span><span class="p">());</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"P2PKH 地址:    {}"</span><span class="p">,</span> <span class="n">p2pkh</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"P2WPKH 地址:   {}"</span><span class="p">,</span> <span class="n">p2wpkh</span><span class="p">);</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"P2SH-P2WPKH:   {}"</span><span class="p">,</span> <span class="n">p2shwpkh</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>這裡展示了三種地址類型：</p>

<p><strong>P2PKH（Pay-to-Public-Key-Hash）</strong>是最傳統的地址類型，以 ‘1’ 開頭。它直接鎖定到公鑰的雜湊值。</p>

<p><strong>P2WPKH（Pay-to-Witness-Public-Key-Hash）</strong>是原生 SegWit 地址，以 ‘bc1q’ 開頭。它提供較低的交易費用和更好的安全性。</p>

<p><strong>P2SH-P2WPKH</strong> 是包裝在 P2SH 中的 SegWit，以 ‘3’ 開頭。它提供了與舊錢包的兼容性。</p>

<h3 id="62-從助記詞生成地址">6.2 從助記詞生成地址</h3>

<p>在實際應用中，我們通常不會直接生成隨機私鑰，而是使用助記詞（BIP39）和分層確定性派生（BIP32）。這種方法有幾個優點：用戶只需備份一組助記詞就能恢復所有地址；從同一個種子可以派生出無限多個地址；不同的派生路徑可以用於不同目的。</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">bip39</span><span class="p">::{</span><span class="n">Mnemonic</span><span class="p">,</span> <span class="n">Language</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">bitcoin</span><span class="p">::</span><span class="nn">bip32</span><span class="p">::{</span><span class="n">Xpriv</span><span class="p">,</span> <span class="n">Xpub</span><span class="p">,</span> <span class="n">DerivationPath</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">str</span><span class="p">::</span><span class="n">FromStr</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">generate_from_mnemonic</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nn">anyhow</span><span class="p">::</span><span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">secp</span> <span class="o">=</span> <span class="nn">Secp256k1</span><span class="p">::</span><span class="nf">new</span><span class="p">();</span>

    <span class="c1">// 生成 12 字助記詞</span>
    <span class="k">let</span> <span class="n">mnemonic</span> <span class="o">=</span> <span class="nn">Mnemonic</span><span class="p">::</span><span class="nf">generate_in</span><span class="p">(</span><span class="nn">Language</span><span class="p">::</span><span class="n">English</span><span class="p">,</span> <span class="mi">12</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"助記詞: {}"</span><span class="p">,</span> <span class="n">mnemonic</span><span class="p">);</span>

    <span class="c1">// 從助記詞生成種子（可選的密碼短語）</span>
    <span class="k">let</span> <span class="n">seed</span> <span class="o">=</span> <span class="n">mnemonic</span><span class="nf">.to_seed</span><span class="p">(</span><span class="s">""</span><span class="p">);</span>

    <span class="c1">// 生成主私鑰</span>
    <span class="k">let</span> <span class="n">master_xpriv</span> <span class="o">=</span> <span class="nn">Xpriv</span><span class="p">::</span><span class="nf">new_master</span><span class="p">(</span><span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">seed</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>

    <span class="c1">// 使用 BIP84 路徑派生原生 SegWit 地址</span>
    <span class="c1">// m/84'/0'/0'/0/0</span>
    <span class="k">let</span> <span class="n">path</span> <span class="o">=</span> <span class="nn">DerivationPath</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="s">"m/84'/0'/0'/0/0"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">derived_xpriv</span> <span class="o">=</span> <span class="n">master_xpriv</span><span class="nf">.derive_priv</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">path</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">derived_xpub</span> <span class="o">=</span> <span class="nn">Xpub</span><span class="p">::</span><span class="nf">from_priv</span><span class="p">(</span><span class="o">&amp;</span><span class="n">secp</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">derived_xpriv</span><span class="p">);</span>

    <span class="k">let</span> <span class="n">public_key</span> <span class="o">=</span> <span class="nn">PublicKey</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">derived_xpub</span><span class="py">.public_key</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">address</span> <span class="o">=</span> <span class="nn">Address</span><span class="p">::</span><span class="nf">p2wpkh</span><span class="p">(</span><span class="o">&amp;</span><span class="n">public_key</span><span class="p">,</span> <span class="nn">Network</span><span class="p">::</span><span class="n">Bitcoin</span><span class="p">);</span>

    <span class="nd">println!</span><span class="p">(</span><span class="s">"派生地址: {}"</span><span class="p">,</span> <span class="n">address</span><span class="p">);</span>

    <span class="nf">Ok</span><span class="p">(())</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>派生路徑 <code class="language-plaintext highlighter-rouge">m/84'/0'/0'/0/0</code> 遵循 BIP84 標準：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">84'</code> 表示這是 BIP84（原生 SegWit）路徑</li>
  <li>第一個 <code class="language-plaintext highlighter-rouge">0'</code> 是 coin type（Bitcoin）</li>
  <li>第二個 <code class="language-plaintext highlighter-rouge">0'</code> 是帳戶索引</li>
  <li><code class="language-plaintext highlighter-rouge">0</code> 是外部鏈（接收地址，vs 1 是找零地址）</li>
  <li>最後的 <code class="language-plaintext highlighter-rouge">0</code> 是地址索引</li>
</ul>

<p>撇號（’）表示「硬化」派生，這種派生方式即使公開了子公鑰，也無法推導出父私鑰。</p>

<hr />

<h2 id="7-練習題">7. 練習題</h2>

<h3 id="練習-1批量地址生成器">練習 1：批量地址生成器</h3>

<p>創建一個程式，從同一個助記詞批量生成 10 個地址。使用 BIP84 派生路徑（m/84’/0’/0’/0/i），其中 i 從 0 到 9。這個練習幫助你理解 HD 錢包的工作原理。</p>

<h3 id="練習-2地址驗證器">練習 2：地址驗證器</h3>

<p>創建一個函數，接受一個字串並驗證它是否是有效的 Bitcoin 地址。你的函數應該：</p>
<ul>
  <li>識別地址類型（P2PKH、P2SH、P2WPKH、P2WSH、P2TR）</li>
  <li>驗證校驗和（對於 Base58 和 Bech32）</li>
  <li>判斷網路（mainnet vs testnet）</li>
</ul>

<h3 id="練習-3哈希計算器">練習 3：哈希計算器</h3>

<p>創建一個命令列工具，計算輸入的各種 Bitcoin 相關雜湊值。支援 SHA256、SHA256d、RIPEMD160 和 HASH160，並能處理十六進制和 UTF-8 輸入。</p>

<hr />

<h2 id="8-總結">8. 總結</h2>

<p>本篇介紹了使用 Rust 進行 Bitcoin 開發的基礎知識。我們討論了為什麼 Rust 是 Bitcoin 開發的理想選擇——它的記憶體安全、效能和豐富的生態系統。我們設置了開發環境，了解了 rust-bitcoin 函式庫的基本類型，學習了 Bitcoin 使用的各種編碼格式，並寫出了第一個地址生成程式。</p>

<p>關鍵要點：</p>
<ul>
  <li>Bitcoin 內部使用 satoshi 作為金額單位</li>
  <li>secp256k1 橢圓曲線是 Bitcoin 密碼學的基礎</li>
  <li>不同的地址類型有不同的用途和特性</li>
  <li>HD 錢包讓一組助記詞可以派生出無限地址</li>
</ul>

<p>下一篇將深入探討地址生成的細節和交易構建的基礎。我們將學習如何創建、簽名和廣播真正的 Bitcoin 交易。</p>

<hr />

<h2 id="參考資源">參考資源</h2>

<h3 id="官方文檔">官方文檔</h3>
<ul>
  <li><a href="https://docs.rs/bitcoin">rust-bitcoin 文檔</a></li>
  <li><a href="https://docs.rs/secp256k1">secp256k1 文檔</a></li>
  <li><a href="https://docs.rs/bdk">BDK 文檔</a></li>
</ul>

<h3 id="學習資源">學習資源</h3>
<ul>
  <li><a href="https://doc.rust-lang.org/book/">Rust 程式設計語言</a></li>
  <li><a href="https://github.com/rust-bitcoin/rust-bitcoin">rust-bitcoin GitHub</a></li>
</ul>

<h3 id="bips">BIPs</h3>
<ul>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki">BIP 32: HD Wallets</a></li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki">BIP 39: Mnemonic Code</a></li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki">BIP 84: Native SegWit</a></li>
</ul>]]></content><author><name>Cypherpunks Taiwan</name></author><category term="技術教學" /><category term="Bitcoin" /><category term="Rust" /><category term="Rust" /><category term="Bitcoin" /><category term="rust-bitcoin" /><category term="開發環境" /><category term="密碼學" /><summary type="html"><![CDATA[這是 Rust Bitcoin 開發入門系列的第一篇。本系列將帶你從零開始使用 Rust 語言開發 Bitcoin 應用，從基礎設置到進階應用。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" /><media:content medium="image" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Bitcoin Script 實戰教學（六）：進階應用與未來展望</title><link href="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/development/2025/03/20/bitcoin-script-tutorial-6-advanced-applications/" rel="alternate" type="text/html" title="Bitcoin Script 實戰教學（六）：進階應用與未來展望" /><published>2025-03-20T00:00:00+00:00</published><updated>2025-03-20T00:00:00+00:00</updated><id>https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/development/2025/03/20/bitcoin-script-tutorial-6-advanced-applications</id><content type="html" xml:base="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/development/2025/03/20/bitcoin-script-tutorial-6-advanced-applications/"><![CDATA[<h2 id="系列導言">系列導言</h2>

<p>這是「Bitcoin Script 實戰教學」系列的最後一篇。經過前五篇的學習，你已經掌握了比特幣腳本的基礎知識、各種標準腳本類型、SegWit 和 Taproot 的技術細節、多重簽名實現，以及時間鎖機制。在這一篇中，我們將探索比特幣腳本的前沿應用，包括 DLC（Discreet Log Contracts）、Vault 設計模式、Covenant 提案，以及未來可能的發展方向。</p>

<p><strong>系列文章：</strong></p>
<ol>
  <li><a href="/bitcoin/development/2025/03/15/bitcoin-script-tutorial-1-fundamentals/">基礎概念與操作碼</a></li>
  <li><a href="/bitcoin/development/2025/03/16/bitcoin-script-tutorial-2-standard-scripts/">標準腳本類型詳解</a></li>
  <li><a href="/bitcoin/development/2025/03/17/bitcoin-script-tutorial-3-segwit-taproot/">SegWit 與 Taproot</a></li>
  <li><a href="/bitcoin/development/2025/03/18/bitcoin-script-tutorial-4-multisig/">多重簽名完全指南</a></li>
  <li><a href="/bitcoin/development/2025/03/19/bitcoin-script-tutorial-5-timelocks/">時間鎖機制完全指南</a></li>
  <li><strong>進階應用與未來展望</strong>（本篇）</li>
</ol>

<hr />

<h2 id="一discreet-log-contractsdlc">一、Discreet Log Contracts（DLC）</h2>

<h3 id="11-什麼是-dlc">1.1 什麼是 DLC？</h3>

<p>DLC（謹慎日誌合約）是一種創新的比特幣智能合約形式，允許兩方基於現實世界的事件結果進行條件支付。它由 Tadge Dryja（閃電網路的共同發明者）在 2018 年提出，解決了一個長期存在的問題：如何在不信任任何中心化實體的情況下，在比特幣上執行依賴外部數據的合約。</p>

<p>考慮這樣的場景：Alice 和 Bob 想要對 2025 年底比特幣的價格進行賭注。Alice 賭價格會超過 10 萬美元，Bob 賭不會。在傳統系統中，他們需要一個可信的第三方來判定結果並執行支付。DLC 則允許他們創建一個合約，由一個獨立的「神諭」（Oracle）來證明結果，但這個神諭甚至不需要知道合約的存在。</p>

<p>這裡的「謹慎」（Discreet）有雙重含義。首先，神諭發布的是通用數據（如「2025-12-31 BTC/USD = 102,345」），而不是針對特定合約的裁決，這意味著神諭不知道誰在使用它的數據。其次，DLC 交易在鏈上看起來就像普通的多簽名交易，觀察者無法判斷這是一個 DLC 結算。</p>

<h3 id="12-adaptor-signaturesdlc-的密碼學基礎">1.2 Adaptor Signatures：DLC 的密碼學基礎</h3>

<p>DLC 的核心技術是 Adaptor Signatures（適配器簽名），這是一種密碼學構造，允許創建「不完整」的簽名，只有在獲得某個秘密值後才能變成有效簽名。</p>

<p>在標準的 Schnorr 簽名中，簽名者用私鑰 d、隨機數 k、和訊息 m 計算簽名 (R, s)，其中 R = k × G，s = k + H(R, P, m) × d。驗證者可以用公式 s × G = R + H(R, P, m) × P 來驗證。</p>

<p>適配器簽名引入了一個「適配點」T = t × G，其中 t 是適配秘密。簽名者創建一個「不完整」簽名 (R’, s’)，其中 R’ = R + T。這個簽名單獨無法通過驗證，但如果有人知道 t，他們可以計算 s = s’ + t，得到相對於 R’ 的有效簽名。</p>

<p>這個機制的妙處在於：給定 (R’, s’) 和完成的簽名 (R’, s)，任何人都可以計算出 t = s - s’。這意味著使用適配器簽名會「揭示」適配秘密。</p>

<h3 id="13-dlc-的工作流程">1.3 DLC 的工作流程</h3>

<p>讓我們詳細看看 DLC 是如何工作的。</p>

<p>首先是設置階段。Alice 和 Bob 協商合約條款，包括：事件（如「2025-12-31 的 BTC/USD 價格」）、可能的結果（如價格區間）、每種結果對應的支付分配。然後他們找到一個或多個可信的神諭，獲取神諭的公鑰和對這個事件的「承諾點」。承諾點代表神諭將為每個可能結果發布的簽名對應的公鑰。</p>

<p>接下來是資金鎖定。雙方創建一筆「Funding Transaction」，將各自的資金鎖定到一個 2-of-2 多簽輸出。這筆交易上鏈後，資金就無法被任何一方單獨移動。</p>

<p>然後是預簽 CET（Contract Execution Transaction）。這是 DLC 的核心。對於每個可能的結果，雙方創建一筆 CET，表示該結果發生時的資金分配。例如，如果有 100 個可能的價格區間，就會有 100 筆不同的 CET。</p>

<p>關鍵在於：這些 CET 不是用普通簽名，而是用適配器簽名。每筆 CET 的適配點是神諭對該結果的簽名對應的公鑰。這意味著只有當神諭發布特定結果的簽名時，對應的 CET 才能被完成並廣播。</p>

<p>最後是結算。當事件發生時，神諭發布結果和對應的簽名 s。獲勝方使用這個簽名作為適配秘密，完成對應 CET 的適配器簽名，得到有效簽名，然後廣播 CET 獲取資金。</p>

<p>這個機制的優美之處在於：神諭只發布一次數據，完全不知道誰在使用它；雙方在設置階段就交換了所有需要的信息，結算時不需要額外互動；整個過程在鏈上看起來只是普通的多簽名操作。</p>

<h3 id="14-dlc-的應用場景">1.4 DLC 的應用場景</h3>

<p>DLC 開啟了比特幣上許多之前不可能實現的應用。</p>

<p>金融衍生品是最直接的應用。期貨、期權、差價合約都可以用 DLC 實現。例如，一個比特幣礦工可以使用 DLC 對沖未來的價格風險，而不需要依賴任何中心化交易所。</p>

<p>合成資產允許用戶獲得對其他資產（如股票、黃金、其他加密貨幣）的價格敞口，而只使用比特幣作為抵押品。這在傳統金融受限或不信任中心化服務的地區特別有價值。</p>

<p>保險合約也可以用 DLC 實現。航班延誤保險可以基於航班數據神諭自動結算；農業保險可以基於天氣數據；甚至健康保險的某些類型也可以這樣設計。</p>

<p>預測市場讓用戶可以對任何有明確結果的事件進行預測和投注。體育賽事、選舉結果、科學發現——只要有可靠的神諭能證明結果，就可以建立 DLC。</p>

<hr />

<h2 id="二vault-設計模式">二、Vault 設計模式</h2>

<h3 id="21-為什麼需要-vault">2.1 為什麼需要 Vault？</h3>

<p>比特幣的安全模型建立在私鑰控制上：誰擁有私鑰，誰就能花費對應的比特幣。這種模型簡單直接，但也意味著一旦私鑰洩露，資金立即面臨風險，而且通常無法追回。</p>

<p>Vault（金庫）是一種設計模式，旨在為這個問題提供一層額外的保護。它的核心思想是：即使熱密鑰（用於日常操作的密鑰）被盜，攻擊者也無法立即獲得資金。相反，任何提款嘗試都需要經過一段等待期，在此期間，真正的擁有者可以發現攻擊並採取行動——使用一個更安全的「恢復密鑰」將資金轉移到安全地址。</p>

<p>這個概念類似於傳統銀行的大額轉帳延遲或企業的多級審批流程。它創造了一個時間窗口，讓安全監控和人為干預成為可能。</p>

<h3 id="22-使用預簽交易實現-vault">2.2 使用預簽交易實現 Vault</h3>

<p>在目前的比特幣腳本限制下，實現 Vault 的主要方法是使用預簽交易。這種方法有其局限性，但已經可以提供有價值的保護。</p>

<p>Vault 的結構包含兩種密鑰。熱密鑰用於發起提款，可能存儲在連接網路的設備上，面臨被盜的風險。恢復密鑰是高安全級別的密鑰，可能是多簽、分散在多個地理位置、或存儲在硬體錢包中，用於在緊急情況下恢復資金。</p>

<p>Vault 輸出的腳本設計有兩條路徑。第一條是正常提款路徑：使用熱密鑰簽名，將資金轉移到一個「Unvault」狀態，這個狀態有 CSV 時間鎖（例如一週）。第二條是緊急恢復路徑：使用恢復密鑰簽名，可以立即將資金轉移到預設的冷存儲地址。</p>

<p>Unvault 狀態同樣有兩條路徑。第一條是完成提款：等待 CSV 延遲期結束後，用熱密鑰簽名提取資金到目標地址。第二條是取消提款：使用恢復密鑰，立即將資金轉回冷存儲。</p>

<h3 id="23-預簽交易的工作流程">2.3 預簽交易的工作流程</h3>

<p>設置 Vault 時，用戶需要預先簽署一系列交易。</p>

<p>首先是 Unvault 交易：從 Vault 輸出到 Unvault 狀態的轉換。然後是 Withdrawal 交易：從 Unvault 狀態到最終目的地的轉換，帶有 CSV 延遲。接著是 Recovery 交易：從 Vault 直接到冷存儲的緊急轉移。最後是 Cancel 交易：從 Unvault 狀態回到冷存儲的取消操作。</p>

<p>這些交易需要在存入資金之前就簽署好，而且必須安全存儲。特別是 Withdrawal 交易需要為每個可能的目的地預先準備，這限制了靈活性。</p>

<p>正常提款流程是：廣播 Unvault 交易，等待 CSV 延遲（例如 1008 個區塊，約一週），然後廣播 Withdrawal 交易。</p>

<p>緊急恢復流程是：發現異常活動（例如收到 Unvault 交易被廣播的警報），在延遲期結束之前，使用恢復密鑰廣播 Cancel 或 Recovery 交易。</p>

<h3 id="24-當前-vault-的限制">2.4 當前 Vault 的限制</h3>

<p>預簽交易方式的 Vault 有幾個顯著限制。</p>

<p>目的地必須預先確定：由於需要預先簽署 Withdrawal 交易，你必須在設置時就知道所有可能的提款目的地。這對於想要靈活提款的用戶來說很不方便。</p>

<p>金額不可分割：預簽交易鎖定了整個 Vault 的金額，無法只提取部分資金。</p>

<p>安全刪除問題：如果攻擊者獲得了預簽的交易副本，他們可以嘗試使用它。確保舊的預簽交易被完全銷毀是困難的。</p>

<p>手續費固定：預簽交易的手續費率在簽署時就確定了，無法適應未來的網路條件。</p>

<p>這些限制的根本原因是比特幣腳本目前無法「內省」交易本身——腳本無法檢查「這筆花費交易必須發送到特定地址」或「必須保持特定金額」。這正是 Covenant 提案試圖解決的問題。</p>

<hr />

<h2 id="三covenant限制未來的花費方式">三、Covenant：限制未來的花費方式</h2>

<h3 id="31-什麼是-covenant">3.1 什麼是 Covenant？</h3>

<p>Covenant（契約/公約）是一個密碼學和智能合約術語，指的是對 UTXO 未來如何被花費的限制。在目前的比特幣腳本中，你可以限制「誰」可以花費（透過簽名要求）和「何時」可以花費（透過時間鎖）。但你無法限制花費交易的其他屬性，如輸出的目的地、金額，或結構。</p>

<p>Covenant 填補了這個空白。有了 Covenant，你可以創建這樣的腳本：「這筆資金只能被轉移到以下三個地址之一」，或「提款必須分批進行，每次最多 10%」，或「這筆交易的找零必須回到同樣的 Covenant 保護下」。</p>

<p>這個能力看似簡單，但影響深遠。它使得許多目前不可能或需要信任的應用成為可能，同時也引發了關於審查和可替代性的擔憂。</p>

<h3 id="32-主要的-covenant-提案">3.2 主要的 Covenant 提案</h3>

<p>比特幣社區正在討論幾個不同的 Covenant 實現方案，每個都有不同的功能範圍和權衡。</p>

<p>OP_CHECKTEMPLATEVERIFY（CTV，BIP 119）是最保守的提案。它允許腳本承諾一個精確的未來交易「模板」——包括輸出的數量、腳本、金額，以及鎖定時間。花費交易必須精確匹配這個模板。CTV 是功能最有限的 Covenant，但也因此最容易分析其安全性和影響。它已經經過多年審查，可能是最接近被激活的提案。</p>

<p>OP_VAULT（BIP 345）是專門為 Vault 用例設計的。它引入兩個新操作碼：OP_VAULT 和 OP_VAULT_RECOVER。OP_VAULT 創建一個可以被「觸發」進入等待狀態的輸出，然後在延遲後完成提款。OP_VAULT_RECOVER 允許在等待期間將資金恢復到安全地址。這個提案解決了預簽 Vault 的許多限制，允許靈活的目的地和部分提款。</p>

<p>OP_CAT（BIP 347）是一個被「復活」的提案。OP_CAT 在比特幣早期存在，但因為擔心記憶體攻擊而被禁用。現在提議在 Tapscript 中重新啟用它，並設置 520 位元組的輸出限制。OP_CAT 本身只是串接兩個堆疊元素，但它可以與其他現有操作碼組合，實現相當強大的 Covenant。透過在腳本中重建交易結構並驗證其雜湊，可以間接檢查交易的屬性。</p>

<p>更激進的提案如 OP_TXHASH 允許將交易的任何欄位的雜湊推入堆疊，提供完全的交易內省能力。這功能最強但也最有爭議，因為它可能對比特幣的審查抵抗性和可替代性產生影響。</p>

<h3 id="33-ctv-的具體應用">3.3 CTV 的具體應用</h3>

<p>讓我們看看 CTV 可以實現的一些具體應用。</p>

<p>擁塞控制（Congestion Control）是 CTV 最引人注目的用例。想像一個交易所需要同時處理 10,000 個用戶提款。在目前的系統中，這需要一筆巨大的交易（或多筆較大的交易），在網路擁塞時成本極高。</p>

<p>使用 CTV，交易所可以創建一棵「展開樹」。根交易只有一個輸出，承諾到特定的展開結構。這個輸出可以被展開成 100 個子輸出，每個子輸出又承諾到進一步的展開。最終，10,000 個用戶中的每一個都可以領取自己的資金。</p>

<p>優雅的是，這個展開可以是「惰性的」。根交易可以在費用便宜時上鏈，承諾了所有用戶的餘額。之後，每個用戶可以在任何時候展開自己的路徑來領取資金，而不需要其他用戶的配合。如果費用降低，可以一次展開更大的部分；如果費用上升，可以只展開自己關心的路徑。</p>

<p>另一個應用是改進的 Vault。有了 CTV，Vault 可以不需要預簽所有可能的提款交易。相反，Vault 輸出可以承諾一個模板，這個模板指定必須有一個回到同類型 Vault 的找零輸出，而提款金額可以是任意的。這解決了預簽 Vault 的金額不可分割問題。</p>

<h3 id="34-covenant-的擔憂">3.4 Covenant 的擔憂</h3>

<p>Covenant 的引入不是沒有爭議的。主要擔憂包括以下幾個方面。</p>

<p>審查向量：如果可以創建「只能轉移到特定地址」的比特幣，監管機構可能會要求交易所只接受「合規」的 Covenant 代幣。這可能導致不同等級的比特幣，損害可替代性。</p>

<p>複雜性增加：更強大的腳本功能意味著更多的攻擊面和更難審計的合約。已經有一些簡單的 bug 導致了資金損失，更複雜的系統可能會有更多這類問題。</p>

<p>遞迴 Covenant：某些 Covenant 設計（不包括 CTV）允許創建「永久」的限制，資金可能被永久困在某種結構中。這引發了關於比特幣長期可用性的擔憂。</p>

<p>支持者認為這些擔憂被誇大了。Covenant 是選擇加入的——如果你不想使用有限制的比特幣，你可以不接受它們。而其提供的功能——更好的自我託管安全、更高效的交易——對生態系統的價值超過了假設的風險。這個辯論仍在進行中。</p>

<hr />

<h2 id="四bitvm在比特幣上驗證任意計算">四、BitVM：在比特幣上驗證任意計算</h2>

<h3 id="41-突破腳本限制的思路">4.1 突破腳本限制的思路</h3>

<p>比特幣腳本是有意設計為受限的——它不是圖靈完備的，沒有循環，操作碼有限。這是為了安全和可預測性。但如果我們想在比特幣上執行更複雜的邏輯怎麼辦？</p>

<p>BitVM 是 2023 年提出的一個突破性想法：不在鏈上執行計算，而是在鏈上驗證計算。這種模式稱為「樂觀執行」（Optimistic Execution），借鑑了以太坊 Optimistic Rollup 的概念。</p>

<p>核心思想是：如果雙方對計算結果有共識，就不需要任何鏈上驗證。只有當有人聲稱錯誤結果時，才需要在鏈上證明誰對誰錯。而且這個證明可以被設計得非常小，即使原始計算非常複雜。</p>

<h3 id="42-挑戰-回應協議">4.2 挑戰-回應協議</h3>

<p>BitVM 使用二分搜尋的挑戰-回應協議來定位錯誤。</p>

<p>假設 Operator 聲稱 f(x) = y，但 Verifier 認為正確答案應該是 y’。雙方的資金鎖定在一個需要正確結算的合約中。</p>

<p>協議開始時，Operator 發布整個計算軌跡的 Merkle 樹根。這個軌跡記錄了從輸入 x 到輸出 y 的每一步中間狀態。</p>

<p>然後進入二分搜尋。Verifier 選擇軌跡的前半部分或後半部分，要求 Operator 打開。Operator 提供所選部分的 Merkle 證明。如果這部分是正確的，Verifier 將搜尋範圍縮小到另一半；如果這部分已經有錯誤，繼續在這半部分搜尋。</p>

<p>經過 log(N) 輪互動（N 是計算步驟數），雙方定位到單個錯誤步驟。這一步可以在鏈上驗證——執行單個 NAND 門或類似的基本操作是比特幣腳本可以做到的。</p>

<p>如果 Operator 在這一步無法提供有效證明，Verifier 獲得 Operator 的質押金。如果 Operator 是誠實的，Verifier 無法找到錯誤步驟，Operator 保留資金。</p>

<h3 id="43-bitvm-的應用">4.3 BitVM 的應用</h3>

<p>BitVM 最引人注目的應用是去信任的跨鏈橋。</p>

<p>目前的比特幣跨鏈橋大多依賴聯邦多簽：一組操作者控制比特幣端的資金，用戶需要信任他們不會共謀盜取資金。歷史上確實發生過多起橋被攻擊或內部作弊的事件。</p>

<p>使用 BitVM，橋可以這樣運作：用戶在比特幣上鎖定資金，在目標鏈（如以太坊）上收到等值代幣。當用戶想要贖回時，在目標鏈上銷毀代幣，然後 Operator 在比特幣上釋放資金給用戶。</p>

<p>如果 Operator 誠實，流程順利進行。如果 Operator 不釋放資金或嘗試欺詐，用戶（或任何觀察者）可以發起 BitVM 挑戰，證明 Operator 的聲明是錯誤的，從而獲得 Operator 的質押金並恢復資金。</p>

<p>這創造了一個「1-of-N」的信任模型：只要有一個誠實的 Verifier 在監控，欺詐就會被發現和懲罰。這比「M-of-N」的多簽模型安全得多，因為後者需要大多數參與者誠實。</p>

<p>其他應用包括 ZK 證明驗證（在比特幣上驗證零知識證明的正確性）、複雜的 DeFi 邏輯（借貸、自動做市商等，雖然互動性有限制）、以及計算密集型的合約（任何可以編譯成電路的程式）。</p>

<h3 id="44-bitvm-的限制">4.4 BitVM 的限制</h3>

<p>BitVM 不是萬能的，它有顯著的限制。</p>

<p>互動性要求：挑戰協議需要雙方多輪互動。如果一方離線，協議可能超時。這使得 BitVM 更適合有明確交易對手的場景，而不是開放式的智能合約。</p>

<p>延遲：挑戰期（通常一到兩週）是必要的，以確保有足夠時間發現和證明欺詐。這意味著 BitVM 不適合需要即時結算的應用。</p>

<p>複雜性：實現一個安全的 BitVM 系統需要大量的工程努力。預簽交易的數量可能非常龐大，存儲和管理是挑戰。</p>

<p>有限的表達能力：雖然理論上可以驗證任意計算，但實際上受到電路大小和挑戰輪數的限制。</p>

<p>儘管有這些限制，BitVM 代表了在比特幣上實現更複雜邏輯的重要一步，而且不需要任何協議變更。</p>

<hr />

<h2 id="五腳本優化實踐">五、腳本優化實踐</h2>

<h3 id="51-大小與費用的關係">5.1 大小與費用的關係</h3>

<p>在比特幣中，交易費用主要由交易的「虛擬大小」（vbytes）決定。虛擬大小的計算考慮了 SegWit 的權重折扣：非 witness 數據每位元組計 4 個權重單位，witness 數據每位元組計 1 個權重單位。虛擬大小 = (總權重 + 3) / 4。</p>

<p>這意味著優化腳本大小可以直接節省費用。對於多簽名、複雜條件等場景，精心設計的腳本可能比樸素實現節省 20-30% 的費用。</p>

<h3 id="52-實用優化技巧">5.2 實用優化技巧</h3>

<p>利用隱式操作碼。OP_CHECKSIG 在失敗時會使整個腳本失敗，所以不需要額外的 OP_VERIFY。類似地，OP_EQUALVERIFY、OP_CHECKSIGVERIFY 等合併操作碼比分開的操作更緊湊。</p>

<p>重用堆疊元素。如果同一個值需要使用多次，用 OP_DUP 複製比再次推入堆疊更節省空間。</p>

<p>使用最短編碼。OP_1 到 OP_16 各只需 1 個位元組，而推入對應的數字需要 2 個位元組。負數和大數字的編碼也要注意使用最小表示。</p>

<p>優化 Taproot 腳本樹。將最可能使用的腳本放在樹的淺層（接近根部），這樣花費時需要揭示的 Merkle 路徑更短。對於多條件腳本，仔細分析每條路徑的使用概率。</p>

<p>考慮聚合。對於 n-of-n 多簽，使用 MuSig2 將所有簽名聚合成一個，這比 n 個獨立簽名節省大量空間。即使對於 m-of-n，有時候也可以設計備用的聚合路徑。</p>

<h3 id="53-安全與效率的權衡">5.3 安全與效率的權衡</h3>

<p>優化不能以犧牲安全為代價。一些需要注意的陷阱包括以下幾點。</p>

<p>不要跳過必要的驗證。例如，確保所有分支都正確終止，即使看起來「不可達」的分支也可能因為意外輸入而被執行。</p>

<p>注意簽名雜湊類型。SIGHASH_NONE 和 SIGHASH_SINGLE 可能意外地允許修改輸出，只有在完全理解其影響時才使用。</p>

<p>時間鎖需要安全邊際。區塊時間有波動，MTP 與實際時間可能有偏差。設計時間鎖時要留有餘裕。</p>

<p>測試所有路徑。使用腳本調試工具（如 btcdeb）測試腳本的每條可能執行路徑，包括失敗情況。</p>

<hr />

<h2 id="六未來展望">六、未來展望</h2>

<h3 id="61-當前腳本能力總結">6.1 當前腳本能力總結</h3>

<p>讓我們總結一下比特幣腳本目前能做什麼和不能做什麼。</p>

<p>可以做到的包括：各種簽名驗證（單簽、多簽、聚合簽名）、絕對和相對時間鎖、雜湊鎖和 HTLC、複雜的條件邏輯、Taproot 的隱藏腳本樹、預簽交易實現的 Vault、依賴神諭的 DLC、透過樂觀執行的任意計算驗證（BitVM）。</p>

<p>無法直接做到（需要協議升級）的包括：檢查花費交易的輸出目的地、檢查花費交易的輸出金額、循環和遞迴計算、原生的零知識證明驗證、完全靈活的 Covenant。</p>

<h3 id="62-可能的協議升級">6.2 可能的協議升級</h3>

<p>比特幣的升級過程謹慎而緩慢，但社區正在積極討論幾個可能的改進。</p>

<p>短期可能實現的包括：OP_CTV 已經過多年審查，有可能在未來幾年內透過軟分叉激活。它將啟用擁塞控制和改進的 Vault，同時風險相對較低。</p>

<p>OP_CAT 的復活也在討論中。雖然它單獨功能有限，但與現有操作碼組合可以實現許多 Covenant 用例。由於 Tapscript 有堆疊元素大小限制，早期的安全擔憂已大大減輕。</p>

<p>中期可能探索的包括：OP_VAULT 提供了專門的 Vault 功能，比通用 Covenant 更容易分析影響。SIGHASH_ANYPREVOUT 允許更好的閃電網路協議（如 Eltoo），可能與其他升級一起打包。</p>

<p>長期研究方向包括：原生 ZK 驗證將允許在比特幣上直接驗證零知識證明，這對可擴展性和隱私有深遠影響。更強大的 Covenant 和腳本能力也在研究中，但需要在功能和風險之間找到平衡。</p>

<h3 id="63-生態系統的發展">6.3 生態系統的發展</h3>

<p>除了協議層面的變化，比特幣腳本的應用生態也在快速發展。</p>

<p>工具鏈在改進。Miniscript 提供了一種更安全的方式來編寫和分析腳本，許多錢包和服務正在採用。腳本調試、形式驗證、自動測試工具都在進步。</p>

<p>標準化在推進。描述符（Descriptors）成為錢包備份和恢復的標準格式。PSBT 使多方協作更加容易。MuSig2 的廣泛採用提升了多簽的效率和隱私。</p>

<p>應用在擴展。閃電網路持續增長，DLC 平台開始上線，Vault 解決方案進入生產環境。這些應用驗證了比特幣腳本的實用性，也推動了進一步的創新。</p>

<hr />

<h2 id="七實作練習">七、實作練習</h2>

<h3 id="練習-1設計-dlc-結構">練習 1：設計 DLC 結構</h3>

<p>選擇一個簡單的二元事件（如「明天會下雨嗎？」），設計完整的 DLC 結構。考慮：如何找到可靠的神諭？如何處理神諭不發布結果的情況？計算需要多少個 CET。</p>

<h3 id="練習-2預簽-vault-實現">練習 2：預簽 Vault 實現</h3>

<p>使用你熟悉的比特幣程式庫，實現一個簡化的預簽 Vault。包括：創建 Vault 輸出、預簽 Unvault 和 Withdrawal 交易、模擬正常提款和緊急恢復流程。思考在生產環境中需要解決的額外問題。</p>

<h3 id="練習-3ctv-擁塞控制模擬">練習 3：CTV 擁塞控制模擬</h3>

<p>假設 CTV 已經激活，設計一個擁塞控制樹來處理 1000 個接收者的批量支付。計算不同樹結構（深度、分支因子）的權衡，比較與傳統大交易的費用差異。</p>

<hr />

<h2 id="八總結">八、總結</h2>

<p>這一系列文章帶你從比特幣腳本的基礎走到了最前沿的應用。讓我們回顧關鍵要點。</p>

<p>比特幣腳本是一個有意設計為受限的系統，它不是為了與以太坊競爭「智能合約平台」的地位，而是為了提供安全、可驗證、可預測的價值轉移能力。在這個設計空間內，我們仍然可以構建令人印象深刻的應用。</p>

<p>多簽和時間鎖是兩個最基本的構建塊。多簽將控制權從單點分散到多點，時間鎖在時間維度上增加條件。這兩者的組合（加上哈希鎖）使得 HTLC 成為可能，而 HTLC 是閃電網路和原子交換的基礎。</p>

<p>Taproot 是腳本能力的一次重大飛躍。透過 MAST 結構，複雜的條件邏輯可以保持隱藏；透過 Schnorr 簽名，多方協作可以看起來像單方操作。這對隱私和效率都有深遠影響。</p>

<p>DLC、Vault、BitVM 代表了在現有能力下的極限探索。它們展示了創意和密碼學如何突破表面的限制，實現之前認為不可能的功能。</p>

<p>Covenant 的討論反映了比特幣社區在功能擴展和風險控制之間的持續權衡。這不是一個簡單的技術問題，而是涉及經濟激勵、審查抵抗、長期可持續性的複雜考量。</p>

<p>無論未來如何發展，理解比特幣腳本的當前能力和限制對於任何比特幣開發者都是必要的。希望這個系列給你提供了一個堅實的基礎。</p>

<hr />

<h2 id="參考資料">參考資料</h2>

<ul>
  <li><a href="https://github.com/discreetlogcontracts/dlcspecs">DLC 規範</a> - DLC 的正式規格</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0119.mediawiki">BIP 119: OP_CHECKTEMPLATEVERIFY</a> - CTV 提案</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0345.mediawiki">BIP 345: OP_VAULT</a> - Vault 專用操作碼提案</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0347.mediawiki">BIP 347: OP_CAT</a> - OP_CAT 復活提案</li>
  <li><a href="https://bitvm.org/bitvm.pdf">BitVM 白皮書</a> - BitVM 的原始論文</li>
  <li><a href="https://covenants.info/">Covenant 資訊集合</a> - 各種 Covenant 提案的比較</li>
  <li><a href="https://bitcoinops.org/">Bitcoin Optech</a> - 比特幣技術的最新發展</li>
  <li><a href="https://bitcoin.sipa.be/miniscript/">Miniscript</a> - 更安全的腳本編寫方式</li>
</ul>]]></content><author><name>Cypherpunks Taiwan</name></author><category term="技術教學" /><category term="bitcoin" /><category term="development" /><category term="bitcoin" /><category term="script" /><category term="dlc" /><category term="vault" /><category term="covenant" /><summary type="html"><![CDATA[Bitcoin Script 系列最終篇，探討 DLC、Vault、Covenant 等進階應用，以及比特幣腳本未來可能的發展方向。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" /><media:content medium="image" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Bitcoin Script 實戰教學（五）：時間鎖機制完全指南</title><link href="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/development/2025/03/19/bitcoin-script-tutorial-5-timelocks/" rel="alternate" type="text/html" title="Bitcoin Script 實戰教學（五）：時間鎖機制完全指南" /><published>2025-03-19T00:00:00+00:00</published><updated>2025-03-19T00:00:00+00:00</updated><id>https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/development/2025/03/19/bitcoin-script-tutorial-5-timelocks</id><content type="html" xml:base="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/development/2025/03/19/bitcoin-script-tutorial-5-timelocks/"><![CDATA[<h2 id="系列導言">系列導言</h2>

<p>這是「Bitcoin Script 實戰教學」系列的第五篇。本篇將深入探討時間鎖機制——比特幣腳本中控制「何時」可以花費資金的強大工具。時間鎖是構建支付通道、閃電網路、原子交換等進階應用的基礎技術，理解它對於掌握比特幣智能合約至關重要。</p>

<p><strong>系列文章：</strong></p>
<ol>
  <li><a href="/bitcoin/development/2025/03/15/bitcoin-script-tutorial-1-fundamentals/">基礎概念與操作碼</a></li>
  <li><a href="/bitcoin/development/2025/03/16/bitcoin-script-tutorial-2-standard-scripts/">標準腳本類型詳解</a></li>
  <li><a href="/bitcoin/development/2025/03/17/bitcoin-script-tutorial-3-segwit-taproot/">SegWit 與 Taproot</a></li>
  <li><a href="/bitcoin/development/2025/03/18/bitcoin-script-tutorial-4-multisig/">多重簽名完全指南</a></li>
  <li><strong>時間鎖機制完全指南</strong>（本篇）</li>
  <li>進階應用與智能合約</li>
</ol>

<hr />

<h2 id="一理解時間鎖">一、理解時間鎖</h2>

<h3 id="11-什麼是時間鎖">1.1 什麼是時間鎖？</h3>

<p>在日常生活中，我們經常遇到時間相關的限制：銀行定存有到期日、支票可以開立遠期支票、遺囑在當事人去世前無效。時間鎖（Timelock）在比特幣中實現了類似的概念：它允許我們設定條件，使得資金只能在特定時間之後才能被花費。</p>

<p>這個看似簡單的功能，實際上是構建複雜金融應用的基石。想像一下：如果沒有時間鎖，就無法實現「如果三天內交易沒有完成，資金自動退回」這樣的條件。而這恰恰是跨鏈原子交換和閃電網路的核心需求。</p>

<p>比特幣支援兩種基本類型的時間鎖：絕對時間鎖指定一個具體的時間點或區塊高度，資金在此之前無法花費；相對時間鎖則指定一個時間長度，從 UTXO 被創建（確認）後開始計算，經過這段時間後資金才能花費。</p>

<h3 id="12-時間的表示方式">1.2 時間的表示方式</h3>

<p>比特幣的時間鎖使用一個巧妙的機制來區分區塊高度和 Unix 時間戳：如果數值小於 5 億（500,000,000），它被解釋為區塊高度；如果大於或等於 5 億，它被解釋為 Unix 時間戳。</p>

<p>選擇 5 億作為分界點是經過深思熟慮的。比特幣大約每 10 分鐘產生一個區塊，按此速度計算，達到 5 億區塊需要大約 9,500 年。而 5 億秒作為 Unix 時間戳，對應的是 1985 年 11 月——這顯然已經過去了。因此，這個分界點在可預見的未來都不會造成歧義。</p>

<p>區塊高度作為時間度量有其獨特優勢：它不受時區影響，且與比特幣網路的實際進展直接相關。但區塊產生時間有波動（可能 5 分鐘也可能 20 分鐘），所以對於需要精確時間的場景，時間戳可能更合適。</p>

<p>值得注意的是，比特幣的時間戳驗證不使用當前時間，而是使用「中位時間過去」（Median Time Past，MTP）——過去 11 個區塊時間戳的中位數。這個設計防止了礦工通過操縱單個區塊的時間戳來影響時間鎖。</p>

<h3 id="13-四種時間鎖機制">1.3 四種時間鎖機制</h3>

<p>比特幣實現了四種不同層級的時間鎖機制，可以按照兩個維度進行分類。</p>

<p>按時間類型分：nLocktime 和 CLTV 是絕對時間鎖，指定具體的時間點或區塊高度；nSequence 和 CSV 是相對時間鎖，指定從 UTXO 確認後經過的時間。</p>

<p>按實現層級分：nLocktime 和 nSequence 是交易層級的機制，它們影響整個交易何時可以被打包進區塊；CLTV 和 CSV 是腳本層級的機制，它們作為腳本中的操作碼，在花費時驗證時間條件。</p>

<p>這四種機制各有其設計目的和適用場景，接下來我們將逐一深入探討。</p>

<hr />

<h2 id="二nlocktime交易層級絕對時間鎖">二、nLocktime：交易層級絕對時間鎖</h2>

<h3 id="21-設計與歷史">2.1 設計與歷史</h3>

<p>nLocktime 是比特幣最早的時間鎖機制，從創世區塊就存在。它是交易結構的一部分，佔據交易末尾的 4 個位元組。這個欄位的設計初衷是實現一種稱為「高頻交易通道」的功能——允許雙方在鏈下快速更新交易，但這個機制從未完整實現。</p>

<p>nLocktime 的工作原理相當直接：如果設置了一個非零的 nLocktime 值，這筆交易在指定的時間或區塊高度之前，不會被礦工打包進區塊，也不會被節點接受進記憶池。</p>

<h3 id="22-啟用條件">2.2 啟用條件</h3>

<p>nLocktime 的啟用需要一個額外條件：交易中至少有一個輸入的 sequence 欄位不等於 0xFFFFFFFF（最大值）。這個設計源於 sequence 欄位的原始用途——允許透過遞增 sequence 來更新未確認交易。當所有輸入的 sequence 都是最大值時，表示交易是「最終的」，nLocktime 將被忽略。</p>

<p>這個機制在實踐中造成了一些混淆。許多錢包軟體在不需要時間鎖時會將 sequence 設為 0xFFFFFFFF，此時即使設置了 nLocktime 也不會生效。如果你要使用 nLocktime，確保至少一個輸入的 sequence 小於最大值——通常使用 0xFFFFFFFE。</p>

<h3 id="23-實際應用與限制">2.3 實際應用與限制</h3>

<p>nLocktime 最常見的應用是創建「延遲支付」——一筆在未來某個時間點才生效的交易。例如，你可以創建一筆 nLocktime 設為一週後的交易，作為對某人的承諾，但保留在此之前取消的能力。</p>

<p>然而，nLocktime 有一個根本性的限制：它只是「紳士協定」。持有私鑰的人可以隨時創建一筆新交易（沒有時間鎖），花費相同的輸入。nLocktime 交易要等到解鎖時間才能廣播，而在此之前，發送者完全可以反悔。</p>

<p>這個限制意味著 nLocktime 不適合需要強制執行的場景。接收者無法確信資金真的會在未來到達——發送者可能在到期前花掉這些幣。對於這些場景，我們需要腳本層級的時間鎖。</p>

<h3 id="24-記憶池行為">2.4 記憶池行為</h3>

<p>另一個實用性問題是記憶池的處理方式。目前的比特幣節點不會接受 nLocktime 尚未解鎖的交易進入記憶池。這意味著你不能提前將交易發送到網路上「等待」——你需要自己保管這筆交易，直到時間到了再廣播。</p>

<p>這對用戶體驗有明顯影響：你必須確保在正確的時間上線並廣播交易，或者依賴第三方服務來代為執行。</p>

<hr />

<h2 id="三op_checklocktimeverifycltv">三、OP_CHECKLOCKTIMEVERIFY（CLTV）</h2>

<h3 id="31-從交易層級到腳本層級">3.1 從交易層級到腳本層級</h3>

<p>CLTV（Check Lock Time Verify）透過 BIP 65 在 2015 年底引入，填補了 nLocktime 留下的空白。與 nLocktime 不同，CLTV 是一個腳本操作碼，在腳本執行時強制檢查時間條件。</p>

<p>CLTV 的設計非常精巧：它讀取堆疊頂端的值作為時間鎖，然後驗證花費這筆輸出的交易的 nLocktime 是否滿足條件。注意，CLTV 本身不消耗堆疊上的值——這是為了兼容性考慮。因此，使用 CLTV 後通常需要 OP_DROP 來清理堆疊。</p>

<h3 id="32-執行規則">3.2 執行規則</h3>

<p>CLTV 的驗證邏輯檢查幾個條件。首先，堆疊頂端的值必須是非負數。其次，堆疊頂端的值和交易的 nLocktime 必須使用相同的時間類型——兩者都小於 5 億（區塊高度）或都大於等於 5 億（時間戳）。混合使用會導致腳本失敗。</p>

<p>最關鍵的檢查是：交易的 nLocktime 必須大於或等於腳本中指定的值。這確保了交易至少要等到腳本規定的時間才能被確認。</p>

<p>此外，CLTV 還要求花費這個輸出的輸入的 sequence 不能是 0xFFFFFFFF，因為那會禁用 nLocktime 機制。</p>

<h3 id="33-為什麼-cltv-無法繞過">3.3 為什麼 CLTV 無法繞過</h3>

<p>CLTV 解決了 nLocktime 的「可繞過」問題。一旦資金被發送到包含 CLTV 的腳本地址，就沒有任何方法可以在指定時間之前花費它——即使所有相關私鑰的持有者都同意也不行。</p>

<p>這是因為 CLTV 條件被編碼在腳本中，而腳本是輸出的一部分。花費這個輸出的任何交易都必須滿足腳本條件，沒有例外。唯一的方法是等待時間到期。</p>

<h3 id="34-應用場景遺產規劃">3.4 應用場景：遺產規劃</h3>

<p>CLTV 的經典應用之一是遺產規劃。考慮這樣的場景：Alice 想要確保如果她發生意外，她的比特幣可以被她的女兒 Carol 繼承。但她不想現在就把控制權交給 Carol。</p>

<p>使用 CLTV，Alice 可以創建一個這樣的腳本：正常情況下 Alice 可以用自己的簽名花費；但如果腳本中的時間條件滿足（比如五年後），Carol 也可以用她的簽名花費。Alice 可以每隔幾年將資金轉移到新的腳本（更新時間鎖），只要她仍然健在。</p>

<p>這個方案不需要任何信任的第三方，不需要律師或託管服務。時間鎖由比特幣協議本身強制執行。</p>

<h3 id="35-應用場景分期解鎖">3.5 應用場景：分期解鎖</h3>

<p>另一個常見應用是分期解鎖，用於員工股權激勵或投資者鎖倉。假設公司承諾給員工分四年發放比特幣獎勵，每年解鎖 25%。</p>

<p>公司可以創建四個獨立的 UTXO，每個包含總獎勵的 25%，並設置不同的 CLTV 時間鎖：第一個一年後解鎖，第二個兩年後，依此類推。這樣員工可以確信公司無法撤銷承諾（除非員工把私鑰給了公司），而公司也知道員工不能提前取走資金。</p>

<hr />

<h2 id="四nsequence相對時間鎖基礎">四、nSequence：相對時間鎖基礎</h2>

<h3 id="41-從失敗的設計到重獲新生">4.1 從失敗的設計到重獲新生</h3>

<p>nSequence 欄位的歷史頗為曲折。在比特幣最初的設計中，中本聰構想了一種機制：未確認交易可以透過提高 sequence 數字來「更新」。較高 sequence 的交易會取代較低的版本，而礦工會優先打包 sequence 最大的交易。</p>

<p>這個設計從未正確實現，原因是它存在嚴重的激勵問題。礦工沒有義務遵守這個規則——他們可以打包任何有效交易，包括低 sequence 但高手續費的版本。這讓整個機制變得不可靠。</p>

<p>多年來，sequence 欄位基本上被忽略，大多數錢包將其設為最大值。直到 2016 年，BIP 68 重新定義了這個欄位的語義，賦予它相對時間鎖的新功能。</p>

<h3 id="42-bip-68-的語義定義">4.2 BIP 68 的語義定義</h3>

<p>BIP 68 對 sequence 欄位進行了精確的位元定義。最高位（bit 31）是禁用標誌：如果設置，這個輸入不受相對時間鎖限制。第 22 位是類型標誌：如果設置，時間鎖以 512 秒為單位計算；否則以區塊數計算。最低 16 位（bits 0-15）是實際的鎖定值。</p>

<p>這個設計允許的最大相對時間鎖是 65,535 個區塊（約 455 天）或 65,535 × 512 秒（約 388 天）。對於大多數應用場景，這已經足夠了。</p>

<p>選擇 512 秒（約 8.5 分鐘）作為時間單位而不是秒，是為了匹配比特幣的區塊時間粒度。這也意味著時間鎖的精度大約是 8 分鐘，對於大多數場景來說足夠精確。</p>

<h3 id="43-相對時間鎖的含義">4.3 相對時間鎖的含義</h3>

<p>與絕對時間鎖不同，相對時間鎖的起點是 UTXO 被確認的時間，而不是某個固定時間點。這有重要的實際意義：你不需要預先知道 UTXO 何時會被創建，只需要指定「創建後需要等待多久」。</p>

<p>考慮一個支付通道的關閉場景：當一方單方面廣播關閉交易時，另一方需要時間來檢測和回應。相對時間鎖確保無論通道何時關閉，都有固定的「挑戰期」。如果使用絕對時間鎖，你需要在通道開設時就預測可能的關閉時間，這是不切實際的。</p>

<h3 id="44-與-nlocktime-的交互">4.4 與 nLocktime 的交互</h3>

<p>nSequence 的相對時間鎖與 nLocktime 的絕對時間鎖可以同時使用。規則是：一個交易要被接受，必須同時滿足 nLocktime 條件和所有輸入的 nSequence 條件。</p>

<p>這創造了一些有趣的可能性。例如，一個交易可以被設計成「在 2025 年 1 月 1 日之後，且在輸入被創建一週後」才能被確認。兩個條件都必須滿足。</p>

<hr />

<h2 id="五op_checksequenceverifycsv">五、OP_CHECKSEQUENCEVERIFY（CSV）</h2>

<h3 id="51-將相對時間鎖帶入腳本">5.1 將相對時間鎖帶入腳本</h3>

<p>CSV（Check Sequence Verify）透過 BIP 112 引入，是相對時間鎖在腳本層級的實現。與 CLTV 類似，CSV 讀取堆疊頂端的值，並驗證花費交易的對應輸入的 sequence 是否滿足條件。</p>

<p>CSV 需要交易版本至少為 2。這是因為 BIP 68 的 sequence 語義只適用於版本 2 及以上的交易，舊版本交易的 sequence 欄位沒有時間鎖含義。</p>

<h3 id="52-執行規則">5.2 執行規則</h3>

<p>CSV 的驗證邏輯首先檢查堆疊值的禁用位（bit 31）。如果設置了禁用位，CSV 成為無操作——直接成功，不做任何檢查。這允許腳本在某些條件下「跳過」時間鎖。</p>

<p>如果禁用位未設置，CSV 驗證：交易版本至少為 2；輸入的 sequence 禁用位未設置；堆疊值和 sequence 使用相同的時間類型（都是區塊或都是時間）；sequence 的鎖定值大於或等於堆疊值的鎖定值。</p>

<h3 id="53-支付通道中的核心作用">5.3 支付通道中的核心作用</h3>

<p>CSV 在支付通道和閃電網路中扮演著核心角色。讓我們看看為什麼。</p>

<p>在雙向支付通道中，雙方可以離線更新通道狀態（餘額分配）。但這創造了一個問題：任何一方都可以廣播舊的狀態，試圖獲取比應得更多的資金。這被稱為「通道關閉欺詐」。</p>

<p>解決方案是使用 CSV 創建「挑戰期」。當一方廣播關閉交易時，他們的資金不能立即提取，必須等待一段時間（例如一週）。在這段時間內，另一方可以提交證據證明這是舊狀態，並取走所有資金作為懲罰。</p>

<p>這個「撤銷」機制的關鍵在於相對時間鎖：無論何時關閉通道，都有固定的挑戰期。如果使用絕對時間鎖，長期運行的通道將面臨時間鎖過期的問題。</p>

<h3 id="54-可撤銷輸出腳本">5.4 可撤銷輸出腳本</h3>

<p>閃電網路的承諾交易使用一種稱為「可撤銷輸出」的模式。當通道更新時，雙方交換前一狀態的「撤銷密鑰」。如果任何一方廣播舊狀態，對方可以使用撤銷密鑰立即取走所有資金。</p>

<p>這個腳本通常這樣設計：有兩條花費路徑。第一條是撤銷路徑——對方可以用撤銷密鑰簽名，立即花費。第二條是延遲路徑——擁有者需要等待 CSV 延遲期，然後用自己的簽名花費。</p>

<p>如果廣播的是最新狀態，對方沒有撤銷密鑰，只能等待延遲期結束後資金被正當領取。如果廣播的是舊狀態，對方持有撤銷密鑰，可以在延遲期內取走所有資金。這創造了強大的不作弊激勵。</p>

<hr />

<h2 id="六htlc時間鎖與哈希鎖的結合">六、HTLC：時間鎖與哈希鎖的結合</h2>

<h3 id="61-核心概念">6.1 核心概念</h3>

<p>HTLC（Hash Time Locked Contract，哈希時間鎖定合約）是時間鎖最重要的應用之一，結合了哈希鎖和時間鎖，創造出一種條件支付機制：收款人必須在限定時間內提供一個秘密（原像），否則資金會退回給付款人。</p>

<p>HTLC 的結構包含兩條路徑。成功路徑要求收款人提供一個值 preimage，使得 HASH160(preimage) 等於預設的 payment_hash，同時提供有效簽名。退款路徑允許付款人在時間鎖過期後，用自己的簽名取回資金。</p>

<p>這個機制的精妙之處在於：付款人創建 HTLC 時並不知道原像，只有收款人知道。收款人透過揭示原像來「領取」支付，而這個揭示動作是公開的——任何看到原像的人都可以用它來解鎖其他使用相同 payment_hash 的 HTLC。</p>

<h3 id="62-原子交換">6.2 原子交換</h3>

<p>HTLC 最早的應用之一是跨鏈原子交換。假設 Alice 有 BTC 想要換取 Bob 的 LTC。傳統方法需要信任：誰先發送誰就面臨對方不履約的風險。HTLC 消除了這種信任需求。</p>

<p>流程如下：Alice 首先生成一個隨機的 preimage 並計算 payment_hash。她在比特幣鏈上創建一個 HTLC，將 BTC 鎖定給 Bob，條件是 Bob 需要提供這個 preimage。Alice 設置較長的超時時間，比如 48 小時。</p>

<p>Bob 看到 Alice 的 HTLC 後，在萊特幣鏈上創建對應的 HTLC，將 LTC 鎖定給 Alice，使用相同的 payment_hash。Bob 設置較短的超時時間，比如 24 小時。</p>

<p>現在 Alice 可以安全地領取 LTC：她用 preimage 解鎖 Bob 在萊特幣鏈上的 HTLC。這個動作會將 preimage 公開在萊特幣鏈上。</p>

<p>Bob 從萊特幣鏈上看到 preimage，用它來解鎖 Alice 在比特幣鏈上的 HTLC，獲取 BTC。</p>

<p>如果 Alice 不領取 LTC（可能她改變主意了），兩個 HTLC 都會超時：Bob 取回 LTC，Alice 取回 BTC。沒有人損失資金。</p>

<p>超時時間的差異很重要：Alice 必須有足夠時間在 Bob 取回 LTC 之前領取它。如果兩個超時相同，Bob 可能在 Alice 領取 LTC 的同時取回它，造成 Alice 揭示了 preimage 卻沒有拿到 LTC，而 Bob 用這個 preimage 拿走 BTC。</p>

<h3 id="63-閃電網路支付">6.3 閃電網路支付</h3>

<p>閃電網路將 HTLC 的原理發揮到極致。在閃電網路中，支付可以透過多個節點中繼，每一跳都使用 HTLC 來保證原子性——要麼整條路徑上的所有 HTLC 都被解鎖，要麼都超時退回。</p>

<p>假設 Alice 要透過 Bob（路由節點）支付給 Carol。首先，Carol 生成 preimage 和 payment_hash，將 payment_hash 告訴 Alice。Alice 在她和 Bob 的通道中創建一個 HTLC（支付給 Bob，條件是 Bob 提供 preimage）。Bob 在他和 Carol 的通道中創建對應的 HTLC（支付給 Carol，同樣的 payment_hash）。Carol 用 preimage 領取 Bob 的 HTLC。Bob 從 Carol 那裡獲得 preimage，用它領取 Alice 的 HTLC。</p>

<p>整個過程中，只有最終收款人 Carol 知道 preimage。當她揭示它時，支付沿著路徑回溯結算。每個節點都有足夠的時間完成自己的部分，因為超時時間沿著路徑遞減。</p>

<h3 id="64-超時差與安全性">6.4 超時差與安全性</h3>

<p>HTLC 路由中的超時差（cltv_expiry_delta）是關鍵的安全參數。每個路由節點需要確保自己有足夠時間：在上游 HTLC 超時之前，有時間發現下游 HTLC 被領取（或超時），並相應地處理上游 HTLC。</p>

<p>如果超時差太小，可能發生這樣的情況：上游 HTLC 即將超時，付款人廣播退款交易。同時，下游收款人廣播領取交易。中間節點可能既無法領取上游（因為超時了）也無法阻止下游（因為已經成功），導致損失。</p>

<p>閃電網路的 BOLT 規範建議每跳至少 40 個區塊（約 6.7 小時）的超時差，以確保在各種網路延遲和區塊重組情況下的安全性。</p>

<hr />

<h2 id="七時間鎖的安全考量">七、時間鎖的安全考量</h2>

<h3 id="71-時間戳操縱">7.1 時間戳操縱</h3>

<p>礦工對區塊時間戳有一定的調整空間。規則允許時間戳比前一區塊向前最多 2 小時，且必須大於 MTP。這意味著單個礦工可以將時間戳設得比真實時間略早或略晚。</p>

<p>對於使用時間戳的時間鎖來說，這創造了一些不確定性。一個設定在特定時間解鎖的 HTLC，實際解鎖時間可能比預期早或晚幾個小時。對於高價值或時間敏感的應用，使用區塊高度通常更可預測。</p>

<p>MTP 機制減輕了這個問題的嚴重性。操縱 MTP 需要控制連續多個區塊的時間戳，這對於沒有大量算力的攻擊者來說是不切實際的。</p>

<h3 id="72-費用炒作攻擊">7.2 費用炒作攻擊</h3>

<p>在時間鎖即將到期時，可能出現費用競爭的情況。考慮一個 HTLC：收款人想要領取，而付款人想要退款。如果兩個交易在接近超時時同時廣播，誰的交易被打包取決於手續費。</p>

<p>這可能導致「費用炒作」——雙方不斷提高手續費試圖讓自己的交易被優先打包。在極端情況下，手續費可能接近甚至超過 HTLC 的價值。</p>

<p>解決方案是設計合理的超時差，確保一方有明確的時間窗口來行動。此外，一些協議使用「預簽名」交易，在 HTLC 創建時就鎖定手續費率，避免後續競價。</p>

<h3 id="73-區塊鏈重組風險">7.3 區塊鏈重組風險</h3>

<p>區塊鏈重組可能影響時間鎖的行為。如果一個 HTLC 被領取的交易所在區塊被重組掉，preimage 的揭示可能被「逆轉」。對於跨鏈原子交換，這創造了風險：一條鏈上的支付被確認（多次），而另一條鏈發生重組。</p>

<p>緩解措施包括：等待足夠多的確認（特別是對於高價值交易）；設計超時時間時考慮可能的重組；在關鍵操作前檢查對手方鏈的確認深度。</p>

<hr />

<h2 id="八taproot-時代的時間鎖">八、Taproot 時代的時間鎖</h2>

<h3 id="81-隱私提升">8.1 隱私提升</h3>

<p>Taproot 為時間鎖應用帶來了顯著的隱私改進。傳統的 HTLC 在花費時會揭示整個腳本結構，觀察者可以清楚看到這是一個條件支付。而在 Taproot 中，時間鎖條件可以隱藏在腳本樹中。</p>

<p>如果大多數情況下雙方合作（例如閃電網路通道正常關閉），他們可以使用 key path 花費——在鏈上看起來就像普通的單簽名交易。時間鎖路徑只有在需要時才會揭示，而且只揭示使用的那個分支，其他備用路徑保持完全隱藏。</p>

<p>這意味著大多數閃電網路交易在鏈上將無法與普通交易區分，大幅提升整個網路的隱私性。</p>

<h3 id="82-效率優化">8.2 效率優化</h3>

<p>除了隱私，Taproot 還帶來效率提升。傳統 HTLC 需要在每次花費時揭示完整腳本，包括所有公鑰、哈希值、時間鎖等。Taproot 的 key path 只需要一個 64 位元組的簽名，是可能的最小見證大小。</p>

<p>即使使用 script path，也只需要揭示實際使用的腳本分支和 Merkle 證明，而不是整個腳本。對於有多個條件分支的複雜合約，這可以節省大量空間。</p>

<h3 id="83-ptlcs超越-htlc">8.3 PTLCs：超越 HTLC</h3>

<p>Taproot 還使得 PTLC（Point Time Locked Contract）成為可能，這是 HTLC 的進化版本。PTLC 使用「adaptor signatures」代替哈希鎖，提供了幾個重要優勢。</p>

<p>首先是隱私。HTLC 的 payment_hash 在整條支付路徑上是相同的，這創造了關聯性——知道入口和出口 payment_hash 相同的觀察者可以追蹤支付。PTLC 在每一跳使用不同的密碼學「謎題」，打斷了這種關聯。</p>

<p>其次是多路徑支付的原子性。將大額支付分成多條路徑時，HTLC 方案中的 preimage 一旦在任何路徑上揭示，就可能被用來領取其他路徑。PTLC 可以設計成真正原子的多路徑支付。</p>

<p>PTLC 的廣泛採用需要閃電網路生態的升級，這正在逐步進行中。</p>

<hr />

<h2 id="九實作練習">九、實作練習</h2>

<h3 id="練習-1cltv-遺產腳本">練習 1：CLTV 遺產腳本</h3>

<p>設計一個遺產規劃腳本：擁有者可以隨時花費，繼承人在指定日期後可以花費。計算正確的 CLTV 值（使用 Unix 時間戳），構建完整的 witness script，生成 P2WSH 地址。</p>

<h3 id="練習-2csv-支付通道">練習 2：CSV 支付通道</h3>

<p>設計一個簡化版的支付通道關閉腳本：雙方可以合作即時關閉（2-of-2 多簽），發起者需要等待 1008 區塊後才能單方面關閉。思考為什麼發起者需要延遲而接收者不需要。</p>

<h3 id="練習-3完整-htlc-生命週期">練習 3：完整 HTLC 生命週期</h3>

<p>模擬一個 HTLC 的完整生命週期：生成 preimage 和 payment_hash，構建 HTLC 腳本，模擬成功領取路徑的見證構造，模擬超時退款路徑的見證構造。比較兩種路徑的不同要求。</p>

<hr />

<h2 id="十小結">十、小結</h2>

<p>本篇我們深入探討了比特幣的時間鎖機制：</p>

<p><strong>nLocktime</strong> 是最早的時間鎖，在交易層級工作，但可以被持有私鑰者繞過。它主要用於「延遲」交易廣播，而非強制執行時間條件。</p>

<p><strong>CLTV</strong> 將絕對時間鎖帶入腳本層級，透過共識規則強制執行，無法繞過。它適用於需要指定具體日期或區塊高度的場景，如遺產規劃、分期解鎖等。</p>

<p><strong>nSequence</strong> 經過 BIP 68 重新定義，實現了交易層級的相對時間鎖。它指定從 UTXO 確認開始必須等待的時間，是支付通道的基礎。</p>

<p><strong>CSV</strong> 是相對時間鎖的腳本版本，同樣透過共識強制執行。它是閃電網路中「挑戰期」機制的核心，確保欺詐行為有時間被發現和懲罰。</p>

<p><strong>HTLC</strong> 結合了哈希鎖和時間鎖，創造了條件支付的能力。它是原子交換和閃電網路多跳支付的基礎，確保「要麼全部成功，要麼全部退款」的原子性。</p>

<p>時間鎖從單純的「延遲」功能，演進成構建複雜金融協議的核心原語。理解這些機制如何工作，是掌握比特幣智能合約的關鍵一步。</p>

<hr />

<h2 id="下一篇預告">下一篇預告</h2>

<p>在本系列的最後一篇文章中，我們將探討 Bitcoin Script 的進階應用，包括 Vault 設計模式、Discreet Log Contracts（DLC）的基礎概念、以及腳本優化技巧。我們還會展望比特幣腳本未來可能的發展方向，如 OP_CAT、OP_CTV 等提案。</p>

<hr />

<h2 id="參考資料">參考資料</h2>

<ul>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki">BIP 65: OP_CHECKLOCKTIMEVERIFY</a> - CLTV 的原始規格</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki">BIP 68: Relative Lock-time</a> - nSequence 相對時間鎖語義</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki">BIP 112: OP_CHECKSEQUENCEVERIFY</a> - CSV 的原始規格</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0113.mediawiki">BIP 113: Median Time-past</a> - MTP 時間計算規則</li>
  <li><a href="https://github.com/lightning/bolts/blob/master/03-transactions.md">BOLT #3: Transactions</a> - 閃電網路交易格式</li>
  <li><a href="https://github.com/lightning/bolts/blob/master/05-onchain.md">BOLT #5: On-chain Handling</a> - 閃電網路鏈上處理建議</li>
  <li><a href="https://bitcoinops.org/en/topics/timelocks/">Bitcoin Optech: Timelocks</a> - 時間鎖主題深入解析</li>
</ul>]]></content><author><name>Cypherpunks Taiwan</name></author><category term="技術教學" /><category term="bitcoin" /><category term="development" /><category term="bitcoin" /><category term="script" /><category term="timelock" /><category term="htlc" /><category term="lightning" /><summary type="html"><![CDATA[Bitcoin Script 系列第五篇，深入解析時間鎖機制，包括 nLocktime、CLTV、nSequence、CSV，以及 HTLC 的實際應用。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" /><media:content medium="image" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Bitcoin Script 實戰教學（四）：多重簽名完全指南</title><link href="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/development/2025/03/18/bitcoin-script-tutorial-4-multisig/" rel="alternate" type="text/html" title="Bitcoin Script 實戰教學（四）：多重簽名完全指南" /><published>2025-03-18T00:00:00+00:00</published><updated>2025-03-18T00:00:00+00:00</updated><id>https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/development/2025/03/18/bitcoin-script-tutorial-4-multisig</id><content type="html" xml:base="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/development/2025/03/18/bitcoin-script-tutorial-4-multisig/"><![CDATA[<h2 id="系列導言">系列導言</h2>

<p>這是「Bitcoin Script 實戰教學」系列的第四篇。本篇將全面探討多重簽名的各種實現方式，從傳統的 OP_CHECKMULTISIG 到現代的 MuSig2。多重簽名是比特幣腳本系統最重要的應用之一，它將比特幣的安全性從單一密鑰提升到分散式信任模型，為個人資產保護、企業財務管理、以及複雜的智能合約奠定了基礎。</p>

<p><strong>系列文章：</strong></p>
<ol>
  <li><a href="/bitcoin/development/2025/03/15/bitcoin-script-tutorial-1-fundamentals/">基礎概念與操作碼</a></li>
  <li><a href="/bitcoin/development/2025/03/16/bitcoin-script-tutorial-2-standard-scripts/">標準腳本類型詳解</a></li>
  <li><a href="/bitcoin/development/2025/03/17/bitcoin-script-tutorial-3-segwit-taproot/">SegWit 與 Taproot</a></li>
  <li><strong>多重簽名完全指南</strong>（本篇）</li>
  <li>時間鎖機制</li>
  <li>進階應用與智能合約</li>
</ol>

<hr />

<h2 id="一理解多重簽名">一、理解多重簽名</h2>

<h3 id="11-從單簽名到多重簽名">1.1 從單簽名到多重簽名</h3>

<p>在傳統的比特幣交易中，一個私鑰對應一個地址，擁有私鑰就能完全控制該地址下的所有資金。這種模型簡單直接，但也有明顯的風險：私鑰一旦丟失或被盜，資金就永久無法恢復或被竊取。</p>

<p>多重簽名（Multisignature，簡稱 Multisig）是一種需要多個私鑰授權才能花費資金的機制。它通常用 m-of-n 的形式表示，其中 n 是參與的公鑰總數，m 是需要的最少簽名數量。例如，2-of-3 多重簽名意味著有三個人各持有一把私鑰，但只需要其中任意兩人簽名就能花費資金。</p>

<p>這個看似簡單的概念帶來了深遠的影響。它讓我們能夠設計出容錯性更強的資產保管方案，建立需要多方同意的企業財務系統，甚至構建去中心化的協議層應用。</p>

<h3 id="12-為什麼需要多重簽名">1.2 為什麼需要多重簽名？</h3>

<p>多重簽名解決了加密貨幣管理中的幾個核心問題。</p>

<p>首先是安全性問題。即使你的一把私鑰被盜，攻擊者也無法動用資金，因為他們還需要獲取其他密鑰。這種「冗餘安全」的概念與傳統銀行保險箱需要兩把鑰匙的原理類似，但在數位領域實現得更加優雅。</p>

<p>其次是可恢復性問題。在 2-of-3 配置中，即使你丟失了一把私鑰，仍然可以使用剩餘的兩把密鑰恢復對資金的控制。這大大降低了「因為忘記密碼而永久失去比特幣」這類悲劇發生的可能性。</p>

<p>第三是治理問題。對於企業或組織來說，重大財務決策不應該由單一個人控制。多重簽名可以確保資金的動用需要多個授權人的同意，這與傳統公司治理中的多人簽核流程相呼應。</p>

<p>最後是信任最小化。在涉及多方的交易場景中，例如託管服務或去中心化交易，多重簽名可以設計出不需要完全信任任何單一方的解決方案。經典的 2-of-3 託管就是一個例子：買家、賣家、仲裁者各持一把密鑰，正常情況下買賣雙方完成交易，只有發生爭議時才需要仲裁者介入。</p>

<h3 id="13-多重簽名的歷史演進">1.3 多重簽名的歷史演進</h3>

<p>比特幣的多重簽名經歷了三個主要的發展階段，每個階段都帶來了效率和隱私的顯著提升。</p>

<p>第一階段是 2012 年隨 BIP 16 引入的 P2SH 多重簽名。這是最早被廣泛採用的標準化多簽方案，使用 OP_CHECKMULTISIG 操作碼在腳本層面驗證多個簽名。雖然這種方法功能完善，但它需要在鏈上暴露所有公鑰和所需的所有簽名，導致較大的交易體積和較高的費用。</p>

<p>第二階段是 2017 年 SegWit 帶來的 P2WSH 多重簽名。這種方式在功能上與 P2SH 類似，但由於簽名數據被放入 witness 區域並享受費用折扣，實際成本下降了約 35%。同時，使用 SHA256 替代 HASH160 也提升了密碼學安全性。</p>

<p>第三階段是 2021 年 Taproot 啟動後帶來的革命性變化。透過 Schnorr 簽名的線性特性，多個簽名現在可以被聚合成一個單一簽名，在鏈上看起來與普通的單簽名交易完全相同。這不僅大幅降低了費用，更重要的是提供了前所未有的隱私保護——外部觀察者無法判斷一筆交易是單人控制還是需要多方授權。</p>

<hr />

<h2 id="二傳統多重簽名詳解">二、傳統多重簽名詳解</h2>

<h3 id="21-op_checkmultisig-的工作原理">2.1 OP_CHECKMULTISIG 的工作原理</h3>

<p>OP_CHECKMULTISIG 是比特幣最早支援多重簽名的操作碼。它的設計相當直接：從堆疊中取出一組公鑰和一組簽名，然後驗證是否有足夠數量的有效簽名。</p>

<p>一個 2-of-3 多重簽名的贖回腳本（Redeem Script）看起來像這樣：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>OP_2 &lt;pubkey1&gt; &lt;pubkey2&gt; &lt;pubkey3&gt; OP_3 OP_CHECKMULTISIG
</pre></td></tr></tbody></table></code></pre></div></div>

<p>這個腳本的含義是：這裡有三個公鑰，需要其中兩個對應的有效簽名才能花費這筆資金。OP_2 指定需要的簽名數量，OP_3 指定公鑰的總數，而 OP_CHECKMULTISIG 執行實際的驗證邏輯。</p>

<p>當花費這筆資金時，ScriptSig（解鎖腳本）需要提供簽名和原始的贖回腳本：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>OP_0 &lt;signature1&gt; &lt;signature2&gt; &lt;redeemScript&gt;
</pre></td></tr></tbody></table></code></pre></div></div>

<p>這裡的 OP_0 是一個看起來奇怪但必需的元素，稍後我們會解釋它的存在原因。</p>

<h3 id="22-臭名昭著的-off-by-one-bug">2.2 臭名昭著的 off-by-one Bug</h3>

<p>OP_CHECKMULTISIG 有一個從比特幣誕生之初就存在的程式錯誤：它會從堆疊中多彈出一個元素。這個被稱為「off-by-one bug」的問題源於原始程式碼中的一個索引錯誤。</p>

<p>具體來說，在驗證完所有簽名之後，操作碼會嘗試彈出一個額外的元素。如果堆疊中沒有這個元素，腳本就會失敗。更糟糕的是，彈出的這個元素被完全忽略——它不參與任何驗證邏輯。</p>

<p>為了讓腳本正確執行，使用者必須在簽名之前放置一個「啞」元素。這個元素可以是任何值，因為它反正會被忽略。慣例上使用 OP_0（空位元組），這就是為什麼你會在所有多簽 ScriptSig 的開頭看到這個看似莫名其妙的零值。</p>

<p>為什麼這個 bug 沒有被修復？因為比特幣的共識規則是神聖不可侵犯的。任何改變腳本驗證行為的修改都可能導致原本有效的交易變得無效，從而引發網路分裂。這個 bug 作為「歷史遺產」被永久保留了下來。</p>

<p>不過，BIP 147 引入了一個軟分叉規則（NULLDUMMY），要求這個啞元素必須是空位元組 OP_0。這防止了一類潛在的交易延展性攻擊，因為在此之前，這個被忽略的元素可以被第三方任意修改而不影響交易有效性。</p>

<h3 id="23-簽名順序的限制">2.3 簽名順序的限制</h3>

<p>OP_CHECKMULTISIG 還有一個重要的限制：簽名必須按照公鑰在腳本中出現的順序排列。這意味著如果你有公鑰 A、B、C，而且只有 A 和 C 簽名了，你不能提供簽名順序為 C、A——必須是 A、C。</p>

<p>這個設計選擇是為了簡化驗證邏輯。操作碼按順序遍歷公鑰，對每個簽名嘗試用下一個未匹配的公鑰驗證。一旦匹配成功就移動到下一個簽名，如果到達公鑰列表末尾而還有未驗證的簽名，則整個驗證失敗。</p>

<p>這個約束給實際應用帶來了一些不便。在收集簽名時，你需要知道每個簽名者對應哪個公鑰，並按正確順序排列。這也是為什麼後來的 Tapscript 採用了不同的設計。</p>

<h3 id="24-建立-p2sh-多重簽名地址">2.4 建立 P2SH 多重簽名地址</h3>

<p>讓我們看一個完整的建立 2-of-3 P2SH 多重簽名地址的過程。</p>

<p>首先，需要收集所有參與者的公鑰。在實踐中，這通常意味著每個參與者用自己的錢包軟體或硬體錢包生成一個公鑰並分享給協調者。重要的是只分享公鑰，絕不分享私鑰。</p>

<p>接下來，按照字典序對公鑰進行排序。這一步確保無論誰來建立地址，使用相同的公鑰集合都會得到相同的地址。沒有這個標準化步驟，不同的排序會產生不同的地址，造成混亂。</p>

<p>然後，構建贖回腳本並計算其 HASH160 雜湊值。這個 20 位元組的雜湊值就是 P2SH 地址的核心部分。最後，加上版本前綴（主網為 0x05）並進行 Base58Check 編碼，就得到了一個以「3」開頭的 P2SH 地址。</p>

<p>每個參與者都需要保存完整的贖回腳本，因為花費時需要揭示它。實際上，大多數多簽錢包會同時記錄所有公鑰，並在需要時重新生成腳本，以確保一致性。</p>

<hr />

<h2 id="三segwit-多重簽名">三、SegWit 多重簽名</h2>

<h3 id="31-p2wsh-的優勢">3.1 P2WSH 的優勢</h3>

<p>SegWit 為多重簽名帶來了實質性的改進，主要體現在三個方面。</p>

<p>費用節省是最直觀的好處。在 SegWit 中，witness 數據（包括簽名）只計算四分之一的區塊權重。對於簽名佔據大部分體積的多簽交易來說，這意味著顯著的成本下降。一個 2-of-3 的 P2WSH 交易相比等價的 P2SH 交易，可以節省約 35% 的費用。隨著簽名數量增加，節省的比例還會更高。</p>

<p>安全性也得到了提升。P2SH 使用 HASH160（RIPEMD160(SHA256(x))），產生 20 位元組的雜湊值，提供約 80 位元的碰撞抵抗。P2WSH 則直接使用 SHA256，產生 32 位元組的雜湊值，提供 128 位元的碰撞抵抗。雖然 80 位元在目前看來已經足夠安全，但對於可能存在數十年的大額資產來說，更高的安全邊際是值得的。</p>

<p>此外，SegWit 重新設計的簽名雜湊演算法（BIP 143）解決了傳統交易的二次雜湊問題，使得驗證大型多簽交易的計算成本更加可控。</p>

<h3 id="32-p2wsh-的結構">3.2 P2WSH 的結構</h3>

<p>P2WSH 多重簽名在概念上與 P2SH 類似，但結構有所不同。</p>

<p>ScriptPubKey 變成了標準的 SegWit v0 格式：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>OP_0 &lt;32-byte-SHA256-of-witnessScript&gt;
</pre></td></tr></tbody></table></code></pre></div></div>

<p>ScriptSig 現在是空的——所有解鎖數據都移到了 witness 區域。</p>

<p>witness 結構與 P2SH 的 ScriptSig 類似：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>&lt;empty-dummy&gt;
&lt;signature1&gt;
&lt;signature2&gt;
&lt;witnessScript&gt;
</pre></td></tr></tbody></table></code></pre></div></div>

<p>witnessScript 的內容與傳統的 redeemScript 完全相同：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>OP_2 &lt;pubkey1&gt; &lt;pubkey2&gt; &lt;pubkey3&gt; OP_3 OP_CHECKMULTISIG
</pre></td></tr></tbody></table></code></pre></div></div>

<p>這種設計讓從 P2SH 遷移到 P2WSH 變得相對簡單：核心邏輯不變，只是封裝方式改變了。</p>

<h3 id="33-巢狀-segwitp2sh-p2wsh">3.3 巢狀 SegWit（P2SH-P2WSH）</h3>

<p>在 SegWit 早期採用階段，許多交易所和服務商尚未支援原生 SegWit 地址。為了兼容這些服務，出現了一種過渡方案：將 P2WSH 包裝在 P2SH 內部。</p>

<p>這種巢狀結構的 ScriptPubKey 是標準的 P2SH 格式（地址以「3」開頭），但 redeemScript 只包含一個 SegWit witness program：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>OP_0 &lt;32-byte-SHA256-of-witnessScript&gt;
</pre></td></tr></tbody></table></code></pre></div></div>

<p>花費時，ScriptSig 只包含這個 redeemScript，而實際的簽名數據仍然放在 witness 區域。</p>

<p>這種方案提供了部分 SegWit 的費用優惠（因為簽名仍在 witness 中），同時保持了與舊系統的兼容性。隨著原生 SegWit 支援越來越普及，這種過渡格式的使用正在減少。</p>

<hr />

<h2 id="四taproot-革新多重簽名">四、Taproot 革新多重簽名</h2>

<h3 id="41-兩種路徑的設計哲學">4.1 兩種路徑的設計哲學</h3>

<p>Taproot 為多重簽名提供了兩種截然不同的實現方式，每種都有其獨特的優勢和適用場景。</p>

<p>Key Path 使用 Schnorr 簽名的聚合特性，將多個簽名者的公鑰和簽名合併成一個。在鏈上，這看起來就像普通的單簽名交易——只有 64 位元組的簽名，沒有公鑰列表，沒有任何多簽的痕跡。這提供了最佳的效率和隱私，但要求所有簽名者都必須參與（n-of-n）。</p>

<p>Script Path 使用 Tapscript 中的 OP_CHECKSIGADD 操作碼，允許 m-of-n 的任意閾值配置。雖然這種方式需要揭示腳本並在鏈上可見多個公鑰和簽名，但它提供了更大的靈活性——不需要所有人都在線，只需要達到閾值即可。</p>

<p>在實踐中，這兩種方式通常會結合使用。Key Path 設置為所有參與者的聚合公鑰，作為最常見情況（共識達成）的高效路徑。Script Path 中包含各種備用方案，例如子集閾值簽名、時間鎖保護的緊急恢復等，作為異常情況的處理機制。</p>

<h3 id="42-musig2聚合簽名的實現">4.2 MuSig2：聚合簽名的實現</h3>

<p>MuSig2 是目前比特幣採用的 Schnorr 多簽聚合協議，由 BIP 327 標準化。它允許 n 個參與者共同產生一個看起來像單簽名的聚合簽名。</p>

<p>協議分為兩個主要階段。第一階段是密鑰聚合：每個參與者提供自己的公鑰，這些公鑰通過特定的演算法組合成一個聚合公鑰。這個聚合公鑰就是 Taproot 輸出的內部公鑰（或經過調整後的輸出公鑰）。</p>

<p>密鑰聚合不是簡單的相加。為了防止一類稱為「流氓密鑰攻擊」的安全問題，每個公鑰在相加前會乘以一個基於所有公鑰的係數。這確保了沒有任何一方可以惡意選擇自己的公鑰來操控聚合結果。</p>

<p>第二階段是簽名生成。MuSig2 需要兩輪通訊。在第一輪，每個簽名者生成一對隨機 nonce 並分享公開部分。在第二輪，每個簽名者計算自己的部分簽名並分享。最終，這些部分簽名被聚合成一個完整的 Schnorr 簽名。</p>

<p>MuSig2 相比其前身 MuSig 的主要改進是將互動輪次從三輪減少到兩輪，並且允許 nonce 預生成，這大大提升了實用性，特別是對於硬體錢包等離線簽名設備。</p>

<h3 id="43-nonce-安全性的關鍵性">4.3 Nonce 安全性的關鍵性</h3>

<p>MuSig2 實現中最需要謹慎處理的是 nonce（隨機數）的管理。nonce 重用可以導致私鑰洩露，這不是理論上的威脅——歷史上確實發生過因為 nonce 問題而損失大量比特幣的事件。</p>

<p>每次簽名必須使用全新的隨機 nonce。為了在這個要求和離線簽名的便利性之間取得平衡，MuSig2 允許預先生成一批 nonce 並安全存儲。但這帶來了狀態管理的複雜性：系統必須確保每個 nonce 只使用一次，即使在崩潰恢復的情況下也是如此。</p>

<p>對於硬體錢包這類安全敏感的環境，通常會採用確定性 nonce 生成方案，基於私鑰和訊息派生 nonce，從而避免狀態管理的問題。但這需要謹慎的密碼學設計，不正確的實現可能引入新的漏洞。</p>

<h3 id="44-tapscript-中的-op_checksigadd">4.4 Tapscript 中的 OP_CHECKSIGADD</h3>

<p>當 n-of-n 聚合不適用時（比如只需要 m-of-n 的子集），Taproot 提供了 Script Path 作為替代方案。Tapscript 引入了 OP_CHECKSIGADD 操作碼，這是傳統 OP_CHECKMULTISIG 的現代化替代品。</p>

<p>OP_CHECKSIGADD 的設計更加簡潔和靈活。它從堆疊取三個元素：一個簽名、一個累加器值、和一個公鑰。如果簽名有效，累加器加一；如果簽名為空（表示該簽名者沒有簽名），累加器不變。最後，累加器被推回堆疊。</p>

<p>一個 2-of-3 的 Tapscript 看起來像這樣：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>&lt;pubkey1&gt; OP_CHECKSIG
&lt;pubkey2&gt; OP_CHECKSIGADD
&lt;pubkey3&gt; OP_CHECKSIGADD
2 OP_NUMEQUAL
</pre></td></tr></tbody></table></code></pre></div></div>

<p>執行時，堆疊上會累積有效簽名的數量，最後與所需閾值比較。</p>

<p>這種設計有幾個優點。首先，它沒有 off-by-one bug，不需要那個莫名其妙的啞元素。其次，簽名順序是靈活的——不需要按公鑰順序排列，未簽名的位置只需提供空值。第三，它支援高效的批量驗證，因為所有簽名可以被收集起來一次性驗證。</p>

<hr />

<h2 id="五閾值簽名更進一步">五、閾值簽名：更進一步</h2>

<h3 id="51-從多重簽名到閾值簽名">5.1 從多重簽名到閾值簽名</h3>

<p>多重簽名和閾值簽名都解決「需要多方授權」的問題，但它們在技術實現上有根本區別。</p>

<p>傳統多重簽名（包括 OP_CHECKMULTISIG 和 OP_CHECKSIGADD）在腳本層面工作：每個參與者獨立產生自己的簽名，腳本驗證時檢查是否有足夠數量的有效簽名。這意味著每個簽名都是獨立存在的，鏈上可以看到有多少人參與、需要多少人簽名。</p>

<p>閾值簽名則在密碼學層面工作：多個參與者協作產生一個單一的聚合簽名，這個簽名與普通的單人簽名無法區分。沒有人（包括區塊鏈分析者）能夠知道這筆交易實際上需要多方授權。</p>

<p>MuSig2 可以看作是 n-of-n 的閾值簽名——所有人都必須參與才能產生有效簽名。但對於 m-of-n（其中 m &lt; n）的情況，需要更複雜的協議。</p>

<h3 id="52-frost-協議">5.2 FROST 協議</h3>

<p>FROST（Flexible Round-Optimized Schnorr Threshold signatures）是目前最有前景的閾值簽名方案之一。它允許任意 t-of-n 配置，同時保持只需兩輪通訊的效率。</p>

<p>FROST 的工作分為兩個階段。密鑰生成階段（DKG，Distributed Key Generation）中，參與者協作生成一個共享的公鑰和各自的私鑰份額。這個過程使用密碼學技術（如 Feldman’s VSS）確保沒有任何人知道完整的私鑰，私鑰只存在於數學上的「虛擬」形式。</p>

<p>簽名階段中，任意 t 個參與者可以合作產生有效簽名。他們各自用私鑰份額計算簽名份額，這些份額被聚合成最終簽名。只需要 t 個人在線，其他人可以完全離線或甚至暫時失聯。</p>

<p>FROST 的優勢在於其靈活性。考慮一個 3-of-5 的企業金庫：五位高管各持有一份私鑰份額，任意三人可以授權支出。即使兩人長期出差或離職，公司仍然可以正常運營。而在鏈上，這與個人錢包的交易看起來完全一樣。</p>

<h3 id="53-實際應用考量">5.3 實際應用考量</h3>

<p>閾值簽名雖然強大，但在實際部署中需要考慮幾個問題。</p>

<p>首先是密鑰生成的複雜性。DKG 協議需要參與者之間的同步通訊，這在跨時區、跨網路環境的場景中可能成為挑戰。一些實現選擇使用可信第三方來簡化這個過程，但這犧牲了部分去中心化特性。</p>

<p>其次是門檻的選擇。門檻過低會降低安全性（攻擊者只需控制較少的份額），門檻過高會降低可用性（需要更多人同時在線）。不同場景需要根據具體的安全和可用性需求做出權衡。</p>

<p>最後是實現成熟度。相比經過十多年生產驗證的 OP_CHECKMULTISIG，閾值簽名協議相對較新。雖然密碼學原理已經被充分研究，但實現中的細節問題可能需要時間來發現和解決。</p>

<hr />

<h2 id="六psbt多方協作的橋樑">六、PSBT：多方協作的橋樑</h2>

<h3 id="61-為什麼需要-psbt">6.1 為什麼需要 PSBT</h3>

<p>在多重簽名的工作流程中，一個核心挑戰是如何在不同的簽名者之間傳遞交易資訊。每個簽名者可能使用不同的錢包軟體或硬體設備，可能處於離線狀態，甚至可能位於世界的不同角落。</p>

<p>PSBT（Partially Signed Bitcoin Transaction，部分簽名比特幣交易）是 BIP 174 定義的標準格式，專門解決這個問題。它是一種可以攜帶交易所有相關資訊的容器格式，包括未簽名的交易本身、每個輸入的詳細資訊（如 UTXO 數據、腳本、已收集的簽名等）、以及輸出資訊。</p>

<p>PSBT 的設計理念是讓不同的軟體和設備可以各自完成自己能做的部分，然後傳遞給下一個參與者。一個創建者可以構建基本交易結構，更新者可以添加輸入輸出的詳細資訊，多個簽名者可以獨立添加自己的簽名，組合者可以將不同來源的簽名合併，完成者可以構建最終的交易格式，提取者可以得到可廣播的原始交易。</p>

<h3 id="62-psbt-的結構">6.2 PSBT 的結構</h3>

<p>PSBT 使用鍵值對格式存儲資料，這種格式具有良好的擴展性——新的欄位類型可以被添加而不破壞舊實現的兼容性。</p>

<p>全局數據包含交易本身（未簽名形式）和 PSBT 版本資訊。每個輸入的數據可能包含：非 witness UTXO（完整的前序交易）、witness UTXO（只有相關輸出的資訊）、贖回腳本、witness 腳本、各種衍生路徑資訊、以及部分簽名的集合。每個輸出的數據通常包含金額和腳本資訊。</p>

<p>這種豐富的資訊結構讓簽名設備（特別是硬體錢包）可以驗證交易的所有關鍵方面：輸入是否存在、輸出是否正確、找零是否合理、費用是否過高。這些驗證對於防止簽名者被欺騙至關重要。</p>

<h3 id="63-多簽工作流程">6.3 多簽工作流程</h3>

<p>讓我們看一個使用 PSBT 的 2-of-3 多簽完整工作流程。</p>

<p>協調者首先創建一個 PSBT，包含要花費的 UTXO 和目標輸出。這個初始 PSBT 還沒有任何簽名，但包含了足夠的資訊讓簽名者理解這筆交易在做什麼。</p>

<p>PSBT 被發送給第一個簽名者（可能通過電子郵件、即時通訊、或任何檔案傳輸方式）。簽名者的錢包軟體或硬體解析 PSBT，顯示交易詳情供用戶確認，然後用自己的私鑰簽名並將部分簽名添加到 PSBT 中。</p>

<p>第一個簽名者返回更新後的 PSBT，然後發送給第二個簽名者。第二個簽名者重複同樣的過程，添加自己的簽名。</p>

<p>現在 PSBT 包含了足夠的簽名（2 個）。組合者（可能是協調者本人）驗證所有簽名的有效性，然後「完成」PSBT——將簽名和腳本組合成最終的 scriptSig 或 witness 格式。</p>

<p>最後，從完成的 PSBT 中提取原始交易並廣播到比特幣網路。</p>

<p>這個流程的優雅之處在於它的模組化：每個參與者只需要關心自己的步驟，不需要了解其他人使用什麼軟體或在哪裡簽名。</p>

<hr />

<h2 id="七安全最佳實踐">七、安全最佳實踐</h2>

<h3 id="71-密鑰分散原則">7.1 密鑰分散原則</h3>

<p>多重簽名的安全性建立在密鑰分散的基礎上。如果所有密鑰都存儲在同一個地方，那麼多簽就失去了意義——攻擊者只需要入侵一個位置就能獲得所有密鑰。</p>

<p>地理分散是第一道防線。不同的密鑰應該存儲在不同的物理位置，這樣即使一個位置被入侵、被沒收、或者遭受自然災害，其他密鑰仍然安全。</p>

<p>設備多樣化是另一個重要考量。使用不同品牌、不同型號的硬體錢包可以防止某個特定設備的漏洞影響整個系統的安全。如果所有密鑰都存儲在同一品牌的硬體錢包上，那麼該品牌的安全漏洞就可能成為致命弱點。</p>

<p>人員分離在企業場景中尤為重要。不同的密鑰應該由不同的人控制，這樣可以防止單個人的背叛或失職影響整體安全。同時，這也為組織提供了職責分離的保障。</p>

<h3 id="72-備份策略">7.2 備份策略</h3>

<p>多簽錢包的備份比單簽錢包更複雜，因為需要考慮多個組成部分。</p>

<p>每個私鑰（或助記詞）需要獨立備份，並且備份應該與原始存儲位置分開。備份的安全級別應該與原始密鑰相當——如果原始密鑰在硬體錢包中受到保護，備份也應該被妥善加密或物理保護。</p>

<p>除了私鑰本身，還需要備份多簽配置資訊：所有公鑰的列表、閾值設置（m-of-n）、腳本類型（P2SH/P2WSH/P2TR）、衍生路徑等。沒有這些資訊，即使擁有所有私鑰也無法重建錢包。</p>

<p>一個常見的做法是創建一個「設置描述符」，這是一種標準化格式，完整描述了多簽錢包的結構。這個描述符不包含私鑰資訊，可以相對安全地存儲在多個位置。</p>

<h3 id="73-測試恢復流程">7.3 測試恢復流程</h3>

<p>備份的價值只有在恢復時才能體現。因此，定期測試恢復流程是安全實踐的重要組成部分。</p>

<p>在小額測試資金的情況下，嘗試完整的恢復過程：使用備份恢復每個密鑰、重建多簽錢包配置、創建並簽名測試交易。這個過程可以發現備份中的問題，也讓相關人員熟悉緊急情況下的操作流程。</p>

<p>對於大額資產，應該定期（例如每年一次）驗證所有備份的完整性和可訪問性，但不需要每次都實際恢復——只需確認備份存在、可讀、且內容正確即可。</p>

<hr />

<h2 id="八實作練習">八、實作練習</h2>

<h3 id="練習-1建立-2-of-3-p2wsh-多簽地址">練習 1：建立 2-of-3 P2WSH 多簽地址</h3>

<p>給定三個公鑰，手動計算 2-of-3 P2WSH 多簽地址。步驟包括：按字典序對公鑰排序、構建 witness script、計算 SHA256 雜湊、用 Bech32 編碼生成地址。嘗試用你熟悉的程式語言實現這個過程。</p>

<h3 id="練習-2模擬-psbt-工作流程">練習 2：模擬 PSBT 工作流程</h3>

<p>設計一個簡單的多簽交易場景，包括：創建初始 PSBT、第一個簽名者添加簽名、第二個簽名者添加簽名、組合並完成 PSBT、提取最終交易。可以使用 Bitcoin Core 的 RPC 命令或任何支援 PSBT 的程式庫。</p>

<h3 id="練習-3比較不同多簽方案的效率">練習 3：比較不同多簽方案的效率</h3>

<p>計算相同 2-of-3 配置在不同方案下的交易大小：P2SH、P2WSH、Taproot Script Path、Taproot Key Path（假設使用 MuSig2）。分析每種方案的空間效率和隱私特性。</p>

<hr />

<h2 id="九小結">九、小結</h2>

<p>本篇我們深入探討了比特幣多重簽名的各個層面：</p>

<p><strong>傳統多簽</strong>使用 OP_CHECKMULTISIG，是最早的標準化方案。雖然有 off-by-one bug 和簽名順序限制等歷史包袱，但它經過了十多年的生產驗證，仍然是可靠的選擇。</p>

<p><strong>SegWit 多簽</strong>（P2WSH）提供了約 35% 的費用節省和更強的雜湊安全性，是目前企業應用的主流選擇。</p>

<p><strong>Taproot 多簽</strong>帶來了革命性的改進。透過 MuSig2 的 Key Path，n-of-n 多簽可以達到與單簽名相同的效率和隱私；透過 Tapscript 的 OP_CHECKSIGADD，m-of-n 配置獲得了更好的靈活性和批量驗證支援。</p>

<p><strong>閾值簽名</strong>如 FROST 代表了下一代多方安全技術，可以在密碼學層面實現對外完全隱藏的多方授權。</p>

<p><strong>PSBT</strong> 作為多簽工作流程的標準化格式，解決了不同軟體和設備之間的互操作性問題。</p>

<p>選擇哪種多簽方案取決於具體需求：需要最高隱私和效率，選擇 Taproot Key Path；需要靈活的閾值配置，選擇 Taproot Script Path 或傳統方案；需要與舊系統兼容，選擇 P2WSH 或 P2SH。</p>

<hr />

<h2 id="下一篇預告">下一篇預告</h2>

<p>在下一篇文章中，我們將探討時間鎖機制——比特幣腳本中控制「何時」可以花費資金的強大工具。我們會深入了解絕對時間鎖（nLocktime、OP_CHECKLOCKTIMEVERIFY）和相對時間鎖（nSequence、OP_CHECKSEQUENCEVERIFY），並探討它們在閃電網路、原子交換、遺產規劃等場景中的應用。</p>

<hr />

<h2 id="參考資料">參考資料</h2>

<ul>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0011.mediawiki">BIP 11: M-of-N 標準交易</a> - 最早的多簽標準</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki">BIP 16: P2SH</a> - Pay to Script Hash 規範</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0147.mediawiki">BIP 147: NULLDUMMY</a> - 修復多簽延展性</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki">BIP 174: PSBT</a> - 部分簽名交易格式</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki">BIP 327: MuSig2</a> - Schnorr 多簽聚合協議</li>
  <li><a href="https://bitcoinops.org/en/topics/multisignature/">Bitcoin Optech: Multisig</a> - 多簽主題深入解析</li>
  <li><a href="https://eprint.iacr.org/2020/852">FROST 論文</a> - 閾值簽名的學術基礎</li>
</ul>]]></content><author><name>Cypherpunks Taiwan</name></author><category term="技術教學" /><category term="bitcoin" /><category term="development" /><category term="bitcoin" /><category term="script" /><category term="multisig" /><category term="musig" /><category term="threshold" /><summary type="html"><![CDATA[Bitcoin Script 系列第四篇，深入解析多重簽名的各種實現方式，包括傳統 P2SH、SegWit、Taproot MuSig2，以及閾值簽名方案。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" /><media:content medium="image" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Bitcoin Script 實戰教學（三）：SegWit 與 Taproot 深入解析</title><link href="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/development/2025/03/17/bitcoin-script-tutorial-3-segwit-taproot/" rel="alternate" type="text/html" title="Bitcoin Script 實戰教學（三）：SegWit 與 Taproot 深入解析" /><published>2025-03-17T00:00:00+00:00</published><updated>2025-03-17T00:00:00+00:00</updated><id>https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/development/2025/03/17/bitcoin-script-tutorial-3-segwit-taproot</id><content type="html" xml:base="https://cypherpunks-core.github.io/%E6%8A%80%E8%A1%93%E6%95%99%E5%AD%B8/bitcoin/development/2025/03/17/bitcoin-script-tutorial-3-segwit-taproot/"><![CDATA[<h2 id="系列導言">系列導言</h2>

<p>這是「Bitcoin Script 實戰教學」系列的第三篇。本篇將深入探討 SegWit 和 Taproot 這兩個重要升級如何改變了比特幣腳本的執行方式。這兩個升級代表了比特幣協議歷史上最重要的技術革新，不僅解決了長期存在的技術問題，更為比特幣的未來發展奠定了基礎。</p>

<p><strong>系列文章：</strong></p>
<ol>
  <li><a href="/bitcoin/development/2025/03/15/bitcoin-script-tutorial-1-fundamentals/">基礎概念與操作碼</a></li>
  <li><a href="/bitcoin/development/2025/03/16/bitcoin-script-tutorial-2-standard-scripts/">標準腳本類型詳解</a></li>
  <li><strong>SegWit 與 Taproot 深入解析</strong>（本篇）</li>
  <li>多重簽名實現</li>
  <li>時間鎖機制</li>
  <li>進階應用與智能合約</li>
</ol>

<hr />

<h2 id="一segwit-深入解析">一、SegWit 深入解析</h2>

<h3 id="11-什麼是-segwit">1.1 什麼是 SegWit？</h3>

<p>SegWit 是 Segregated Witness（隔離見證）的縮寫，於 2017 年 8 月透過軟分叉方式在比特幣主網啟動。這個升級的核心思想非常簡單但影響深遠：將交易的簽名數據（witness）從交易主體中分離出來。</p>

<p>「見證」在密碼學和法律用語中都指的是「證明某事為真的證據」。在比特幣交易中，見證就是數位簽名——它證明交易發起者確實擁有花費這些比特幣的權限。在 SegWit 之前，這些簽名數據和交易的其他部分混合在一起；SegWit 則將它們「隔離」到一個獨立的區域。</p>

<p>這個看似簡單的架構改變，解決了困擾比特幣多年的幾個關鍵問題。</p>

<h3 id="12-交易延展性問題">1.2 交易延展性問題</h3>

<p>交易延展性（Transaction Malleability）是 SegWit 解決的最重要問題之一。要理解這個問題，我們需要先了解比特幣如何識別交易。</p>

<p>每筆比特幣交易都有一個唯一識別碼，稱為交易 ID（txid）。這個 ID 是透過對整個交易進行雙重 SHA-256 雜湊運算得到的。問題在於，在傳統交易格式中，簽名數據也包含在這個雜湊計算中。</p>

<p>ECDSA 簽名有一個數學特性：對於同一個訊息，存在多個數學上等價的有效簽名。具體來說，如果 (r, s) 是一個有效簽名，那麼 (r, -s mod n) 也是有效的。這意味著任何人（不需要私鑰）都可以修改交易中的簽名，產生一個不同的 txid，但交易仍然有效。</p>

<p>這個問題對於一般的單筆交易影響不大，但對於依賴未確認交易 txid 的應用來說是災難性的。最典型的例子是閃電網路：它需要建立一連串相互依賴的交易，如果其中一筆交易的 txid 被修改，整個交易鏈就會斷裂。2014 年，Mt. Gox 交易所的部分損失就與交易延展性攻擊有關。</p>

<p>SegWit 的解決方案非常優雅：既然問題出在簽名會影響 txid，那就把簽名移到 txid 計算範圍之外。交易的核心數據（發送者、接收者、金額）用來計算 txid，而簽名則存放在獨立的 witness 區域。這樣，無論簽名如何被修改，txid 都保持不變。</p>

<h3 id="13-區塊容量提升">1.3 區塊容量提升</h3>

<p>比特幣區塊大小限制為 1MB，這個限制源自中本聰早期為防止垃圾交易攻擊所設置的參數。隨著比特幣使用量增加，這個限制開始制約交易處理能力，導致交易費用上升和確認時間延長。</p>

<p>直接增加區塊大小需要硬分叉，這意味著所有節點必須同時升級，否則網路會分裂。這在社區中引發了激烈爭論，最終導致了 2017 年的分叉事件。</p>

<p>SegWit 採用了一個巧妙的方法：引入「區塊權重」的概念來代替固定的區塊大小限制。新的規則規定區塊的最大權重為 4,000,000 權重單位（WU）。傳統交易數據的每個位元組計算為 4 WU，而 witness 數據的每個位元組只計算為 1 WU。</p>

<p>這個設計有幾個重要含義。首先，舊節點只看到傳統區塊數據，對它們來說區塊仍然在 1MB 限制內，保持了向後相容性。其次，由於 witness 數據佔用權重較少，使用 SegWit 的用戶可以享受較低的交易費用。實際上，這創造了一個經濟誘因，鼓勵用戶和錢包採用 SegWit。</p>

<p>在實踐中，完全使用 SegWit 交易的區塊可以達到約 2MB 到 2.3MB 的實際大小，有效地將區塊容量提升了一倍以上。</p>

<h3 id="14-二次雜湊問題">1.4 二次雜湊問題</h3>

<p>在傳統比特幣交易中，簽名雜湊的計算方式存在一個效能問題。對於一筆有 n 個輸入的交易，每個輸入的簽名都需要對整個交易進行雜湊，這導致總的計算量與 n² 成正比——這就是所謂的二次雜湊問題。</p>

<p>對於普通交易來說，這個問題不太明顯。但對於輸入數量很多的大型交易，計算成本會急劇上升。惡意攻擊者可以構造特殊的交易，讓節點花費大量時間驗證，這被稱為「交易驗證拒絕服務攻擊」。</p>

<p>SegWit 透過 BIP 143 重新設計了簽名雜湊演算法。新演算法預先計算可複用的中間值，使得簽名驗證的複雜度從 O(n²) 降低到 O(n)。這不僅提高了效能，也消除了一類潛在的攻擊向量。</p>

<h3 id="15-witness-程式結構">1.5 Witness 程式結構</h3>

<p>SegWit 引入了一種新的輸出類型，稱為 witness program（見證程式）。這種輸出的 ScriptPubKey 格式非常簡潔：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>&lt;version&gt; &lt;witness program&gt;
</pre></td></tr></tbody></table></code></pre></div></div>

<p>版本位元組表示 witness 程式的類型。版本 0 是目前最常用的，支援兩種標準腳本。當 witness program 的長度為 20 位元組時，它被解釋為 P2WPKH（Pay to Witness Public Key Hash），這是 SegWit 版本的 P2PKH。當長度為 32 位元組時，則是 P2WSH（Pay to Witness Script Hash），這是 SegWit 版本的 P2SH。</p>

<p>版本 1 是為 Taproot 保留的，使用 32 位元組的 witness program。版本 2 到 16 保留給未來的升級使用。這種版本化的設計確保了比特幣可以持續演進，同時保持向後相容性。</p>

<h3 id="16-segwit-交易結構">1.6 SegWit 交易結構</h3>

<p>傳統比特幣交易的結構相對簡單：版本號、輸入列表（包含 ScriptSig）、輸出列表、鎖定時間。SegWit 交易則在此基礎上增加了標記位元組和 witness 區域。</p>

<p>SegWit 交易在版本號之後插入兩個特殊位元組：標記（marker，值為 0x00）和旗標（flag，值為 0x01）。這兩個位元組告訴支援 SegWit 的節點，這是一筆 SegWit 交易。對於不支援 SegWit 的舊節點，這些位元組會被誤解為空的輸入列表，導致交易被視為無效而忽略——這正是軟分叉的巧妙之處。</p>

<p>witness 區域位於輸出列表和鎖定時間之間。每個輸入都有對應的 witness 堆疊，包含簽名和其他必要的驗證數據。這些數據不參與 txid 的計算，但會被驗證節點用來確認交易的有效性。</p>

<hr />

<h2 id="二taproot-升級概述">二、Taproot 升級概述</h2>

<h3 id="21-taproot-的歷史背景">2.1 Taproot 的歷史背景</h3>

<p>Taproot 於 2021 年 11 月在區塊高度 709,632 啟動，是自 SegWit 以來比特幣最重要的協議升級。這個升級由三個相互關聯的 BIP 組成：BIP 340 定義了 Schnorr 簽名方案，BIP 341 定義了 Taproot 的輸出和花費規則，BIP 342 定義了 Tapscript——一個改進版的腳本語言。</p>

<p>Taproot 的設計理念可以追溯到 2018 年，由比特幣核心開發者 Greg Maxwell 提出。他的洞見在於：大多數複雜的比特幣合約最終都會以所有參與方達成共識的方式結算，只有在出現爭議時才需要執行複雜的腳本邏輯。因此，如果我們能讓共識結算看起來像普通的單簽名交易，就能大幅提升隱私性和效率。</p>

<p>這個觀察引導了 Taproot 的核心設計：每個 Taproot 輸出都有兩種花費方式。Key path（密鑰路徑）允許透過一個聚合簽名直接花費，就像普通的單簽名交易一樣。Script path（腳本路徑）則允許揭示並執行預先承諾的腳本，用於處理非共識情況。</p>

<h3 id="22-為什麼選擇-schnorr-簽名">2.2 為什麼選擇 Schnorr 簽名？</h3>

<p>在深入 Taproot 之前，我們需要理解 Schnorr 簽名為什麼是這個升級的基石。比特幣從創始之初就使用 ECDSA（橢圓曲線數位簽名演算法），這是一個成熟且廣泛使用的簽名方案。然而，ECDSA 有一些固有的限制。</p>

<p>Schnorr 簽名由德國密碼學家 Claus Schnorr 在 1989 年發明，實際上比 ECDSA 更早。它沒有被比特幣最初採用的原因很簡單：當時 Schnorr 簽名受專利保護，而 ECDSA 是自由使用的。Schnorr 專利在 2008 年過期，但比特幣已經在 2009 年初以 ECDSA 的形式誕生了。</p>

<p>Schnorr 簽名相比 ECDSA 有幾個重要優勢。首先是數學結構的簡潔性。ECDSA 的安全性證明相當複雜，而 Schnorr 簽名有著優雅的數學證明，基於離散對數問題的困難性。這種簡潔性不僅便於分析，也使得實現更不容易出錯。</p>

<p>其次是線性特性。Schnorr 簽名具有加法同態性，這意味著多個簽名可以數學上相加成一個聚合簽名。對於多重簽名場景，這帶來了革命性的改進：n-of-n 多簽只需要一個 64 位元組的簽名，而不是 n 個獨立簽名。</p>

<p>第三是批量驗證效率。當需要驗證多個 Schnorr 簽名時，可以將它們合併成一個運算，大幅減少計算量。這對於區塊驗證特別有價值，因為每個區塊可能包含數千個簽名。</p>

<hr />

<h2 id="三schnorr-簽名詳解">三、Schnorr 簽名詳解</h2>

<h3 id="31-數學基礎">3.1 數學基礎</h3>

<p>要理解 Schnorr 簽名的工作原理，我們需要一些橢圓曲線密碼學的基礎知識。比特幣使用的是 secp256k1 曲線，這是一個定義在有限域上的橢圓曲線。曲線上有一個特殊的點 G，稱為生成點，任何私鑰 d 對應的公鑰都是 P = d × G（這裡的乘法是橢圓曲線上的純量乘法）。</p>

<p>Schnorr 簽名的安全性基於離散對數問題的困難性：給定 P 和 G，在計算上不可能推導出 d。這與 ECDSA 使用相同的數學難題，但 Schnorr 的構造方式更為直接。</p>

<table>
  <tbody>
    <tr>
      <td>簽名過程如下。簽名者首先選擇一個隨機數 k（稱為 nonce），計算 R = k × G。然後計算 e = H(R</td>
      <td> </td>
      <td>P</td>
      <td> </td>
      <td>m)，其中 H 是雜湊函數，m 是要簽名的訊息，</td>
      <td> </td>
      <td>表示連接。最後計算 s = k + e × d。簽名就是 (R, s) 這對值。</td>
    </tr>
  </tbody>
</table>

<p>驗證同樣簡潔：檢查 s × G 是否等於 R + e × P。這個等式成立的原因可以這樣理解：s × G = (k + e × d) × G = k × G + e × d × G = R + e × P。只有知道私鑰 d 的人才能計算出正確的 s 值。</p>

<h3 id="32-與-ecdsa-的具體差異">3.2 與 ECDSA 的具體差異</h3>

<p>在比特幣的實際應用中，Schnorr 和 ECDSA 簽名有幾個具體的差異。</p>

<p>首先是簽名格式。ECDSA 簽名使用 DER 編碼，長度通常在 71 到 72 位元組之間（取決於 r 和 s 值的具體位元數）。Schnorr 簽名固定為 64 位元組：R 點的 x 座標佔 32 位元組，s 值佔 32 位元組。</p>

<p>其次是公鑰格式。傳統比特幣使用壓縮公鑰（33 位元組，包含一個前綴位元組表示 y 座標的奇偶性）或非壓縮公鑰（65 位元組）。BIP 340 引入了「x-only」公鑰——只有 32 位元組的 x 座標。這種格式是可行的，因為對於任何 x 座標，最多只有兩個有效的 y 座標，而 BIP 340 規定永遠選擇「偶數」的那個。</p>

<p>這種簡化節省了空間，但也意味著私鑰可能需要「翻轉」。如果計算出的公鑰 y 座標是奇數，就需要用曲線的階減去私鑰，使得對應的公鑰 y 座標變為偶數。這是 Taproot 實現中的一個微妙細節。</p>

<h3 id="33-多重簽名聚合">3.3 多重簽名聚合</h3>

<p>Schnorr 簽名最引人注目的特性是支援簽名聚合。考慮一個 2-of-2 多簽場景，Alice 和 Bob 共同控制一筆資金。</p>

<p>在 ECDSA 世界中，這需要一個 P2SH 腳本，包含兩個公鑰和 OP_CHECKMULTISIG。花費時需要提供兩個完整的簽名，總共約 144 位元組的簽名數據。</p>

<p>使用 Schnorr 簽名的 MuSig2 協議，Alice 和 Bob 首先各自選擇隨機數並交換 R 值的承諾，然後交換實際的 R 值並計算聚合 R = R_A + R_B。接著，雙方獨立計算各自的部分簽名 s_A 和 s_B，最終聚合簽名就是 (R, s_A + s_B)。</p>

<p>這個聚合簽名只有 64 位元組，與單一簽名完全相同。更重要的是，從鏈上觀察者的角度來看，這與普通的單簽名交易沒有任何區別。沒有人知道這筆交易實際上需要兩個人的授權。</p>

<p>MuSig2 是 MuSig 協議的改進版本，將互動輪次從三輪減少到兩輪，大幅提升了實用性。然而，它的實現仍然比普通簽名複雜得多，特別是在確保 nonce 安全性方面需要特別小心。</p>

<hr />

<h2 id="四p2trpay-to-taproot詳解">四、P2TR（Pay to Taproot）詳解</h2>

<h3 id="41-taproot-輸出的結構">4.1 Taproot 輸出的結構</h3>

<p>Taproot 輸出的 ScriptPubKey 格式非常簡潔：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>OP_1 &lt;32-byte x-only pubkey&gt;
</pre></td></tr></tbody></table></code></pre></div></div>

<p>這裡的 OP_1（0x51）表示 witness 版本 1，後面跟著 32 位元組的 x-only 公鑰。這個公鑰不是普通的公鑰，而是經過「調整」（tweaked）的輸出公鑰，蘊含著豐富的資訊。</p>

<p>Taproot 地址使用 Bech32m 編碼（BIP 350 定義），主網地址以 bc1p 開頭，測試網地址以 tb1p 開頭。這裡的 p 代表版本 1（版本 0 使用 q，因為在 Bech32 編碼中 0 對應 q，1 對應 p）。</p>

<h3 id="42-公鑰調整機制">4.2 公鑰調整機制</h3>

<p>Taproot 的核心創新在於公鑰調整（key tweaking）機制。假設我們有一個內部公鑰 P（可能是單一公鑰，也可能是多方聚合公鑰），以及一個可選的腳本樹。我們需要構造一個輸出公鑰 Q。</p>

<table>
  <tbody>
    <tr>
      <td>如果沒有腳本樹，調整值 t 計算為：t = H_TapTweak(P)。如果有腳本樹，則先計算樹的 Merkle 根 m，調整值為：t = H_TapTweak(P</td>
      <td> </td>
      <td>m)。這裡的 H_TapTweak 是一個標籤雜湊函數，其定義確保了不同用途的雜湊不會碰撞。</td>
    </tr>
  </tbody>
</table>

<p>輸出公鑰 Q = P + t × G。這個 Q 就是存放在 ScriptPubKey 中的值。</p>

<p>這個設計的巧妙之處在於承諾方式。如果 Q = P + t × G，那麼知道 P 和 t 的人可以驗證 Q 確實是由 P 調整而來的。而 t 本身包含了腳本樹的 Merkle 根（如果有的話），所以 Q 實際上承諾了整個腳本樹的內容，但除非需要使用腳本路徑，否則這些腳本永遠不會暴露。</p>

<h3 id="43-key-path-花費">4.3 Key Path 花費</h3>

<p>Key path 是 Taproot 輸出最常見的花費方式，也是最高效的方式。要透過 key path 花費，簽名者需要知道內部私鑰 d，然後計算調整後的私鑰 d’ = d + t。使用 d’ 對交易進行 Schnorr 簽名，這個簽名就可以通過 Q 的驗證。</p>

<p>witness 只包含一個元素：64 位元組的 Schnorr 簽名。沒有腳本，沒有公鑰揭示，沒有任何其他數據。這意味著無論背後的腳本有多複雜，只要所有參與方達成共識，花費的方式就和最簡單的單簽名交易一樣。</p>

<p>這對隱私有巨大的意義。一個複雜的多方合約——比如閃電網路通道的開設和關閉——在鏈上看起來與普通的個人轉帳沒有區別。觀察者無法知道這筆交易背後是一個人還是一百個人，是否有任何備用腳本存在。</p>

<h3 id="44-script-path-花費">4.4 Script Path 花費</h3>

<p>當需要使用備用腳本時，就需要透過 script path 花費。這種情況下，witness 需要包含三部分：腳本的輸入數據（如簽名）、要執行的腳本本身，以及控制區塊（control block）。</p>

<p>控制區塊的結構包含：一個版本位元組（其中包含葉子版本和 Q 的 y 座標奇偶性）、32 位元組的內部公鑰 P，以及 Merkle 證明路徑（每個節點 32 位元組）。</p>

<table>
  <tbody>
    <tr>
      <td>Merkle 證明路徑允許驗證者確認所揭示的腳本確實是輸出承諾的腳本樹的一部分。驗證過程是：首先用腳本和葉子版本計算葉子雜湊，然後沿著證明路徑向上計算直到得到 Merkle 根，最後驗證 Q = P + H_TapTweak(P</td>
      <td> </td>
      <td>root) × G。</td>
    </tr>
  </tbody>
</table>

<p>這個機制的優美之處在於，只需要揭示你實際使用的腳本和它的證明路徑，而不需要揭示整個腳本樹。如果樹中有 128 個腳本，使用其中一個只需要 7 層的證明（log₂(128) = 7），其他 127 個腳本保持完全保密。</p>

<h3 id="45-mast-的實現">4.5 MAST 的實現</h3>

<p>Taproot 實際上實現了一個叫做 MAST（Merkle Abstract Syntax Tree，默克爾抽象語法樹）的概念。這個概念在比特幣社區討論多年，而 Taproot 終於將它變成了現實。</p>

<p>傳統的 P2SH 需要在花費時揭示整個贖回腳本，無論其中有多少分支。如果你有一個「Alice 可以隨時花費，或者 Bob 在一年後可以花費」的腳本，即使 Alice 花費，也需要揭示 Bob 的備用條件存在。</p>

<p>使用 Taproot，Alice 和 Bob 可以構建一個腳本樹：根節點下有兩個葉子，分別是 Alice 的條件和 Bob 的時間鎖條件。更好的是，他們可以用聚合公鑰作為 key path——如果雙方合作，完全不需要揭示任何腳本。只有當他們無法合作（比如 Alice 失聯）時，Bob 才需要使用 script path，此時只揭示自己的分支，Alice 的腳本保持私密。</p>

<p>這種架構對於閃電網路等複雜協議特別有價值。閃電網路通道的承諾交易包含多個可能的花費路徑，使用 Taproot 可以大幅簡化這些交易的結構並提升隱私性。</p>

<hr />

<h2 id="五tapscript-新規則">五、Tapscript 新規則</h2>

<h3 id="51-tapscript-的設計目標">5.1 Tapscript 的設計目標</h3>

<p>Tapscript 是 Taproot 腳本路徑中使用的腳本規則，由 BIP 342 定義。它基於 SegWit 的腳本規則，但做了幾個重要的修改和改進。</p>

<p>設計 Tapscript 的主要目標是：適配 Schnorr 簽名、改進多簽效率、放寬某些限制、以及為未來升級預留空間。</p>

<h3 id="52-op_checksigadd">5.2 OP_CHECKSIGADD</h3>

<p>Tapscript 最重要的新操作碼是 OP_CHECKSIGADD（0xba）。這個操作碼專門為 Schnorr 簽名的批量驗證優化設計，用來取代傳統的 OP_CHECKMULTISIG。</p>

<p>OP_CHECKMULTISIG 有一個著名的「off-by-one」bug：它會多從堆疊中彈出一個未使用的元素。這個 bug 從比特幣誕生之初就存在，由於共識規則無法輕易修改，一直保留至今。OP_CHECKSIGADD 避免了這個問題。</p>

<p>OP_CHECKSIGADD 的語義很簡單：它從堆疊取三個元素——簽名、計數器 n、公鑰。如果簽名有效，將 n+1 推回堆疊；如果簽名無效（空簽名），將 n 推回堆疊。這允許用簡單的累加邏輯實現 k-of-n 多簽：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>&lt;pk1&gt; OP_CHECKSIG
&lt;pk2&gt; OP_CHECKSIGADD
&lt;pk3&gt; OP_CHECKSIGADD
2 OP_NUMEQUAL
</pre></td></tr></tbody></table></code></pre></div></div>

<p>這個腳本檢查三個公鑰中是否至少有兩個對應有效簽名。與 OP_CHECKMULTISIG 不同，簽名的順序不需要與公鑰順序匹配——未簽名的公鑰對應空簽名即可。</p>

<p>更重要的是，這種結構允許有效的批量驗證。驗證者可以收集所有的 (公鑰, 訊息, 簽名) 三元組，使用 Schnorr 的批量驗證演算法一次性驗證所有簽名，這比逐個驗證快得多。</p>

<h3 id="53-放寬的限制">5.3 放寬的限制</h3>

<p>Tapscript 移除了幾個傳統腳本的限制。</p>

<p>腳本大小限制：傳統腳本限制為 10,000 位元組，Tapscript 沒有這個限制（但仍受區塊權重限制）。這對於某些需要大量公鑰或複雜邏輯的應用來說是重要的改進。</p>

<p>操作碼數量限制：傳統腳本限制每個腳本最多 201 個操作碼，Tapscript 移除了這個限制。這對於需要大量條件分支的複雜腳本很有用。</p>

<p>堆疊元素大小：傳統腳本限制單個堆疊元素最大 520 位元組。Tapscript 仍然有這個限制，但由於 OP_SUCCESS 的存在，未來可以透過軟分叉放寬。</p>

<h3 id="54-op_success-與未來升級">5.4 OP_SUCCESS 與未來升級</h3>

<p>Tapscript 引入了一個創新的升級機制：OP_SUCCESS 操作碼。這些是一組目前未定義的操作碼（包括 0x50, 0x62, 0x89-0x8a 等），在 Tapscript 中被解釋為「立即成功」——遇到這些操作碼時，腳本驗證立即成功，不管其他條件。</p>

<p>這看起來像是一個安全漏洞——使用這些操作碼的腳本不就可以無條件被任何人花費嗎？確實如此。但關鍵在於，這允許未來透過軟分叉賦予這些操作碼實際意義。一旦一個 OP_SUCCESS 被重新定義，使用它的腳本就需要滿足新的條件才能被花費。</p>

<p>這種「先開放再收緊」的升級策略比傳統的「先禁用再啟用」更加靈活。傳統腳本中的禁用操作碼（如 OP_CAT）一旦被執行就會使腳本失敗，這意味著啟用它們需要複雜的版本控制機制。而 OP_SUCCESS 可以被簡單地重新定義，只要新的規則比「無條件成功」更嚴格，就是向後相容的軟分叉。</p>

<hr />

<h2 id="六bech32m-地址編碼">六、Bech32m 地址編碼</h2>

<h3 id="61-從-bech32-到-bech32m">6.1 從 Bech32 到 Bech32m</h3>

<p>SegWit v0 引入了 Bech32 地址編碼，這是一種專為人類可讀性設計的編碼格式，使用 32 個字元（qpzry9x8gf2tvdw0s3jn54khce6mua7l）來表示數據，並包含強大的錯誤檢測能力。</p>

<p>然而，Bech32 被發現存在一個弱點：在某些情況下，地址末尾的 q 字元可以被添加或刪除而不改變校驗和。這個問題對於 SegWit v0 地址影響較小（因為長度是固定的），但對於未來可能有不同長度的 witness 版本來說是個問題。</p>

<p>BIP 350 定義了 Bech32m 來解決這個問題。它使用了不同的校驗和常數，使得添加或刪除字元會被檢測到。規則是：SegWit v0 地址繼續使用原始 Bech32 編碼，而 SegWit v1 及更高版本使用 Bech32m。</p>

<p>這就是為什麼 P2WPKH 和 P2WSH 地址以 bc1q 開頭（q 是版本 0 在 Bech32 中的表示），而 P2TR 地址以 bc1p 開頭（p 是版本 1 在 Bech32 中的表示）。</p>

<h3 id="62-地址類型識別">6.2 地址類型識別</h3>

<p>作為開發者，了解如何識別不同的比特幣地址類型很有用：</p>

<p>以 1 開頭的是傳統 P2PKH 地址，使用 Base58Check 編碼。以 3 開頭的是 P2SH 地址，可能包裝任何類型的腳本，包括 SegWit（P2SH-wrapped SegWit）。以 bc1q 開頭的是原生 SegWit v0 地址（P2WPKH 或 P2WSH）。以 bc1p 開頭的是 Taproot（P2TR）地址。</p>

<p>測試網使用不同的前綴：m 或 n 開頭是測試網 P2PKH，2 開頭是測試網 P2SH，tb1q 是測試網 SegWit v0，tb1p 是測試網 Taproot。</p>

<hr />

<h2 id="七實際應用案例">七、實際應用案例</h2>

<h3 id="71-閃電網路通道">7.1 閃電網路通道</h3>

<p>閃電網路是 Taproot 最重要的受益者之一。傳統的閃電網路通道開設交易使用 2-of-2 P2WSH 多簽，在鏈上清晰可見。使用 Taproot，通道開設只需要一個普通的 P2TR 輸出，無法與普通支付區分。</p>

<p>通道承諾交易包含多個可能的花費路徑：合作關閉、單方面關閉、懲罰交易等。使用 MAST 結構，這些複雜的邏輯可以被隱藏在腳本樹中，只在必要時揭示。</p>

<h3 id="72-多簽錢包">7.2 多簽錢包</h3>

<p>對於需要多方授權的企業或高價值錢包，Taproot 提供了顯著的隱私和成本優勢。一個 3-of-5 的多簽設置，使用 Schnorr 聚合可以讓所有簽名者共同產生一個單一簽名。在鏈上，這與個人錢包的交易看起來完全一樣。</p>

<p>如果某些簽名者無法參與，可以透過 script path 使用備用的閾值方案，只需要揭示需要的那個分支。</p>

<h3 id="73-遺產規劃">7.3 遺產規劃</h3>

<p>Taproot 對於加密貨幣遺產規劃也很有用。假設 Alice 想設置一個輸出：正常情況下她自己可以花費，但如果她失聯超過一年，她的繼承人可以透過 2-of-3 多簽來花費。</p>

<p>使用 Taproot，Alice 的公鑰可以作為 key path——日常使用時就像普通錢包一樣。腳本樹中包含一個帶時間鎖的繼承人多簽腳本。只有當 Alice 真的無法使用 key path 時，繼承人才需要揭示這個腳本。</p>

<hr />

<h2 id="八實作練習">八、實作練習</h2>

<h3 id="練習-1創建-taproot-地址">練習 1：創建 Taproot 地址</h3>

<p>嘗試用你熟悉的程式語言創建一個簡單的 Taproot 地址。步驟包括：生成一個隨機私鑰，計算對應的公鑰（注意轉換為 x-only 格式），計算無腳本的 tap tweak，調整公鑰，使用 Bech32m 編碼生成地址。</p>

<h3 id="練習-2設計腳本樹">練習 2：設計腳本樹</h3>

<p>設計一個 Taproot 輸出，支援以下場景：Alice 和 Bob 可以隨時共同簽名（key path），Alice 可以在 30 天後單獨花費，Carol 可以在 180 天後作為緊急恢復。畫出腳本樹結構，並考慮如何優化樹的深度以最小化未來可能的見證大小。</p>

<h3 id="練習-3比較效率">練習 3：比較效率</h3>

<p>計算以下場景的 witness 大小：2-of-3 多簽使用傳統 P2SH、使用 P2WSH、使用 Taproot key path（假設 MuSig2 聚合）、使用 Taproot script path。分析不同方案的權衡。</p>

<hr />

<h2 id="九小結">九、小結</h2>

<p>本篇我們深入探討了比特幣兩個最重要的升級：</p>

<p><strong>SegWit 的貢獻</strong>包括：解決了交易延展性問題，使閃電網路等二層協議成為可能；提升了有效區塊容量；優化了簽名雜湊計算的效率；引入了版本化的 witness 程式，為未來升級奠定基礎。</p>

<p><strong>Taproot 的創新</strong>包括：引入 Schnorr 簽名，提供更高效的簽名和批量驗證；實現 MAST，允許複雜腳本保持私密；透過 key path/script path 雙路徑設計，讓大多數交易看起來像簡單的單簽名；Tapscript 改進了腳本語言並為未來升級預留空間。</p>

<p>這些技術不僅提升了比特幣的效率和可擴展性，更重要的是大幅增強了隱私性——讓複雜的智能合約在鏈上看起來與普通交易無異。</p>

<hr />

<h2 id="下一篇預告">下一篇預告</h2>

<p>在下一篇文章中，我們將探討多重簽名的各種實現方式，包括傳統的 OP_CHECKMULTISIG、Tapscript 的 OP_CHECKSIGADD、以及 MuSig2 協議的細節。</p>

<hr />

<h2 id="參考資料">參考資料</h2>

<ul>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki">BIP 141: SegWit</a> - SegWit 的原始規格</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki">BIP 143: SegWit 簽名哈希</a> - 新的簽名雜湊演算法</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki">BIP 340: Schnorr 簽名</a> - Schnorr 簽名的比特幣標準</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki">BIP 341: Taproot</a> - Taproot 的完整規格</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki">BIP 342: Tapscript</a> - Tapscript 腳本規則</li>
  <li><a href="https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki">BIP 350: Bech32m</a> - 改進的地址編碼</li>
  <li><a href="https://bitcoinops.org/en/topics/taproot/">Bitcoin Optech: Taproot</a> - 深入的技術解說</li>
</ul>]]></content><author><name>Cypherpunks Taiwan</name></author><category term="技術教學" /><category term="bitcoin" /><category term="development" /><category term="bitcoin" /><category term="script" /><category term="segwit" /><category term="taproot" /><category term="schnorr" /><summary type="html"><![CDATA[Bitcoin Script 系列第三篇，深入解析 SegWit 的技術細節、Taproot 升級、Schnorr 簽名，以及 Tapscript 的新操作碼。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" /><media:content medium="image" url="https://cypherpunks-core.github.io/img/bitcoin-dev.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>