@covenance/dlc
Version:
Crypto and Bitcoin functions for Covenance DLC implementation
107 lines (95 loc) • 3.28 kB
text/typescript
import { Point, CURVE } from './secp256k1';
import { PubKey, Signature } from './types';
import { sha256, bytesToHex } from '../utils';
/**
* Function to verify a general signature
* @param sig - The signature to verify
* @param pubKey - The public key to verify against
* @param message - The message that was signed
* @param tag - The tag to use for hashing (defaults to BIP0340/challenge)
* @returns Boolean indicating whether the signature is valid
*/
export async function verifySig(
sig: Signature,
pubKey: PubKey,
message: Uint8Array,
tag: Uint8Array = Uint8Array.from(Buffer.from('7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c', 'hex')) // SHA256('BIP0340/challenge')
): Promise<boolean> {
// Compute H(tag||tag||R||P||m)
const hashInput = new Uint8Array([
...tag,
...tag,
...sig.R.toRawBytes(true).slice(1),
...pubKey.toRawBytes(true).slice(1),
...message
]);
const hash = await sha256(hashInput);
const e = mod(BigInt('0x' + bytesToHex(hash)), CURVE.n);
// Compute s * G
const sG = Point.BASE.multiply(sig.s);
// Compute R + e * P
const rightSide = sig.R.add(pubKey.multiply(e));
// Verify s * G = R + e * P
return sG.equals(rightSide);
}
/**
* Function to verify a signature with additional script interpreter like verification
* @param sig - The signature to verify
* @param pubKey - The public key to verify against
* @param message - The message that was signed
* @param tag - The tag to use for hashing (defaults to BIP0340/challenge)
* @returns Boolean indicating whether the signature is valid
*/
export async function verifySigStrict(
sig: Signature,
pubKey: PubKey,
message: Uint8Array,
tag: Uint8Array = Uint8Array.from(Buffer.from('7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c', 'hex')) // SHA256('BIP0340/challenge')
): Promise<boolean> {
// First do basic signature verification
const basicVerification = await verifySig(sig, pubKey, message, tag);
if (!basicVerification) return false;
// Perform additional script interpreter like verification
const r = sig.R.x;
const s = sig.s;
if (r >= CURVE.P || s >= CURVE.n) {
return false;
}
const hashInput = new Uint8Array([
...tag,
...tag,
...sig.R.toRawBytes(true).slice(1),
...pubKey.toRawBytes(true).slice(1),
...message
]);
const hash = await sha256(hashInput);
const e = mod(BigInt('0x' + bytesToHex(hash)), CURVE.n);
const sG = Point.BASE.multiply(sig.s);
const R = sG.add(pubKey.multiply(e).negate());
if (
!(R.y % 2n === 0n) ||
!(R.x === r)
) {
return false;
}
return true;
}
/**
* Encodes a point as a BIP-340 x-only key
* @param point - The point to encode
* @returns The encoded point as a 32-byte Buffer
*/
export function encodeXOnlyPubkey({ x, y }: Point): Buffer {
if (y & 1n) y = CURVE.P - y; // even-y representative
if (x < 0n || x >= CURVE.P) throw new RangeError("x out of range");
return Buffer.from(x.toString(16).padStart(64, "0"), "hex");
}
/**
* Modulus operation that handles negative numbers
* @param k - The number to mod
* @param n - The modulus
* @returns The modulus of k and n
*/
export function mod(k: bigint, n: bigint): bigint {
return ((k % n) + n) % n;
}