bitcoinjs / bitcoinjs-lib

A javascript Bitcoin library for node.js and browsers.
MIT License
5.69k stars 2.11k forks source link

Extract output addresses from hex transaction #1282

Closed bestatigen closed 5 years ago

bestatigen commented 5 years ago

Description:

I'm trying to retrieve the output addresses from a hex transaction created this way:

import {
  TransactionBuilder as bitcoinTransactionBuilder,
  Transaction as BitcoinTransaction,
} from 'bitcoinjs-lib';

let transaction = bitcoinTransactionBuilder.fromTransaction(
  BitcoinTransaction.fromHex(transactionHex),
  network
);

as in https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/transaction_builder.js#L106.

On transaction__tx.outs[0] I have:

{
  script: Uint8Array(25)
  value: number
}

Is the output address encoded in script? If so what is the process to extract it?

dcousens commented 5 years ago

@junderw and with that... TransactionBuilder shall become a closure...

junderw commented 5 years ago

@dcousens sigh... Yeah...

@devey You don't even need the TransactionBuilder to get it.

Something like the below should work... kinda hacky though. Not tested.

import {
  TransactionBuilder as bitcoinTransactionBuilder,
  Transaction as BitcoinTransaction,
  payments as BitcoinPayments,
} from 'bitcoinjs-lib';

let tx = BitcoinTransaction.fromHex(transactionHex)
let addresses = tx.outs.map(output => {
    let payment, address, isDone
    Object.keys(BitcoinPayments).forEach(type => {
        if (isDone) return
        try {
            payment = BitcoinPayments[type]({output: output.script})
        } catch(e) {
            console.log('Not ' + type + ' keep trying...')
            return
        }
        try {
            address = payment.address
            if (!address) throw new Error('oops')
        } catch(e) {
            address = '[NO ADDRESS (Legacy P2PK, OP_RETURN etc.)]'
        }
        isDone = true
    })
    return address
})

console.log(addresses)
junderw commented 5 years ago

maybe want to just copy paste all the payment type names in an array and iterate on them in order of prevalence... as imo the default order starts with some obscure ones that don't even have addresses iirc.

bestatigen commented 5 years ago

Thanks for the time @junderw; spot on, just needed to pass network too:

payment = BitcoinPayments[type]({
  output: output0Script,
  network,
});
educob commented 5 years ago

Hi junderw,

I tried your code above and I don't get any address for blocks read from hard disk (blkxxxxx.dat) as explained in this issue: https://github.com/bitcoinjs/bitcoinjs-lib/issues/1286

I modified the code like this:

    const block = bitcoin.Block.fromBuffer(rawblock)
    const script = block.transactions[0].outs[0].script
    let payment, address, isDone
    Object.keys(bitcoin.payments).forEach(type => {
        if (isDone) return
        try {
            payment = bitcoin.payments[type]({output: script})
        } catch(e) {
            console.log('Not ' + type + ' keep trying...')
            return
        }
        try {
            address = payment.address
            if (!address) throw new Error('oops')
        } catch(e) {
            address = '[NO ADDRESS (Legacy P2PK, OP_RETURN etc.)]'
        }
        isDone = true
    })
    console.log("address:", address)

Please help me to obtain the address.

Thanks.

junderw commented 5 years ago

what do you get?

an error? what does the error say?

be specific or no one can help you.

educob commented 5 years ago

This is the tx I get when reading from blk00000.dat

Transaction {
version: 1,
locktime: 0,
ins: 
[ { hash: <Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>,
index: 4294967295,
script: <Buffer 04 ff ff 00 1d 01 04 45 54 68 65 20 54 69 6d 65 73 20 30 33 2f 4a 61 6e 2f 32 30 30 39 20 43 68 61 6e 63 65 6c 6c 6f 72 20 6f 6e 20 62 72 69 6e 6b 20 ... >,
sequence: 4294967295,
witness: [] } ],
outs: 
[ { value: 5000000000,
script: <Buffer 41 04 67 8a fd b0 fe 55 48 27 19 67 f1 a6 71 30 b7 10 5c d6 a8 28 e0 39 09 a6 79 62 e0 ea 1f 61 de b6 49 f6 bc 3f 4c ef 38 c4 f3 55 04 e5 1e c1 12 de ... > } ] }

