From 7432e6e29bf26d31d8680f802be36841378602d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Strnad?= <43024885+vostrnad@users.noreply.github.com> Date: Mon, 1 Jul 2024 02:02:53 +0200 Subject: [PATCH 1/2] Fix errors caused by P2TR inputs without witness data --- frontend/src/app/bitcoin.utils.ts | 31 ++++++++++++-------- frontend/src/app/shared/transaction.utils.ts | 26 ++++++++-------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/frontend/src/app/bitcoin.utils.ts b/frontend/src/app/bitcoin.utils.ts index 9d9091842..92d3de7f3 100644 --- a/frontend/src/app/bitcoin.utils.ts +++ b/frontend/src/app/bitcoin.utils.ts @@ -71,19 +71,24 @@ export function calcSegwitFeeGains(tx: Transaction) { } if (isP2tr) { - if (vin.witness.length === 1) { - // key path spend - // we don't know if this was a multisig or single sig (the goal of taproot :)), - // so calculate fee savings by comparing to the cheapest single sig input type: P2WPKH and say "saved at least ...%" - // the witness size of P2WPKH is 1 (stack size) + 1 (size) + 72 (low s signature) + 1 (size) + 33 (pubkey) = 108 WU - // the witness size of key path P2TR is 1 (stack size) + 1 (size) + 64 (signature) = 66 WU - realizedTaprootGains += 42; - } else { - // script path spend - // complex scripts with multiple spending paths can often be made around 2x to 3x smaller with the Taproot script tree - // because only the hash of the alternative spending path has the be in the witness data, not the entire script, - // but only assumptions can be made because the scripts themselves are unknown (again, the goal of taproot :)) - // TODO maybe add some complex scripts that are specified somewhere, so that size is known, such as lightning scripts + // every valid taproot input has at least one witness item, however transactions + // created before taproot activation don't need to have any witness data + // (see https://mempool.space/tx/b10c007c60e14f9d087e0291d4d0c7869697c6681d979c6639dbd960792b4d41) + if (vin.witness?.length) { + if (vin.witness.length === 1) { + // key path spend + // we don't know if this was a multisig or single sig (the goal of taproot :)), + // so calculate fee savings by comparing to the cheapest single sig input type: P2WPKH and say "saved at least ...%" + // the witness size of P2WPKH is 1 (stack size) + 1 (size) + 72 (low s signature) + 1 (size) + 33 (pubkey) = 108 WU + // the witness size of key path P2TR is 1 (stack size) + 1 (size) + 64 (signature) = 66 WU + realizedTaprootGains += 42; + } else { + // script path spend + // complex scripts with multiple spending paths can often be made around 2x to 3x smaller with the Taproot script tree + // because only the hash of the alternative spending path has the be in the witness data, not the entire script, + // but only assumptions can be made because the scripts themselves are unknown (again, the goal of taproot :)) + // TODO maybe add some complex scripts that are specified somewhere, so that size is known, such as lightning scripts + } } } else { const script = isP2shP2Wsh || isP2wsh ? vin.inner_witnessscript_asm : vin.inner_redeemscript_asm; diff --git a/frontend/src/app/shared/transaction.utils.ts b/frontend/src/app/shared/transaction.utils.ts index 7bc986330..0e3922140 100644 --- a/frontend/src/app/shared/transaction.utils.ts +++ b/frontend/src/app/shared/transaction.utils.ts @@ -335,19 +335,21 @@ export function getTransactionFlags(tx: Transaction, cpfpInfo?: CpfpInfo, replac case 'v0_p2wpkh': flags |= TransactionFlags.p2wpkh; break; case 'v0_p2wsh': flags |= TransactionFlags.p2wsh; break; case 'v1_p2tr': { - if (!vin.witness?.length) { - throw new Error('Taproot input missing witness data'); - } flags |= TransactionFlags.p2tr; - // in taproot, if the last witness item begins with 0x50, it's an annex - const hasAnnex = vin.witness?.[vin.witness.length - 1].startsWith('50'); - // script spends have more than one witness item, not counting the annex (if present) - if (vin.witness.length > (hasAnnex ? 2 : 1)) { - // the script itself is the second-to-last witness item, not counting the annex - const asm = vin.inner_witnessscript_asm; - // inscriptions smuggle data within an 'OP_0 OP_IF ... OP_ENDIF' envelope - if (asm?.includes('OP_0 OP_IF')) { - flags |= TransactionFlags.inscription; + // every valid taproot input has at least one witness item, however transactions + // created before taproot activation don't need to have any witness data + // (see https://mempool.space/tx/b10c007c60e14f9d087e0291d4d0c7869697c6681d979c6639dbd960792b4d41) + if (vin.witness?.length) { + // in taproot, if the last witness item begins with 0x50, it's an annex + const hasAnnex = vin.witness?.[vin.witness.length - 1].startsWith('50'); + // script spends have more than one witness item, not counting the annex (if present) + if (vin.witness.length > (hasAnnex ? 2 : 1)) { + // the script itself is the second-to-last witness item, not counting the annex + const asm = vin.inner_witnessscript_asm; + // inscriptions smuggle data within an 'OP_0 OP_IF ... OP_ENDIF' envelope + if (asm?.includes('OP_0 OP_IF')) { + flags |= TransactionFlags.inscription; + } } } } break; From 27c70bd9196bd79b51350810445eba8e46e61bd1 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 1 Jul 2024 01:22:10 +0000 Subject: [PATCH 2/2] Also fix backend errors caused by P2TR inputs without witness data --- backend/src/api/common.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 04f380418..07fc4830d 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -460,11 +460,10 @@ export class Common { case 'v0_p2wpkh': flags |= TransactionFlags.p2wpkh; break; case 'v0_p2wsh': flags |= TransactionFlags.p2wsh; break; case 'v1_p2tr': { - if (!vin.witness?.length) { - throw new Error('Taproot input missing witness data'); - } flags |= TransactionFlags.p2tr; - flags = Common.isInscription(vin, flags); + if (vin.witness?.length) { + flags = Common.isInscription(vin, flags); + } } break; } } else {