Bitcoinj

bitcoinj java庫不解碼某些交易的輸入地址

  • January 12, 2019

我正在嘗試從 OP_RETURN 比特幣交易中“提取”發件人的地址,但我的程式碼無法正常工作。

getWalletAddressOfSender(最終交易 tx)

public static Address getWalletAddressOfSender(final Transaction tx) {

   Address fromAddress = null;

   for (final TransactionInput ti : tx.getInputs()) {

       try {

           Script scriptSig = ti.getScriptSig();
           List<ScriptChunk> chunks = scriptSig.getChunks();

           byte[] pubKey = scriptSig.getPubKey();
           fromAddress = new Address(MainNetParams.get(), Utils.sha256hash160(pubKey));// scriptSig.getFromAddress(MainNetParams.get());

           return fromAddress;

       } catch (final ScriptException x) {
           System.out.println(x.getMessage());
       }
   }

   return null;
}

當 TransactionInput 來自以 3… (P2SH) 開頭的地址時,列表 ‘scriptSig.getChunks()’ 將有 5 個項目,並且 ‘getPubKey()’ 將拋出異常’Script not of right size, expecting 2 but got 5’。

scriptSig.getPubKey()

/**
* Returns the public key in this script. If a script contains two constants and nothing else, it is assumed to
* be a scriptSig (input) for a pay-to-address output and the second constant is returned (the first is the
* signature). If a script contains a constant and an OP_CHECKSIG opcode, the constant is returned as it is
* assumed to be a direct pay-to-key scriptPubKey (output) and the first constant is the public key.
*
* @throws ScriptException if the script is none of the named forms.
*/
public byte[] getPubKey() throws ScriptException {
   if (chunks.size() != 2) {
       throw new ScriptException("Script not of right size, expecting 2 but got " + chunks.size());
   }
   final ScriptChunk chunk0 = chunks.get(0);
   final byte[] chunk0data = chunk0.data;
   final ScriptChunk chunk1 = chunks.get(1);
   final byte[] chunk1data = chunk1.data;
   if (chunk0data != null && chunk0data.length > 2 && chunk1data != null && chunk1data.length > 2) {
       // If we have two large constants assume the input to a pay-to-address output.
       return chunk1data;
   } else if (chunk1.equalsOpCode(OP_CHECKSIG) && chunk0data != null && chunk0data.length > 2) {
       // A large constant followed by an OP_CHECKSIG is the key.
       return chunk0data;
   } else {
       throw new ScriptException("Script did not match expected form: " + this);
   }
}

所以我將程式碼更改為

public static Address getWalletAddressOfSender(final Transaction tx) {

   Address fromAddress = null;

   for (final TransactionInput ti : tx.getInputs()) {

       try {
           Script scriptSig = ti.getScriptSig();
           List<ScriptChunk> chunks = scriptSig.getChunks();
           if(chunks.size() > 2) {
               System.out.print("This is a 5 chunks transaction... ");
               byte[] pubKeyHash = scriptSig.getPubKeyHash();
               fromAddress = Address.fromP2SHHash(MainNetParams.get(), pubKeyHash);
           } else {
               byte[] pubKey = scriptSig.getPubKey();
               fromAddress = new Address(MainNetParams.get(), Utils.sha256hash160(pubKey));// scriptSig.getFromAddress(MainNetParams.get());   
           }

           return fromAddress;
       } catch (final ScriptException x) {
           System.out.println(x.getMessage());
       }
   }

   return null;
}

但它也會拋出另一個異常’Script not in the standard scriptPubKey form’(當 chunks.size() > 2 時)

scriptSig.getPubKeyHash()

/**
* <p>If a program matches the standard template DUP HASH160 <pubkey hash> EQUALVERIFY CHECKSIG
* then this function retrieves the third element.
* In this case, this is useful for fetching the destination address of a transaction.</p>
* 
* <p>If a program matches the standard template HASH160 <script hash> EQUAL
* then this function retrieves the second element.
* In this case, this is useful for fetching the hash of the redeem script of a transaction.</p>
* 
* <p>Otherwise it throws a ScriptException.</p>
*
*/
public byte[] getPubKeyHash() throws ScriptException {
   if (isSentToAddress())
       return chunks.get(2).data;
   else if (isPayToScriptHash())
       return chunks.get(1).data;
   else
       throw new ScriptException("Script not in the standard scriptPubKey form");
}

這是一個事務,其中 chunks.size() == 2 並且我能夠提取發件人的地址 (1KYiKJEfdJtap9QX2v9BXJMpz2SfU4pgZw)

