Add fake pubkey filter
This commit is contained in:
@@ -6,6 +6,7 @@ import { NodeSocket } from '../repositories/NodesSocketsRepository';
|
||||
import { isIP } from 'net';
|
||||
import rbfCache from './rbf-cache';
|
||||
import transactionUtils from './transaction-utils';
|
||||
import { isPoint } from '../utils/secp256k1';
|
||||
export class Common {
|
||||
static nativeAssetId = config.MEMPOOL.NETWORK === 'liquidtestnet' ?
|
||||
'144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49'
|
||||
@@ -211,6 +212,15 @@ export class Common {
|
||||
}
|
||||
}
|
||||
|
||||
static isBurnKey(pubkey: string): boolean {
|
||||
return [
|
||||
'022222222222222222222222222222222222222222222222222222222222222222',
|
||||
'033333333333333333333333333333333333333333333333333333333333333333',
|
||||
'020202020202020202020202020202020202020202020202020202020202020202',
|
||||
'030303030303030303030303030303030303030303030303030303030303030303',
|
||||
].includes(pubkey);
|
||||
}
|
||||
|
||||
static getTransactionFlags(tx: TransactionExtended): number {
|
||||
let flags = 0n;
|
||||
if (tx.version === 1) {
|
||||
@@ -249,8 +259,8 @@ export class Common {
|
||||
flags |= this.setSchnorrSighashFlags(flags, vin.witness);
|
||||
} else if (vin.witness) {
|
||||
flags |= this.setSegwitSighashFlags(flags, vin.witness);
|
||||
} else if (vin.scriptsig_asm) {
|
||||
flags |= this.setLegacySighashFlags(flags, vin.scriptsig_asm);
|
||||
} else if (vin.scriptsig?.length) {
|
||||
flags |= this.setLegacySighashFlags(flags, vin.scriptsig_asm || transactionUtils.convertScriptSigAsm(vin.scriptsig));
|
||||
}
|
||||
|
||||
if (vin.prevout?.scriptpubkey_address) {
|
||||
@@ -263,12 +273,23 @@ export class Common {
|
||||
} else {
|
||||
flags |= TransactionFlags.no_rbf;
|
||||
}
|
||||
let hasFakePubkey = false;
|
||||
for (const vout of tx.vout) {
|
||||
switch (vout.scriptpubkey_type) {
|
||||
case 'p2pk': flags |= TransactionFlags.p2pk; break;
|
||||
case 'p2pk': {
|
||||
flags |= TransactionFlags.p2pk;
|
||||
// detect fake pubkey (i.e. not a valid DER point on the secp256k1 curve)
|
||||
hasFakePubkey = hasFakePubkey || !isPoint(vout.scriptpubkey.slice(2, -2));
|
||||
} break;
|
||||
case 'multisig': {
|
||||
flags |= TransactionFlags.p2ms;
|
||||
// TODO - detect fake multisig data embedding
|
||||
// detect fake pubkeys (i.e. not valid DER points on the secp256k1 curve)
|
||||
const asm = vout.scriptpubkey_asm || transactionUtils.convertScriptSigAsm(vout.scriptpubkey);
|
||||
for (const key of (asm?.split(' ') || [])) {
|
||||
if (!hasFakePubkey && !key.startsWith('OP_')) {
|
||||
hasFakePubkey = hasFakePubkey || this.isBurnKey(key) || !isPoint(key);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case 'p2pkh': flags |= TransactionFlags.p2pkh; break;
|
||||
case 'p2sh': flags |= TransactionFlags.p2sh; break;
|
||||
@@ -282,6 +303,9 @@ export class Common {
|
||||
}
|
||||
outValues[vout.value || Math.random()] = (outValues[vout.value || Math.random()] || 0) + 1;
|
||||
}
|
||||
if (hasFakePubkey) {
|
||||
flags |= TransactionFlags.fake_pubkey;
|
||||
}
|
||||
if (tx.ancestors?.length) {
|
||||
flags |= TransactionFlags.cpfp_child;
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ export const TransactionFlags = {
|
||||
replacement: 0b00000100_00000000_00000000n,
|
||||
// data
|
||||
op_return: 0b00000001_00000000_00000000_00000000n,
|
||||
fake_multisig: 0b00000010_00000000_00000000_00000000n,
|
||||
fake_pubkey: 0b00000010_00000000_00000000_00000000n,
|
||||
inscription: 0b00000100_00000000_00000000_00000000n,
|
||||
// heuristics
|
||||
coinjoin: 0b00000001_00000000_00000000_00000000_00000000n,
|
||||
|
||||
74
backend/src/utils/secp256k1.ts
Normal file
74
backend/src/utils/secp256k1.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
function powMod(x: bigint, power: number, modulo: bigint): bigint {
|
||||
for (let i = 0; i < power; i++) {
|
||||
x = (x * x) % modulo;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
function sqrtMod(x: bigint, P: bigint): bigint {
|
||||
const b2 = (x * x * x) % P;
|
||||
const b3 = (b2 * b2 * x) % P;
|
||||
const b6 = (powMod(b3, 3, P) * b3) % P;
|
||||
const b9 = (powMod(b6, 3, P) * b3) % P;
|
||||
const b11 = (powMod(b9, 2, P) * b2) % P;
|
||||
const b22 = (powMod(b11, 11, P) * b11) % P;
|
||||
const b44 = (powMod(b22, 22, P) * b22) % P;
|
||||
const b88 = (powMod(b44, 44, P) * b44) % P;
|
||||
const b176 = (powMod(b88, 88, P) * b88) % P;
|
||||
const b220 = (powMod(b176, 44, P) * b44) % P;
|
||||
const b223 = (powMod(b220, 3, P) * b3) % P;
|
||||
const t1 = (powMod(b223, 23, P) * b22) % P;
|
||||
const t2 = (powMod(t1, 6, P) * b2) % P;
|
||||
const root = powMod(t2, 2, P);
|
||||
return root;
|
||||
}
|
||||
|
||||
const curveP = BigInt(`0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F`);
|
||||
|
||||
/**
|
||||
* This function tells whether the point given is a DER encoded point on the ECDSA curve.
|
||||
* @param {string} pointHex The point as a hex string (*must not* include a '0x' prefix)
|
||||
* @returns {boolean} true if the point is on the SECP256K1 curve
|
||||
*/
|
||||
export function isPoint(pointHex: string): boolean {
|
||||
if (
|
||||
!(
|
||||
// is uncompressed
|
||||
(
|
||||
(pointHex.length === 130 && pointHex.startsWith('04')) ||
|
||||
// OR is compressed
|
||||
(pointHex.length === 66 &&
|
||||
(pointHex.startsWith('02') || pointHex.startsWith('03')))
|
||||
)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Function modified slightly from noble-curves
|
||||
|
||||
|
||||
// Now we know that pointHex is a 33 or 65 byte hex string.
|
||||
const isCompressed = pointHex.length === 66;
|
||||
|
||||
const x = BigInt(`0x${pointHex.slice(2, 66)}`);
|
||||
if (x >= curveP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isCompressed) {
|
||||
const y = BigInt(`0x${pointHex.slice(66, 130)}`);
|
||||
if (y >= curveP) {
|
||||
return false;
|
||||
}
|
||||
// Just check y^2 = x^3 + 7 (secp256k1 curve)
|
||||
return (y * y) % curveP === (x * x * x + 7n) % curveP;
|
||||
} else {
|
||||
// Get unaltered y^2 (no mod p)
|
||||
const ySquared = (x * x * x + 7n) % curveP;
|
||||
// Try to sqrt it, it will round down if not perfect root
|
||||
const y = sqrtMod(ySquared, curveP);
|
||||
// If we square and it's equal, then it was a perfect root and valid point.
|
||||
return (y * y) % curveP === ySquared;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user