2023-12-14 11:26:17 +00:00
|
|
|
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 {
|
2024-01-30 16:41:35 +00:00
|
|
|
if (!pointHex?.length) {
|
|
|
|
return false;
|
|
|
|
}
|
2023-12-14 11:26:17 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|