<https://www.blockchain.com/btc/tx/b5765d54e275794939eb48c77dd8862a6e865dee6d71bc7004660dca32de8c43>

這些是一些交易,其中 chunks.size() == 5 並且我無法提取發件人的地址 (3….)

<https://www.blockchain.com/btc/tx/b02e17479660a4685daba4e8f0f73aea96e0c36ab14142b68f868ac76a77455a>

<https://www.blockchain.com/btc/tx/f0a6708167eca88b9fe4dad4c110ddff2b3f6c5e08771793b8ca40400d4effab>

<https://www.blockchain.com/btc/tx/28a91393393916367e890965200d4f8af04416b65ee6fea22c0adf29af8ea3b8>

在這些情況下獲取發件人地址的正確方法是什麼?

僅供參考:這是我獲取收件人地址的糟糕方法

// THIS IF A FUCKED UP WORKAROUND, FIX ASAP
@Nullable
public static Address getWalletAddressOfReceiver(final Transaction tx, final Address senderAddress) {

   for (final TransactionOutput output : tx.getOutputs()) {
       try {
           final Script script = output.getScriptPubKey();     
           Address receiverAddress = script.getToAddress(MainNetParams.get(), true);
           if(receiverAddress.equals(senderAddress))
               continue;
           return receiverAddress;

       } catch (final ScriptException x) {

       }
   }

   return null;
}

由於收件人的地址在“tx.getOutputs()”中的隨機位置,如果它等於發件人的地址,我就跳過它並獲取下一個。

當輸入兌換 P2PKH 輸出(支付到以 1 開頭的地址)時,輸入腳本(scriptSig)的格式為:[signature] [public key]. 因為公鑰包含在標準輸入腳本(兌換 P2PKH 輸出)和輸出腳本 (P2PK) 中,所以該Script.getPubKey()方法被編寫為在輸入腳本或輸出腳本中返回公鑰,因此它可以在這里為您工作。

但是,公鑰雜湊(地址)僅包含在標準輸出腳本中。因此,該Script.getPubKeyHash()方法假定您正在討論輸出腳本,因此在這裡對您不起作用。

一個輸入腳本贖回一個 P2SH(pay to address 以 3 開頭)輸出不一定是 5 個塊,但地址是贖回腳本的雜湊,贖回腳本總是最後一個塊。(具體來說,輸入腳本是:[script sig for the redeem script (may be multiple chunks)] [redeem script]。)所以您需要做的就是獲取最後一個塊(這也是您為 P2PKH 所做的)並對其進行雜湊處理。

您可能會遇到麻煩的一個地方是,如果您正在兌換 P2SH 輸出,並且輸入腳本是 2 個塊。(這可能會出現,例如,如果贖回腳本是[public key] OP_CHECKSIG- 那麼 sigScript 的形式是[signature] [20-byte hash],與贖回 P2PKH 輸出的形式無法區分。)檢查這一點的方法是查看前一筆交易並檢查其輸出腳本(這也可以讓您更輕鬆地找到您正在尋找的地址)。

但是假設當您兌換 P2SH 輸出時輸入腳本超過 2 個塊,這樣的事情應該可以工作:

public static Address getWalletAddressOfSender(final Transaction tx) {

   Address fromAddress = null;

   for (final TransactionInput ti : tx.getInputs()) {

       try {

           Script scriptSig = ti.getScriptSig();
           List&lt;ScriptChunk&gt; chunks = scriptSig.getChunks();

           byte[] pubKey = chunks.get(chunks.size() - 1).data;
           byte[] pubKeyHash = Utils.sha256hash160(pubKey);

           if (chunks.size() &gt; 2) { // assume P2SH
               fromAddress = Address.fromP2SHHash(MainNetParams.get(), pubKeyHash);
           } else if (chunks.size() == 2) { // assume P2PKH
               fromAddress = new Address(MainNetParams.get(), pubKeyHash);
           }
           return fromAddress;

       } catch (final ScriptException x) {
           System.out.println(x.getMessage());
       }
   }

   return null;
}

(注意我在這裡使用了變數名pubKey和變數名pubKeyHash,但在 P2SH 的情況下,這些將改為腳本和腳本雜湊。如果引用的輸出是多重簽名、P2PK 或非標準的東西,此方法也可能會導致問題。 )

我還注意到您的方法僅返回第一個發件人地址。如果交易有來自多個來源的輸入,則可能值得返回一個列表或一組這些發件人地址。只是一個想法。

希望這可以幫助!

引用自:https://bitcoin.stackexchange.com/questions/83481