@covenance/dlc
Version:
Crypto and Bitcoin functions for Covenance DLC implementation
131 lines (114 loc) • 4.61 kB
text/typescript
import { Point, utils, CURVE } from './secp256k1';
import { PrivKey, PubKey, Signature, AdaptorSignature, Sighash } from './types';
import { sha256, bytesToHex } from '../utils';
import { mod } from './general';
/**
* Counterparty function to create an adaptor signature for a specific event outcome
* @param counterpartyPrivKey - Counterparty's private key
* @param oracleSigPoint - Oracle's signature point for the event outcome
* @param cetSighash - Sighash of the event outcome CET
* @returns Counterparty's adaptor signature
*/
export async function createAdaptorSig(
counterpartyPrivKey: PrivKey,
oracleSigPoint: Point,
cetSighash: Sighash,
tag: Uint8Array = Uint8Array.from(Buffer.from('7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c', 'hex')) // SHA256('BIP0340/challenge')
): Promise<AdaptorSignature> {
let r_cp: Uint8Array;
let r_cpBigInt: bigint;
let R_cp: Point;
let R_prime_cp: Point;
// Generate nonce UNTIL (R_cp + S_i).y is even
do {
// Generate random nonce
r_cp = utils.randomPrivateKey();
r_cpBigInt = BigInt('0x' + bytesToHex(r_cp));
// Compute R_cp = r_cp * G
R_cp = Point.fromPrivateKey(r_cpBigInt);
// Compute R'_cp = R_cp + S_i
R_prime_cp = R_cp.add(oracleSigPoint);
} while ((R_prime_cp.y & 1n) === 1n);
// Fail if r = 0
if (r_cpBigInt === 0n) {
throw new Error('Generated nonce is zero');
}
// Get counterparty's public key
const P_cp = Point.fromPrivateKey(counterpartyPrivKey);
// Compute H(tag||tag||R'_cp||P_cp||cet_i)
const hashInput = new Uint8Array([
...tag,
...tag,
...R_prime_cp.toRawBytes(true).slice(1),
...P_cp.toRawBytes(true).slice(1),
...cetSighash
]);
const hash = await sha256(hashInput);
const e = mod(BigInt('0x' + bytesToHex(hash)), CURVE.n);
// Compute s'_cp = r_cp + H(R'_cp||P_cp||cet_i)x_cp
const x_cp = BigInt('0x' + bytesToHex(counterpartyPrivKey));
const s_prime_cp = mod(r_cpBigInt + e * x_cp, CURVE.n);
return { R_prime: R_prime_cp, s_prime: s_prime_cp};
}
/**
* Counterparty function to adapt (finalize) an adaptor signature
* @param adaptorSig - The adaptor signature to finalize
* @param s - The oracle's scalar value (s) from their signature or repayment secret
* @returns The final signature
*/
export function adaptSig(
adaptorSig: AdaptorSignature,
s: bigint
): Signature {
// Compute s_cp = s'_cp + s
const s_cp = adaptorSig.s_prime + s;
return { R: adaptorSig.R_prime, s: mod(s_cp, CURVE.n) };
}
/**
* Function to verify an adaptor signature
* @param adaptorSig - The adaptor signature to verify
* @param counterpartyPubKey - The counterparty's public key
* @param cetSighash - The sighash of the event outcome CET
* @param oracleSigPoint - The oracle's signature point for the event outcome
* @returns Boolean indicating whether the adaptor signature is valid
*/
export async function verifyAdaptorSig(
adaptorSig: AdaptorSignature,
counterpartyPubKey: PubKey,
cetSighash: Sighash,
oracleSigPoint: Point,
tag: Uint8Array = Uint8Array.from(Buffer.from('7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c', 'hex')) // SHA256('BIP0340/challenge')
): Promise<boolean> {
// Compute H(tag||tag||R'_cp||P_cp||cet_i)
const hashInput = new Uint8Array([
...tag,
...tag,
...adaptorSig.R_prime.toRawBytes(true).slice(1),
...counterpartyPubKey.toRawBytes(true).slice(1),
...cetSighash
]);
const hash = await sha256(hashInput);
const e = mod(BigInt('0x' + bytesToHex(hash)), CURVE.n);
// Compute s'_cp * G
const sG = Point.BASE.multiply(adaptorSig.s_prime);
// Compute R_cp = R'_cp - S_i
const R_cp = adaptorSig.R_prime.subtract(oracleSigPoint);
// Compute R_cp + e * P_cp
const rightSide = R_cp.add(counterpartyPubKey.multiply(e));
// Verify s'_cp * G = R_cp + e * P_cp
return sG.equals(rightSide);
}
/**
* Function to generate a private key which produces a public key with even y.
* This is to avoid doing BIP-340 secret-key parity adjustment (flip if public key has odd Y) each time an adaptor signature is created.
* @returns A private key which produces a public key with even y.
*/
export function generateEvenYPrivateKey(): PrivKey {
let privKey = BigInt('0x' + bytesToHex(utils.randomPrivateKey()));
let pubKey = Point.fromPrivateKey(privKey);
if ((pubKey.y & 1n) === 1n) {
privKey = CURVE.n - privKey; // negate secret
pubKey = Point.fromPrivateKey(privKey); // now pubKey.y is even
}
return Buffer.from(privKey.toString(16).padStart(64, "0"), "hex");
}