How to recover btc address from txn witness data?
saurabhburade opened this issue · 5 comments
saurabhburade commented
- Old reference #1104
- I have an array of witness hex data like this https://mempool.space/tx/2bb85f4b004be6da54f766c17c1e855187327112c231ef2ff35ebad0ea67c69e
- I want to recover the address
bc1pkqndh95dk*******uleyx4pqq7x4tl
junderw commented
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
To be specific:
- P2WPKH: The last item on the witness stack is the pubkey, HASH160 it and use bech32 with segwit version 0 to encode.
- P2WSH: The last item on the witness stack is the witnessScript, SHA256 it and use bech32 with segwit version 0 to encode.
- 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
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
@junderw Unable to recover address from
https://mempool.space/tx/1bc6814dfab83f468e7ba4a24694974f1e33744e6b6ea0518ec6e99c80ff1016
junderw commented
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.
#2088 (comment) (Read this reply to your issue)