bitcoinjs / bitcoinjs-lib

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

How to recover btc address from txn witness data? #2088

Closed saurabhburade closed 7 months ago

saurabhburade commented 7 months ago
junderw commented 7 months ago

It is impossible for p2tr key spend witnesses. (Since it’s only a signature) but every other type of segwit address can be recovered by its witness stack.

is that ok?

junderw commented 7 months ago

To be specific:

  1. P2WPKH: The last item on the witness stack is the pubkey, HASH160 it and use bech32 with segwit version 0 to encode.
  2. P2WSH: The last item on the witness stack is the witnessScript, SHA256 it and use bech32 with segwit version 0 to encode.
  3. P2TR (script): Follow BIP341 script validation rules up until the point where it compares the final output to the witness program from the previous output. If the transaction is confirmed in a block we can assume this check passed, so therefore we can get the witness program by performing the validation rules and encoding the result using bech32m with segwit version 1.
junderw commented 7 months ago

If you give this a P2TR witness that you know is definitely a P2TR, then it will give you the address if it's a script spend.

Probably buggy though.

import * as bitcoin from 'bitcoinjs-lib';
import * as vint from 'varuint-bitcoin';
import * as ecc from 'tiny-secp256k1';

// Required
bitcoin.initEccLib(ecc);

function p2trScriptToAddress(witness: Buffer[]): string {
  if (!Array.isArray(witness) || !witness.every(b => Buffer.isBuffer(b))) {
    throw new Error('All witness elements must be Buffers');
  }

  const wlen = witness.length;
  if (wlen < 2) {
    throw new Error('Not a p2tr script spend witness');
  }

  let annexOffset = 0;
  if (witness[wlen - 1][0] === 0x50) {
    // has annex
    annexOffset = 1;
  }

  if (wlen < 2 + annexOffset) {
    throw new Error('Not a p2tr script spend witness');
  }

  const controlBlock = witness[wlen - annexOffset - 1];
  if (controlBlock.length < 33 || ((controlBlock.length - 33) / 32) % 1 !== 0) {
    throw new Error('Incorrect Control block length');
  }

  const script = witness[wlen - annexOffset - 2];
  const leafVersion = controlBlock[0] & 0xfe;
  const internalPubkey = controlBlock.subarray(1, 33);

  // Get preimage for tapleaf hash
  const preImageLeafHash = Buffer.allocUnsafe(
    script.length + vint.encodingLength(script.length) + 1,
  );
  preImageLeafHash.writeUInt8(leafVersion, 0);
  vint.encode(script.length, preImageLeafHash, 1);
  script.copy(preImageLeafHash, 1 + vint.encodingLength(script.length));
  // Get tapleaf hash
  let tapLeafHash = bitcoin.crypto.taggedHash('TapLeaf', preImageLeafHash);

  const loops = (controlBlock.length - 33) / 32;
  for (let j = 0; j < loops; j++) {
    const branch = controlBlock.subarray(33 + 32 * j, 65 + 32 * j);

    if (Buffer.compare(tapLeafHash, branch) < 0) {
      tapLeafHash = bitcoin.crypto.taggedHash(
        'TapBranch',
        Buffer.concat([tapLeafHash, branch]),
      );
    } else {
      tapLeafHash = bitcoin.crypto.taggedHash(
        'TapBranch',
        Buffer.concat([branch, tapLeafHash]),
      );
    }
  }

  const final = bitcoin.crypto.taggedHash(
    'TapTweak',
    Buffer.concat([internalPubkey, tapLeafHash]),
  );
  if (!ecc.isPrivate(final)) {
    throw new Error(
      'Rare error. Final taptree hash was higher than curve order',
    );
  }
  const result = ecc.xOnlyPointAddTweak(internalPubkey, final);
  if (result === null) {
    throw new Error('Error when tweaking');
  }

  return bitcoin.address.fromOutputScript(
    Buffer.concat([Buffer.from([0x51, 0x20]), Buffer.from(result.xOnlyPubkey)]),
  );
}
saurabhburade commented 6 months ago

@junderw Unable to recover address from https://mempool.space/tx/1bc6814dfab83f468e7ba4a24694974f1e33744e6b6ea0518ec6e99c80ff1016

junderw commented 6 months ago

If you're not going to read my replies, posts, and comments, then I am wasting my time here.

Good luck with whatever it is you're doing.

https://github.com/bitcoinjs/bitcoinjs-lib/issues/2088#issuecomment-2079221481 (Read this reply to your issue)