diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index c1c998fb8..1d3b68541 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 { 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 6803ef489..9d9cd801b 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;