Why is bitcoinj java library not being able to decode input addresses for some transactions?

by Bruno   Last Updated January 08, 2019 14:27 PM

I'm trying to "extract" sender's addresses from OP_RETURN Bitcoin transactions, but my code is not working properly.

getWalletAddressOfSender(final Transaction 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;
}

When the TransactionInput is from a address starting with 3... (P2SH) the list 'scriptSig.getChunks()' will have 5 items and 'getPubKey()' will throw the exception '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);
    }
}

So i changed the code to

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;
}

but it will also throw another exception 'Script not in the standard scriptPubKey form' (when chunks.size() > 2)

scriptSig.getPubKeyHash()

/**
 * <p>If a program matches the standard template DUP HASH160 &lt;pubkey hash&gt; 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 &lt;script hash&gt; 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");
}

Here's a transaction where chunks.size() == 2 and i'm able to extract sender's address (1KYiKJEfdJtap9QX2v9BXJMpz2SfU4pgZw)

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

These are some transactions where chunks.size() == 5 and i'm NOT able to extract the sender's address (3....)

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

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

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

What's the correct way of getting the sender's address in these cases ?

FYI, this is how i'm getting the recipient's addresses (and it works fine even for the 3... cases)

public static Address getWalletAddressOfReceiver(final Transaction tx) {

    for (final TransactionOutput output : tx.getOutputs()) {
        try {

            final Script script = output.getScriptPubKey();     
            Address receiverAddress = script.getToAddress(MainNetParams.get(), true);
            return receiverAddress;

        } catch (final ScriptException x) {

        }
    }

    return null;
}


Related Questions


BitcoinJ manually sign P2SH output

Updated December 31, 2018 15:27 PM