Add fake pubkey filter
This commit is contained in:
		
							parent
							
								
									ce195c9133
								
							
						
					
					
						commit
						512589dc79
					
				@ -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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -29,7 +29,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,
 | 
			
		||||
@ -64,7 +64,7 @@ export const TransactionFilters: { [key: string]: Filter } = {
 | 
			
		||||
    replacement: { key: 'replacement', label: 'Replacement', flag: TransactionFlags.replacement, important: true },
 | 
			
		||||
    /* data */
 | 
			
		||||
    op_return: { key: 'op_return', label: 'OP_RETURN', flag: TransactionFlags.op_return, important: true },
 | 
			
		||||
    // fake_multisig: { key: 'fake_multisig', label: 'Fake multisig', flag: TransactionFlags.fake_multisig },
 | 
			
		||||
    fake_pubkey: { key: 'fake_pubkey', label: 'Fake pubkey', flag: TransactionFlags.fake_pubkey },
 | 
			
		||||
    inscription: { key: 'inscription', label: 'Inscription', flag: TransactionFlags.inscription, important: true },
 | 
			
		||||
    /* heuristics */
 | 
			
		||||
    coinjoin: { key: 'coinjoin', label: 'Coinjoin', flag: TransactionFlags.coinjoin, important: true },
 | 
			
		||||
@ -82,7 +82,7 @@ export const FilterGroups: { label: string, filters: Filter[]}[] = [
 | 
			
		||||
  { label: 'Features', filters: ['rbf', 'no_rbf', 'v1', 'v2', 'multisig'] },
 | 
			
		||||
  { label: 'Address Types', filters: ['p2pk', 'p2ms', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'] },
 | 
			
		||||
  { label: 'Behavior', filters: ['cpfp_parent', 'cpfp_child', 'replacement'] },
 | 
			
		||||
  { label: 'Data', filters: ['op_return', 'fake_multisig', 'inscription'] },
 | 
			
		||||
  { label: 'Data', filters: ['op_return', 'fake_pubkey', 'inscription'] },
 | 
			
		||||
  { label: 'Heuristics', filters: ['coinjoin', 'consolidation', 'batch_payout'] },
 | 
			
		||||
  { label: 'Sighash Flags', filters: ['sighash_all', 'sighash_none', 'sighash_single', 'sighash_default', 'sighash_acp'] },
 | 
			
		||||
].map(group => ({ label: group.label, filters: group.filters.map(filter => TransactionFilters[filter] || null).filter(f => f != null) }));
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user