As you can see script is in buffer form so payments classes (p2pk, ...) reject it at the very beginning:

function p2pk (a, opts) {
  if (
    !a.input &&
    !a.output &&
    !a.pubkey &&
    !a.input &&
    !a.signature
  ) throw new TypeError('Not enough data')

So I get: 'Not enough data' error.

But when I get a block from the node by calling client.getBlockByHash I get:

...
vout: 
[ { value: 0.00040514, n: 0, scriptPubKey: [Object] },
 { value: 2.86177728, n: 1, scriptPubKey: [Object] } ],

The script is an object so it has the attributes that payments classes expect.

So what it's happening is that when calling bitcoin.Block.fromBuffer the scripts are not processed at all and bitcoinjs-lib don't know how to read data from them.

Thanks.

educob commented 5 years ago

More info.

This is most confusing cause when I get notified a transaction from the node by using:

  let tx_sock = zmq.socket('sub')
  let tx_addr = 'tcp://127.0.0.1:'+process.env.BITCOIN_TX_NOTIFY
  tx_sock.connect(tx_addr)
  tx_sock.subscribe('rawtx')
  tx_sock.on('message', async function(topic, message) {
    let tx = bitcoin.Transaction.fromBuffer(message)

The tx received is:

 { version: 1,
locktime: 0,
ins: 
[ { hash: <Buffer eb 05 f6 b5 90 d1 58 2f e2 3d 79 78 5a 1e 6a 77 c8 2e a5 38 10 32 59 94 92 b6 30 55 a3 6e a6 9a>,
 index: 0,
script: <Buffer 48 30 45 02 21 00 ff 06 96 77 49 bd b2 b6 d4 49 b ... >,
sequence: 4294967295,
witness: [] } ],
outs: 
[ { value: 0,
script: <Buffer 6a 4c 50 00 00 59 2f 00 01 9f b6 5a db ca ... > },
{ value: 773995,
script: <Buffer 76 a9 14 7f dc 10 9b c7 9a a5 c3 4f 9b 19 a6 7d eb 25 89 20 ... }

As you can see the script is in buffer form again. But this time calling:

tx.outs.forEach( (vout, index) => { // UTXO
      try {
        let address = bitcoin.address.fromOutputScript(vout.script, network)

works ok and I get the address.

So I don't understand why with this script in buffer form it works but with the block read from blk00000.dat it doesn't.

Thanks.

educob commented 5 years ago

I made it work!!!!

I had to modify the library.

1) address.js I added: try { return payments.p2pk({ output, network }).address } catch (e) {}

2) payments.p2pk I added:

const crypto = require('../crypto')
const bs58check = require('bs58check')

and after

  lazy.prop(o, 'pubkey', function () {
    if (!a.output) return
    return a.output.slice(1, -1)
  })

I added:

  lazy.prop(o, 'hash', function () {
    if (a.pubkey || o.pubkey) return crypto.hash160(a.pubkey || o.pubkey)
  })
  lazy.prop(o, 'address', function () {
    if (!o.hash) return

    const payload = Buffer.allocUnsafe(21)
    payload.writeUInt8(network.pubKeyHash, 0)
    o.hash.copy(payload, 1)
    return bs58check.encode(payload)
  })

It works but I would appreciate a proper update of the library as I did copy/paste and a lot of guessing. Thanks.

junderw commented 5 years ago

P2PK does not have an address.

Your modification is incorrect.

educob commented 5 years ago

Yes. You are right in the strict sense that a p2pk script is just the publick key + op_checksig.

But how do I know which address the bitcoins were sent to when the script is p2pk?

Doing this modification I got the real addresses (the satoshi 1st address)

Thanks.

junderw commented 5 years ago

Block Explorers started showing P2PKH addresses for P2PK outputs because they are irresponsible and don't care if people lose money.

You should show the pubkey and OP_CHECKSIG using toAsm

educob commented 5 years ago

Ok. Thanks.

junderw commented 5 years ago

I believe this is concluded. Please re-open if needed.