1
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 <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");
}
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: here's my shitty method to get the recipient's address
// 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;
}
As the recipient's address is in a random position in "tx.getOutputs()" i just skip it if it's equal to sender's address and get the next one.
Awesome! It works for most cases, however i encountered some transactions from P2SH addresses that have only 1 chunk. So for it to work on these cases i changed the code to
– Bruno – 2019-01-14T04:43:23.753if (chunks.size() > 2 || chunks.size() == 1) { // assume P2SH. And now it works. Check this one for example f8eb444bdc11b4d85dfe44fb6aa2eb20628a93d23fde84ec60835a25acb617fdAlso, using the same transaction example above, how do i detect that
3AJyCVDv5Nb55tpCnMxYxsqc3cAYQXZWEPis an input address (so the code won't consider it the "destination" address) ? (Please consider my "getWalletAddressOfReceiver" code from original question). The getWalletAddressOfSender method returns3BMEtyRXqVGRujANXNEXyTZ3tY3kRqraQQas the sender's address, which is the correct one btw. But the "getWalletAddressOfReceiver" will return3AJyCVDv5Nb55tpCnMxYxsqc3cAYQXZWEP(incorrect) instead of1BNY3BKk6AhWNq8iYrCuJxiNMtHHSGbg4o(correct). – Bruno – 2019-01-14T05:02:49.2331Really a P2SH input could have any number of inputs. You could check the formatting more rigorously, i.e. if you're spending a P2PKH output it should have two chunks: The first should be a signature, usually of size 71-73 (althought it technically could be smaller than 71 bytes), and the second chunk will be a public key: either 33 or 65 bytes. But ultimately, you can only tell for sure from outputs, not from inputs.
I'll add a bit to my answer about the receiver address. – Vecna – 2019-01-15T05:08:19.873
1I think your problem with getting the receiver address is that you're only looking at one address at a time. This transaction has 2 inputs and 2 outputs that pay to addresses, so it's not enough to just return the first address from each place. So if you walk through getting the sender address and it's the first one,
3BMEtyRXqVGRujANXNEXyTZ3tY3kRqraQQand then compare it to the first output address,3AJyCVDv5Nb55tpCnMxYxsqc3cAYQXZWEP, then the code will say, "These are different. Return this receiver address." I would suggest using sets for the addresses, instead of just assuming one. – Vecna – 2019-01-15T05:26:18.6701You'll also probably run into trouble with that second output because it's an OP_RETURN, so I'd suggest putting in a check before you call
script.getToAddress, e.g.if (script.isSentToAddress() || script.isPayToScriptHash()) { Address receiverAddress = script.getToAddress(MainNetParams.get(), true); }– Vecna – 2019-01-15T05:30:12.037