@covenance/dlc
Version:
Crypto and Bitcoin functions for Covenance DLC implementation
207 lines (175 loc) • 7.32 kB
text/typescript
import { expect } from 'chai';
import { Transaction, Address, PrivateKey, Script, Networks } from '../../src/btc';
import { Point, utils } from '../../src/crypto/secp256k1';
import { signCetWithAdaptorSig, verifyCetAdaptorSig, verifyCetSignature, sigToTaprootBuf } from '../../src/cet/signature';
import { createCet } from '../../src/cet/transactions';
import { adaptSig } from '../../src/crypto/counterparty';
import { commitToEvent, attestEventOutcome } from '../../src';
describe('CET Signature', () => {
let borrowerKey: PrivateKey;
let lenderKey: PrivateKey;
let borrowerAddress: Address;
let lenderAddress: Address;
let dlcUtxo: any;
let cet: Transaction;
let oraclePrivKey: Uint8Array;
let oraclePubKey: Point;
let eventOutcomeHashes: Uint8Array[];
before(async () => {
// Generate test keys
oraclePrivKey = utils.randomPrivateKey();
oraclePubKey = Point.fromPrivateKey(oraclePrivKey);
borrowerKey = new PrivateKey();
lenderKey = new PrivateKey();
borrowerAddress = new Address(borrowerKey.toPublicKey(), Networks.testnet, 'taproot');
lenderAddress = new Address(lenderKey.toPublicKey(), Networks.testnet, 'taproot');
// Generate test event outcome hashes
eventOutcomeHashes = [
new Uint8Array([1, 2, 3]),
new Uint8Array([4, 5, 6])
];
// Create mock DLC UTXO
dlcUtxo = {
txId: 'a'.repeat(64),
outputIndex: 0,
satoshis: 100000, // 0.001 BTC
script: new Script('')
};
cet = createCet(dlcUtxo, 60000, 40000, borrowerAddress, lenderAddress);
});
describe('signCetWithAdaptorSig and verifyCetAdaptorSig', () => {
it('should create and verify a valid adaptor signature', async () => {
const { signaturePoints, nonce } = await commitToEvent(eventOutcomeHashes, oraclePubKey);
const outcomeIndex = 0;
const counterpartyPrivKey = utils.randomPrivateKey();
const counterpartyPubKey = Point.fromPrivateKey(counterpartyPrivKey);
const adaptorSig = await signCetWithAdaptorSig(
counterpartyPrivKey,
signaturePoints[outcomeIndex],
cet,
0,
Buffer.from('')
);
expect(adaptorSig).to.be.instanceOf(Object);
expect(adaptorSig).to.have.property('R_prime');
expect(adaptorSig).to.have.property('s_prime');
expect(adaptorSig.R_prime).to.be.instanceOf(Point);
expect(typeof adaptorSig.s_prime).to.equal('bigint');
const isAdaptorSigValid = await verifyCetAdaptorSig(
adaptorSig,
counterpartyPubKey,
signaturePoints[outcomeIndex],
cet,
0,
Buffer.from('')
);
expect(isAdaptorSigValid).to.be.true;
const oracleSig = await attestEventOutcome(
oraclePrivKey,
nonce,
eventOutcomeHashes[outcomeIndex]
);
const completedSig = adaptSig(adaptorSig, oracleSig.s);
const isValid = await verifyCetSignature(completedSig, counterpartyPubKey, cet, 0, Buffer.from(''));
expect(isValid).to.be.true;
});
it('should detect invalid completed signatures', async () => {
const { signaturePoints, nonce } = await commitToEvent(eventOutcomeHashes, oraclePubKey);
const outcomeIndex = 0;
const counterpartyPrivKey = utils.randomPrivateKey();
const counterpartyPubKey = Point.fromPrivateKey(counterpartyPrivKey);
const adaptorSig = await signCetWithAdaptorSig(
counterpartyPrivKey,
signaturePoints[outcomeIndex],
cet,
0,
Buffer.from('')
);
const isAdaptorSigValid = await verifyCetAdaptorSig(
adaptorSig,
counterpartyPubKey,
signaturePoints[outcomeIndex],
cet,
0,
Buffer.from('')
);
expect(isAdaptorSigValid).to.be.true;
const oracleSig = await attestEventOutcome(
oraclePrivKey,
nonce,
eventOutcomeHashes[1] // Different outcome
);
const completedSig = adaptSig(adaptorSig, oracleSig.s);
const isValid = await verifyCetSignature(completedSig, counterpartyPubKey, cet, 0, Buffer.from(''));
expect(isValid).to.be.false;
});
it('should detect signatures for wrong messages', async () => {
const { signaturePoints, nonce } = await commitToEvent(eventOutcomeHashes, oraclePubKey);
const outcomeIndex = 0;
const counterpartyPrivKey = utils.randomPrivateKey();
const counterpartyPubKey = Point.fromPrivateKey(counterpartyPrivKey);
const adaptorSig = await signCetWithAdaptorSig(
counterpartyPrivKey,
signaturePoints[outcomeIndex],
cet,
0,
Buffer.from('')
);
const isAdaptorSigValid = await verifyCetAdaptorSig(
adaptorSig,
counterpartyPubKey,
signaturePoints[outcomeIndex],
cet,
0,
Buffer.from('')
);
expect(isAdaptorSigValid).to.be.true;
const oracleSig = await attestEventOutcome(
oraclePrivKey,
nonce,
eventOutcomeHashes[outcomeIndex]
);
const completedSig = adaptSig(adaptorSig, oracleSig.s);
const differentCet = createCet(dlcUtxo, 70000, 30000, borrowerAddress, lenderAddress);
const isValid = await verifyCetSignature(completedSig, counterpartyPubKey, differentCet, 0, Buffer.from(''));
expect(isValid).to.be.false;
});
});
describe('sigToTaprootBuf', () => {
it('should correctly serialize a Taproot signature with default sighash', () => {
// Test vector from BIP341 test cases
const R = new Point(
BigInt('0x8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983'),
BigInt('0x78fd9f664e274c9c2a2744197da522fdf1e3aba999b318e2587be098d90d4533')
);
const signature = {
R,
s: BigInt('0x8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983')
};
const serialized = sigToTaprootBuf(signature);
expect(serialized.length).to.equal(64);
expect(Buffer.from(serialized.slice(0, 32)).toString('hex'))
.to.equal('8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983');
expect(Buffer.from(serialized.slice(32, 64)).toString('hex'))
.to.equal('8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983');
});
it('should correctly serialize a Taproot signature with custom sighash', () => {
const R = new Point(
BigInt('0x8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983'),
BigInt('0x78fd9f664e274c9c2a2744197da522fdf1e3aba999b318e2587be098d90d4533')
);
const signature = {
R,
s: BigInt('0x8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983')
};
const sighash = 0x01; // SIGHASH_ALL
const serialized = sigToTaprootBuf(signature, sighash);
expect(serialized.length).to.equal(65);
expect(Buffer.from(serialized.slice(0, 32)).toString('hex'))
.to.equal('8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983');
expect(Buffer.from(serialized.slice(32, 64)).toString('hex'))
.to.equal('8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae983');
expect(serialized[64]).to.equal(0x01);
});
});
});