mirror of
https://github.com/mempool/mempool.git
synced 2025-10-10 00:33:08 +02:00
use stricter format for detecting DER signatures
This commit is contained in:
@@ -86,6 +86,54 @@ export function isDERSig(w: string): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enforce canonical DER-encoded signature format
|
||||||
|
// <0x30> <total len> <0x02> <len R> <R> <0x02> <len S> <S> <hashtype>
|
||||||
|
// see https://github.com/bitcoin/bitcoin/blob/9a05b45da60d214cb1e5a50c3d2293b1defc9bb0/src/script/interpreter.cpp#L97-L106
|
||||||
|
export function isCanonicalDERSig(w: string): boolean {
|
||||||
|
// minimum DER signature length is 8 bytes + sighash flag (see https://mempool.space/testnet/tx/c6c232a36395fa338da458b86ff1327395a9afc28c5d2daa4273e410089fd433)
|
||||||
|
if (w.length < 18) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first byte is 0x30 ("SEQUENCE")
|
||||||
|
if (!w.startsWith('30')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// second byte encodes the total length of the sequence (not including sighash flag)
|
||||||
|
const compoundLength = parseInt(w.slice(2, 4), 16);
|
||||||
|
if (w.length !== (compoundLength * 2) + 6) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// third byte is 0x02 ("INTEGER")
|
||||||
|
if (w.slice(4, 6) !== '02') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fourth byte encodes the length of the R component
|
||||||
|
const rLength = parseInt(w.slice(6, 8), 16);
|
||||||
|
// rLength doesn't overflow remaining space
|
||||||
|
if (w.length < (rLength * 2) + 10) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const sEnd = 8 + (rLength * 2);
|
||||||
|
|
||||||
|
// next byte after R is 0x02 ("INTEGER")
|
||||||
|
if (w.slice(sEnd, sEnd + 2) !== '02') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// next byte encodes the length of the S component
|
||||||
|
const sLength = parseInt(w.slice(sEnd + 2, sEnd + 4), 16);
|
||||||
|
// R + S lengths exactly fit the length of the signature
|
||||||
|
if (w.length !== ((rLength + sLength) * 2) + 14) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export enum SighashFlag {
|
export enum SighashFlag {
|
||||||
DEFAULT = 0,
|
DEFAULT = 0,
|
||||||
ALL = 1,
|
ALL = 1,
|
||||||
@@ -156,7 +204,7 @@ export function extractDERSignaturesWitness(witness: string[]): SigInfo[] {
|
|||||||
const signatures: SigInfo[] = [];
|
const signatures: SigInfo[] = [];
|
||||||
|
|
||||||
for (const w of witness) {
|
for (const w of witness) {
|
||||||
if (isDERSig(w)) {
|
if (isCanonicalDERSig(w)) {
|
||||||
signatures.push({
|
signatures.push({
|
||||||
signature: w,
|
signature: w,
|
||||||
sighash: decodeSighashFlag(parseInt(w.slice(-2), 16)),
|
sighash: decodeSighashFlag(parseInt(w.slice(-2), 16)),
|
||||||
@@ -179,7 +227,7 @@ export function extractDERSignaturesASM(script_asm: string): SigInfo[] {
|
|||||||
// Look for OP_PUSHBYTES_N followed by a hex string
|
// Look for OP_PUSHBYTES_N followed by a hex string
|
||||||
if (ops[i].startsWith('OP_PUSHBYTES_')) {
|
if (ops[i].startsWith('OP_PUSHBYTES_')) {
|
||||||
const hexData = ops[i + 1];
|
const hexData = ops[i + 1];
|
||||||
if (isDERSig(hexData)) {
|
if (isCanonicalDERSig(hexData)) {
|
||||||
const sighash = decodeSighashFlag(parseInt(hexData.slice(-2), 16));
|
const sighash = decodeSighashFlag(parseInt(hexData.slice(-2), 16));
|
||||||
signatures.push({
|
signatures.push({
|
||||||
signature: hexData,
|
signature: hexData,
|
||||||
@@ -225,9 +273,13 @@ export function processInputSignatures(vin: Vin): SigInfo[] {
|
|||||||
case 'p2pkh':
|
case 'p2pkh':
|
||||||
signatures = extractDERSignaturesASM(vin.scriptsig_asm);
|
signatures = extractDERSignaturesASM(vin.scriptsig_asm);
|
||||||
break;
|
break;
|
||||||
case 'p2sh':
|
case 'p2sh': {
|
||||||
signatures = [...extractDERSignaturesASM(vin.scriptsig_asm), ...extractDERSignaturesASM(vin.inner_redeemscript_asm), ...extractDERSignaturesWitness(vin.witness || [])];
|
if (vin.witness?.length) {
|
||||||
break;
|
signatures = extractDERSignaturesWitness(vin.witness || []);
|
||||||
|
} else {
|
||||||
|
signatures = [...extractDERSignaturesASM(vin.scriptsig_asm), ...extractDERSignaturesASM(vin.inner_redeemscript_asm)];
|
||||||
|
}
|
||||||
|
} break;
|
||||||
case 'v0_p2wpkh':
|
case 'v0_p2wpkh':
|
||||||
signatures = extractDERSignaturesWitness(vin.witness || []);
|
signatures = extractDERSignaturesWitness(vin.witness || []);
|
||||||
break;
|
break;
|
||||||
@@ -464,7 +516,7 @@ export function getNonWitnessSize(tx: Transaction): number {
|
|||||||
|
|
||||||
export function setSegwitSighashFlags(flags: bigint, witness: string[]): bigint {
|
export function setSegwitSighashFlags(flags: bigint, witness: string[]): bigint {
|
||||||
for (const w of witness) {
|
for (const w of witness) {
|
||||||
if (isDERSig(w)) {
|
if (isCanonicalDERSig(w)) {
|
||||||
flags |= setSighashFlags(flags, w);
|
flags |= setSighashFlags(flags, w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -478,7 +530,7 @@ export function setLegacySighashFlags(flags: bigint, scriptsig_asm: string): big
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// check pushed data
|
// check pushed data
|
||||||
if (isDERSig(item)) {
|
if (isCanonicalDERSig(item)) {
|
||||||
flags |= setSighashFlags(flags, item);
|
flags |= setSighashFlags(flags, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user