mempool/frontend/src/app/shared/script.utils.ts

281 lines
6.0 KiB
TypeScript
Raw Normal View History

const opcodes = {
OP_FALSE: 0,
OP_0: 0,
OP_PUSHDATA1: 76,
OP_PUSHDATA2: 77,
OP_PUSHDATA4: 78,
OP_1NEGATE: 79,
OP_PUSHNUM_NEG1: 79,
OP_RESERVED: 80,
OP_TRUE: 81,
OP_1: 81,
OP_2: 82,
OP_3: 83,
OP_4: 84,
OP_5: 85,
OP_6: 86,
OP_7: 87,
OP_8: 88,
OP_9: 89,
OP_10: 90,
OP_11: 91,
OP_12: 92,
OP_13: 93,
OP_14: 94,
OP_15: 95,
OP_16: 96,
OP_PUSHNUM_1: 81,
OP_PUSHNUM_2: 82,
OP_PUSHNUM_3: 83,
OP_PUSHNUM_4: 84,
OP_PUSHNUM_5: 85,
OP_PUSHNUM_6: 86,
OP_PUSHNUM_7: 87,
OP_PUSHNUM_8: 88,
OP_PUSHNUM_9: 89,
OP_PUSHNUM_10: 90,
OP_PUSHNUM_11: 91,
OP_PUSHNUM_12: 92,
OP_PUSHNUM_13: 93,
OP_PUSHNUM_14: 94,
OP_PUSHNUM_15: 95,
OP_PUSHNUM_16: 96,
OP_NOP: 97,
OP_VER: 98,
OP_IF: 99,
OP_NOTIF: 100,
OP_VERIF: 101,
OP_VERNOTIF: 102,
OP_ELSE: 103,
OP_ENDIF: 104,
OP_VERIFY: 105,
OP_RETURN: 106,
OP_TOALTSTACK: 107,
OP_FROMALTSTACK: 108,
OP_2DROP: 109,
OP_2DUP: 110,
OP_3DUP: 111,
OP_2OVER: 112,
OP_2ROT: 113,
OP_2SWAP: 114,
OP_IFDUP: 115,
OP_DEPTH: 116,
OP_DROP: 117,
OP_DUP: 118,
OP_NIP: 119,
OP_OVER: 120,
OP_PICK: 121,
OP_ROLL: 122,
OP_ROT: 123,
OP_SWAP: 124,
OP_TUCK: 125,
OP_CAT: 126,
OP_SUBSTR: 127,
OP_LEFT: 128,
OP_RIGHT: 129,
OP_SIZE: 130,
OP_INVERT: 131,
OP_AND: 132,
OP_OR: 133,
OP_XOR: 134,
OP_EQUAL: 135,
OP_EQUALVERIFY: 136,
OP_RESERVED1: 137,
OP_RESERVED2: 138,
OP_1ADD: 139,
OP_1SUB: 140,
OP_2MUL: 141,
OP_2DIV: 142,
OP_NEGATE: 143,
OP_ABS: 144,
OP_NOT: 145,
OP_0NOTEQUAL: 146,
OP_ADD: 147,
OP_SUB: 148,
OP_MUL: 149,
OP_DIV: 150,
OP_MOD: 151,
OP_LSHIFT: 152,
OP_RSHIFT: 153,
OP_BOOLAND: 154,
OP_BOOLOR: 155,
OP_NUMEQUAL: 156,
OP_NUMEQUALVERIFY: 157,
OP_NUMNOTEQUAL: 158,
OP_LESSTHAN: 159,
OP_GREATERTHAN: 160,
OP_LESSTHANOREQUAL: 161,
OP_GREATERTHANOREQUAL: 162,
OP_MIN: 163,
OP_MAX: 164,
OP_WITHIN: 165,
OP_RIPEMD160: 166,
OP_SHA1: 167,
OP_SHA256: 168,
OP_HASH160: 169,
OP_HASH256: 170,
OP_CODESEPARATOR: 171,
OP_CHECKSIG: 172,
OP_CHECKSIGVERIFY: 173,
OP_CHECKMULTISIG: 174,
OP_CHECKMULTISIGVERIFY: 175,
OP_NOP1: 176,
OP_NOP2: 177,
OP_CHECKLOCKTIMEVERIFY: 177,
OP_CLTV: 177,
OP_NOP3: 178,
OP_CHECKSEQUENCEVERIFY: 178,
OP_CSV: 178,
OP_NOP4: 179,
OP_NOP5: 180,
OP_NOP6: 181,
OP_NOP7: 182,
OP_NOP8: 183,
OP_NOP9: 184,
OP_NOP10: 185,
OP_CHECKSIGADD: 186,
OP_PUBKEYHASH: 253,
OP_PUBKEY: 254,
OP_INVALIDOPCODE: 255,
};
// add unused opcodes
for (let i = 187; i <= 255; i++) {
opcodes[`OP_RETURN_${i}`] = i;
}
export { opcodes };
/** extracts m and n from a multisig script (asm), returns nothing if it is not a multisig script */
export function parseMultisigScript(script: string): void | { m: number, n: number } {
if (!script) {
return;
}
const ops = script.split(' ');
if (ops.length < 3 || ops.pop() !== 'OP_CHECKMULTISIG') {
return;
}
const opN = ops.pop();
if (!opN) {
return;
}
if (!opN.startsWith('OP_PUSHNUM_')) {
return;
}
const n = parseInt(opN.match(/[0-9]+/)?.[0] || '', 10);
if (ops.length < n * 2 + 1) {
return;
}
// pop n public keys
for (let i = 0; i < n; i++) {
if (!/^0((2|3)\w{64}|4\w{128})$/.test(ops.pop() || '')) {
return;
}
if (!/^OP_PUSHBYTES_(33|65)$/.test(ops.pop() || '')) {
return;
}
}
const opM = ops.pop();
if (!opM) {
return;
}
if (!opM.startsWith('OP_PUSHNUM_')) {
return;
}
const m = parseInt(opM.match(/[0-9]+/)?.[0] || '', 10);
if (ops.length) {
return;
}
return { m, n };
}
export function getVarIntLength(n: number): number {
if (n < 0xfd) {
return 1;
} else if (n <= 0xffff) {
return 3;
} else if (n <= 0xffffffff) {
return 5;
} else {
return 9;
}
}
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 (!pointHex?.length) {
return false;
}
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;
}
}