我的原始交易銷毀了 0.0284377 BTC。我做錯了什麼?
幾年前,我設計了一個 .NET 模組,它有助於將 BTC 傳輸給我的客戶。它根據此處和此處提供的材料創建所需交易的二進製表示:
然後將二進製表示轉換為十六進制,並通過各種免費服務(如 BlockExplorer、BlockCypher 等)推出。
該系統多年來一直完美執行。直到昨天。一位客戶要求將0.0284377 BTC發送到38MRMGjMBMp4k7vZhKLHhcM9Pm8AMLy18v。他從來沒有收到過。果然,他是對的。它從未發送過。
我查看了所有日誌,發現我的軟體確實送出了具有以下輸入和輸出的事務:
- 輸入:13P38hMYJXFdxDJJn8TtPUJZFXmcpf2J99
- 輸出#1:38MRMGjMBMp4k7vZhKLHhcM9Pm8AMLy18v(我的客戶)
- 輸出#2:1Ny3CV3rAsNMWpLfpxhXW3Fh71YmMEXXU7(我的找零地址)
我這邊一切看起來都很好,但果然,當我查看區塊瀏覽器時,我所看到的讓我震驚。考慮事務9a138b14dcc8ae740073c06933ae04e3b08fe6be6ada0dc175e6484250dfe269並查看輸入和輸出:
如您所見,我的輸入是正確的。我的零錢地址 XU7 也是正確的。但現在看看我客戶的地址。它說的是17fQRjEudTVgexE8aDfhGyzDFEqSnnJJCA(我將其稱為 JCA)而不是預期的38MRMGjMBMp4k7vZhKLHhcM9Pm8AMLy18v(我將其稱為 18v)。有沒有搞錯?我和我的客戶都不知道 JCA 地址。這是一個完全未知的地址。
很明顯,問題出在我的程式碼中,所以我深入探勘以找出是什麼讓這個特定的交易變得獨一無二。我發現他想要的地址 (18v) 以“3”開頭,而我在整個應用程序歷史中完成的所有其他出站交易的目標地址都以“1”開頭。
我的權宜之計是強制使用者只指定以 1 開頭的地址。但這是一個臨時解決方案。我需要找出我做錯了什麼。
我轉向Google。研究表明,以“3”開頭的地址通常是 SegWit 地址,而以“1”開頭的地址是傳統的老式地址。我記得幾個月前關於 SegWit 的所有討論,但我認為它不會影響我,我的遺留交易仍然可以正常清除。顯然,這對我來說是一個錯誤的判斷,所以現在我必須弄清楚我做錯了什麼以及虛假的 JCA 地址來自哪裡。
這就是我卡住的地方。根據此處和此處的討論,我認為我的問題可能與未壓縮與壓縮公鑰有關:
這是我所知道的:當需要為我的交易創建輸出時,我的程式碼執行以下操作(解釋如下):
Func<String,byte[]> makeOutScript=(btcAddrHex)=>{ byte[] addrBytes=BTCUtils.Base58Decode(btcAddrHex); byte[] pubKeyBytes=addrBytes.Take(addrBytes.Length-4).Skip(1).ToArray(); using(MemoryStream ms=new MemoryStream()){ using(BinaryWriter bw=new BinaryWriter(ms)){ bw.Write((byte)0x76); //op_dup bw.Write((byte)0xa9); //op_hash160 bw.Write((byte)20); //size of public key bw.Write(pubKeyBytes); //public key bw.Write((byte)0x88); //op_equalverify bw.Write((byte)0xac); //op_checksig bw.Flush(); return ms.ToArray(); } } };
我在這裡做的是:通過 Base58 解碼將輸出地址轉換為字節。我去掉最後四個字節,即校驗和,和第一個字節,始終為 0x00(顯然是某種網路指示器)。這給我留下了原始公鑰。實際上,查看我的內部註釋,它實際上不是公鑰,而是 0x04 附加到公鑰,然後通過 SHA256,然後通過 RIPEMD160。但我將把這個 blob 稱為公鑰。從那裡字節序列是 0x76、0xa9、20(公鑰 blob 的大小),公鑰 blob 本身是 0x88 和 0xac。
我不會假裝理解所有這些單字節條目的含義,但上面的部落格文章說要包括它們,所以這就是我所做的,直到現在它工作得很好。
問題是:18v 地址在送出到網路時究竟是如何轉換為 JCA 地址的?也許我的“公鑰大小”值(20)不正確?在直覺層面上,壓縮(18v)和未壓縮(JCA)公鑰的大小都是恆定的 20 似乎很奇怪。但也許我完全走錯了路,壓縮與它無關。
如果這筆交易是 100 BTC 而不是 0.0284377,我不必告訴你會有多糟糕。我很幸運,我的錯誤是在低價值交易中觸發的。但我真的很想解決這個問題。你能指出我正確的道路嗎?
3...
以P2SH 地址開頭的地址,它們已經存在將近 6 年了。從歷史上看,他們看到了有限的使用——被 Copay 和 GreenAddress 等多重簽名錢包使用。然而,SegWit 的兼容模式也使用 P2SH,這使得它們的使用最近更加普遍。主要問題是您將地址轉換為 scriptPubKey 的程式碼不查看版本字節。在 Base58 解碼地址後,你應該得到 <1-byte version> <20-byte hash> <4-byte checksum> 結構。如果那個版本是 0,它是用於
1...
地址的,那麼這個散列就是一個公鑰散列,並且你正確地構造了相應的 scriptPubKey。但是,如果該版本號為 5,則它是在 BIP13 中指定的 P2SH 地址,它使用在 BIP16 中指定的相應scriptPubKey。
雖然 SegWit 最初只使用舊的 P2SH 格式,但也為 SegWit 引入了一種新的地址格式(它提供了稍微更好的性能、靈活性和錯誤檢測能力),如BIP173中所述。(免責聲明:我是 BIP173 的作者)。這些地址將以
bc1...
.我建議您停止您的服務,並且至少實施對預期版本號的驗證,以及您可以在許多實施中找到的測試集。如果您願意,您還可以實現 BIP13 和 BIP173 地址,這兩個地址可能很快就會變得更加流行。