Skip to content

Commit

Permalink
chore: miscellaneous fixes (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 authored Jan 1, 2024
1 parent a1f6f84 commit 646a317
Show file tree
Hide file tree
Showing 17 changed files with 162 additions and 75 deletions.
4 changes: 4 additions & 0 deletions lib/Boltz.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { init } from './init';
import Musig from './musig/Musig';
import swapTree from './swap/SwapTree';
import * as Types from './consts/Types';
import { targetFee } from './TargetFee';
import Networks from './consts/Networks';
import * as Scripts from './swap/Scripts';
Expand Down Expand Up @@ -32,6 +34,7 @@ const ContractABIs = {

export {
Musig,
Types,
Networks,
OutputType,
ContractABIs,
Expand All @@ -43,6 +46,7 @@ export {
SwapUtils,
TaprootUtils,
SwapTreeSerializer,
init,
swapTree,
targetFee,
swapScript,
Expand Down
4 changes: 4 additions & 0 deletions lib/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { initEccLib } from 'bitcoinjs-lib';
import { TinySecp256k1Interface } from 'bitcoinjs-lib/src/types';

export const init = (eccLib: TinySecp256k1Interface) => initEccLib(eccLib);
4 changes: 3 additions & 1 deletion lib/liquid/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ export const getOutputValue = (
blindingPrivateKey?: Buffer;
},
): number => {
return output.blindingPrivateKey
return output.blindingPrivateKey &&
output.rangeProof !== undefined &&
output.rangeProof.length > 0
? Number(
confidentialLiquid.unblindOutputWithKey(
output,
Expand Down
10 changes: 5 additions & 5 deletions lib/liquid/swap/Claim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import { reverseBuffer, varuint } from 'liquidjs-lib/src/bufferutils';
import { ecpair, secp } from '../init';
import Networks from '../consts/Networks';
import { getOutputValue } from '../Utils';
import { getHexString } from '../../Utils';
import { OutputType } from '../../consts/Enums';
import { validateInputs } from '../../swap/Claim';
import { LiquidClaimDetails } from '../consts/Types';
import { getHexBuffer, getHexString } from '../../Utils';
import { scriptBuffersToScript } from '../../swap/SwapUtils';
import { createControlBlock, tapLeafHash, toHashTree } from './TaprooUtils';

const dummyWitness = getHexBuffer('0x21');
const dummyTaprootSignature = Buffer.alloc(64);

const getSighashType = (type: OutputType) =>
type === OutputType.Taproot
Expand Down Expand Up @@ -171,7 +171,7 @@ export const constructClaimTransaction = (
for (const [i, utxo] of utxos.entries()) {
if (utxo.type === OutputType.Taproot) {
if (utxo.cooperative) {
signatures.push(dummyWitness);
signatures.push(dummyTaprootSignature);
continue;
}

Expand Down Expand Up @@ -248,9 +248,9 @@ export const constructClaimTransaction = (

if (utxo.type === OutputType.Taproot) {
if (utxo.cooperative) {
// Add a dummy to allow for extraction
// Add a dummy to allow for extraction and an accurate fee estimation
finals.finalScriptWitness = witnessStackToScriptWitness([
dummyWitness,
dummyTaprootSignature,
]);
} else {
const tapleaf = isRefund
Expand Down
18 changes: 10 additions & 8 deletions lib/musig/Musig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,24 +82,26 @@ class Musig {
this.nonceAgg = this.secp.musig.nonceAgg(nonces);
};

public aggregateNonces = (nonces: Map<Uint8Array, Uint8Array>) => {
const ordered: Uint8Array[] = [];

if (!nonces.has(this.key.publicKey)) {
nonces.set(this.key.publicKey, this.getPublicNonce());
public aggregateNonces = (nonces: [Uint8Array, Uint8Array][]) => {
if (
nonces.find(([keyCmp]) => this.key.publicKey.equals(keyCmp)) === undefined
) {
nonces.push([this.key.publicKey, this.getPublicNonce()]);
}

if (this.publicKeys.length !== nonces.size) {
if (this.publicKeys.length !== nonces.length) {
throw 'number of nonces != number of public keys';
}

const ordered: Uint8Array[] = [];

for (const key of this.publicKeys) {
const nonce = nonces.get(key);
const nonce = nonces.find(([keyCmp]) => key.equals(keyCmp));
if (nonce === undefined) {
throw `could not find nonce for public key ${getHexString(key)}`;
}

ordered.push(nonce);
ordered.push(nonce[1]);
}

this.aggregateNoncesOrdered(ordered);
Expand Down
55 changes: 31 additions & 24 deletions lib/swap/Claim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { ClaimDetails } from '../consts/Types';
import { encodeSignature, scriptBuffersToScript } from './SwapUtils';
import { createControlBlock, hashForWitnessV1 } from './TaprootUtils';

const dummyTaprootSignature = Buffer.alloc(64);

const isRelevantTaprootOutput = (utxo: Omit<ClaimDetails, 'value'>) =>
utxo.type === OutputType.Taproot && utxo.cooperative !== true;

Expand Down Expand Up @@ -117,32 +119,37 @@ export const constructClaimTransaction = (

// Construct and sign the witness for (nested) SegWit inputs
// When the Taproot output is spent cooperatively, we leave it empty
if (utxo.type === OutputType.Taproot && utxo.cooperative !== true) {
const tapLeaf = isRefund
? utxo.swapTree!.refundLeaf
: utxo.swapTree!.claimLeaf;
const sigHash = hashForWitnessV1(
utxos,
tx,
index,
tapleafHash(tapLeaf),
Transaction.SIGHASH_DEFAULT,
);
if (utxo.type === OutputType.Taproot) {
if (utxo.cooperative !== true) {
const tapLeaf = isRefund
? utxo.swapTree!.refundLeaf
: utxo.swapTree!.claimLeaf;
const sigHash = hashForWitnessV1(
utxos,
tx,
index,
tapleafHash(tapLeaf),
Transaction.SIGHASH_DEFAULT,
);

const signature = utxo.keys.signSchnorr(sigHash);
const witness = isRefund ? [signature] : [signature, utxo.preimage];
const signature = utxo.keys.signSchnorr(sigHash);
const witness = isRefund ? [signature] : [signature, utxo.preimage];

tx.setWitness(
index,
witness.concat([
tapLeaf.output,
createControlBlock(
toHashTree(utxo.swapTree!.tree),
tapLeaf,
utxo.internalKey!,
),
]),
);
tx.setWitness(
index,
witness.concat([
tapLeaf.output,
createControlBlock(
toHashTree(utxo.swapTree!.tree),
tapLeaf,
utxo.internalKey!,
),
]),
);
} else {
// Stub the signature to allow for accurate fee estimations
tx.setWitness(index, [dummyTaprootSignature]);
}
} else if (
utxo.type === OutputType.Bech32 ||
utxo.type === OutputType.Compatibility
Expand Down
22 changes: 15 additions & 7 deletions lib/swap/SwapTreeSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ type SerializedLeaf = {
output: string;
};

type SerializedTree = {
claimLeaf: SerializedLeaf;
refundLeaf: SerializedLeaf;
};

const serializeLeaf = (leaf: Tapleaf): SerializedLeaf => ({
version: leaf.version,
output: getHexString(leaf.output),
Expand All @@ -17,14 +22,15 @@ const deserializeLeaf = (leaf: SerializedLeaf): Tapleaf => ({
output: getHexBuffer(leaf.output),
});

export const serializeSwapTree = (tree: SwapTree): string =>
JSON.stringify({
claimLeaf: serializeLeaf(tree.claimLeaf),
refundLeaf: serializeLeaf(tree.refundLeaf),
});
export const serializeSwapTree = (tree: SwapTree): SerializedTree => ({
claimLeaf: serializeLeaf(tree.claimLeaf),
refundLeaf: serializeLeaf(tree.refundLeaf),
});

export const deserializeSwapTree = (tree: string): SwapTree => {
const parsed = JSON.parse(tree);
export const deserializeSwapTree = (
tree: string | SerializedTree,
): SwapTree => {
const parsed = typeof tree === 'string' ? JSON.parse(tree) : tree;

const res = {
claimLeaf: deserializeLeaf(parsed.claimLeaf),
Expand All @@ -36,3 +42,5 @@ export const deserializeSwapTree = (tree: string): SwapTree => {
tree: swapLeafsToTree(res.claimLeaf, res.refundLeaf),
};
};

export { SerializedLeaf, SerializedTree };
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "boltz-core",
"version": "1.0.5",
"version": "2.0.0",
"description": "Core library of Boltz",
"main": "dist/lib/Boltz.js",
"scripts": {
Expand Down
24 changes: 24 additions & 0 deletions test/integration/liquid/Utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,30 @@ describe('Liquid Utils', () => {
).toEqual(amount);
});

test('should decode unconfidential outputs when a blinding key is provided', async () => {
const addr = await elementsClient.getNewAddress();
const { scriptPubKey, unconfidentialAddress } =
address.fromConfidential(addr);

const blindingKey = getHexBuffer(
await elementsClient.dumpBlindingKey(addr),
);

const amount = 123_321;
const tx = Transaction.fromHex(
await elementsClient.getRawTransaction(
await elementsClient.sendToAddress(unconfidentialAddress, amount),
),
);

expect(
getOutputValue({
...tx.outs.find((out) => out.script.equals(scriptPubKey!))!,
blindingPrivateKey: blindingKey,
}),
).toEqual(amount);
});

test('should decode confidential outputs', async () => {
const addr = await elementsClient.getNewAddress();
expect(addr.startsWith('el1')).toBeTruthy();
Expand Down
8 changes: 5 additions & 3 deletions test/integration/liquid/swapTree/SwapTreeClaim.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ describe.each`
blindingKey,
);

// Check the dummy signature
expect(tx.ins[0].witness).toHaveLength(1);
expect(tx.ins[0].witness[0].equals(Buffer.alloc(64))).toEqual(true);

const theirNonce = secp.musig.nonceGen(randomBytes(32));
musig!.aggregateNonces(
new Map([[refundKeys.publicKey, theirNonce.pubNonce]]),
);
musig!.aggregateNonces([[refundKeys.publicKey, theirNonce.pubNonce]]);
musig!.initializeSession(
hashForWitnessV1(Networks.liquidRegtest, [utxo], tx, 0),
);
Expand Down
2 changes: 1 addition & 1 deletion test/integration/musig/Musig.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ describe('Musig', () => {

// Create signature
const theirNonce = secp.musig.nonceGen(randomBytes(32));
musig.aggregateNonces(new Map([[theirKey.publicKey, theirNonce.pubNonce]]));
musig.aggregateNonces([[theirKey.publicKey, theirNonce.pubNonce]]);
musig.initializeSession(sigHash);
musig.signPartial();
musig.addPartial(
Expand Down
8 changes: 5 additions & 3 deletions test/integration/swapTree/SwapTreeClaim.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ describe.each`

const tx = constructClaimTransaction([utxo], destinationOutput, 1_000);

// Check the dummy signature
expect(tx.ins[0].witness).toHaveLength(1);
expect(tx.ins[0].witness[0].equals(Buffer.alloc(64))).toEqual(true);

const theirNonce = secp.musig.nonceGen(randomBytes(32));
musig!.aggregateNonces(
new Map([[refundKeys.publicKey, theirNonce.pubNonce]]),
);
musig!.aggregateNonces([[refundKeys.publicKey, theirNonce.pubNonce]]);
musig!.initializeSession(hashForWitnessV1([utxo], tx, 0));
musig!.signPartial();
musig!.addPartial(
Expand Down
30 changes: 13 additions & 17 deletions test/unit/musig/Musig.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ describe('Musig', () => {
[pubKeys[1], secp.musig.nonceGen(randomBytes(32)).pubNonce],
[pubKeys[2], secp.musig.nonceGen(randomBytes(32)).pubNonce],
]);
musig.aggregateNonces(nonces);
musig.aggregateNonces(Array.from(nonces.entries()));

expect(musig['pubNonces']).toEqual([
musig.getPublicNonce(),
Expand All @@ -192,7 +192,7 @@ describe('Musig', () => {
ourKey.publicKey,
ECPair.makeRandom().publicKey,
]);
expect(() => musig.aggregateNonces(new Map())).toThrow(
expect(() => musig.aggregateNonces([])).toThrow(
'number of nonces != number of public keys',
);
});
Expand All @@ -205,15 +205,13 @@ describe('Musig', () => {
]);

expect(() =>
musig.aggregateNonces(
new Map([
[ourKey.publicKey, musig.getPublicNonce()],
[
ECPair.makeRandom().publicKey,
secp.musig.nonceGen(randomBytes(32)).pubNonce,
],
]),
),
musig.aggregateNonces([
[ourKey.publicKey, musig.getPublicNonce()],
[
ECPair.makeRandom().publicKey,
secp.musig.nonceGen(randomBytes(32)).pubNonce,
],
]),
).toThrow(
`could not find nonce for public key ${Buffer.from(
musig['publicKeys'][1],
Expand Down Expand Up @@ -527,12 +525,10 @@ describe('Musig', () => {
}

musig.aggregateNonces(
new Map<Uint8Array, Uint8Array>(
counterparties.map((party) => [
party.key.publicKey,
party.nonce.pubNonce,
]),
),
counterparties.map((party) => [
party.key.publicKey,
party.nonce.pubNonce,
]),
);

musig.initializeSession(toSign);
Expand Down
2 changes: 1 addition & 1 deletion test/unit/swap/Claim.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { randomBytes } from 'crypto';
import { ECPair } from '../Utils';
import { getHexBuffer } from '../../../lib/Utils';
import { OutputType } from '../../../lib/consts/Enums';
import { p2trOutput } from '../../../lib/swap/Scripts';
import { ClaimDetails } from '../../../lib/consts/Types';
import { claimDetails, claimDetailsMap } from './ClaimDetails';
import { constructClaimTransaction } from '../../../lib/swap/Claim';
import { randomBytes } from 'crypto';

describe('Claim', () => {
const testClaim = (utxos: ClaimDetails[], fee: number) => {
Expand Down
Loading

0 comments on commit 646a317

Please sign in to comment.