Skip to content

Commit

Permalink
Add tests for createTapTreeUsingHuffmanConstructor
Browse files Browse the repository at this point in the history
  • Loading branch information
Eunovo committed Mar 28, 2023
1 parent 87f103f commit 72438b6
Show file tree
Hide file tree
Showing 2 changed files with 368 additions and 2 deletions.
228 changes: 228 additions & 0 deletions test/huffman.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import * as assert from 'assert';
import { describe, it } from 'mocha';
import { HuffmanTapTreeNode, Taptree } from '../src/types';
import { createTapTreeUsingHuffmanConstructor } from '../src/psbt/bip371';

describe('Taptree using Huffman Constructor', () => {
const scriptBuff = Buffer.from('');

it('test empty array', () => {
assert.throws(() => createTapTreeUsingHuffmanConstructor([]), {
message: 'Cannot create taptree from empty list.',
});
});

it(
'should return only one node for a single leaf',
testLeafDistances([{ weight: 1, node: { output: scriptBuff } }], [0]),
);

it(
'should return a balanced tree for a list of scripts with equal weights',
testLeafDistances(
[
{
weight: 1,
node: {
output: scriptBuff,
},
},
{
weight: 1,
node: {
output: scriptBuff,
},
},
{
weight: 1,
node: {
output: scriptBuff,
},
},
{
weight: 1,
node: {
output: scriptBuff,
},
},
],
[2, 2, 2, 2],
),
);

it(
'should return an optimal binary tree for a list of scripts with weights [1, 2, 3, 4, 5]',
testLeafDistances(
[
{
weight: 1,
node: {
output: scriptBuff,
},
},
{
weight: 2,
node: {
output: scriptBuff,
},
},
{
weight: 3,
node: {
output: scriptBuff,
},
},
{
weight: 4,
node: {
output: scriptBuff,
},
},
{
weight: 5,
node: {
output: scriptBuff,
},
},
],
[3, 3, 2, 2, 2],
),
);

it(
'should return an optimal binary tree for a list of scripts with weights [1, 2, 3, 3]',
testLeafDistances(
[
{
weight: 1,
node: {
output: scriptBuff,
},
},
{
weight: 2,
node: {
output: scriptBuff,
},
},
{
weight: 3,
node: {
output: scriptBuff,
},
},
{
weight: 3,
node: {
output: scriptBuff,
},
},
],
[3, 3, 2, 1],
),
);

it(
'should return an optimal binary tree for a list of scripts with some negative weights: [1, 2, 3, -3]',
testLeafDistances(
[
{
weight: 1,
node: {
output: scriptBuff,
},
},
{
weight: 2,
node: {
output: scriptBuff,
},
},
{
weight: 3,
node: {
output: scriptBuff,
},
},
{
weight: -3,
node: {
output: scriptBuff,
},
},
],
[3, 2, 1, 3],
),
);

it(
'should return an optimal binary tree for a list of scripts with some weights specified as infinity',
testLeafDistances(
[
{
weight: 1,
node: {
output: scriptBuff,
},
},
{
weight: Number.POSITIVE_INFINITY,
node: {
output: scriptBuff,
},
},
{
weight: 3,
node: {
output: scriptBuff,
},
},
{
weight: Number.NEGATIVE_INFINITY,
node: {
output: scriptBuff,
},
},
],
[3, 1, 2, 3],
),
);
});

function testLeafDistances(
input: HuffmanTapTreeNode[],
expectedDistances: number[],
) {
return () => {
const tree = createTapTreeUsingHuffmanConstructor(input);

if (!Array.isArray(tree)) {
// tree is just one node
assert.deepEqual([0], expectedDistances);
return;
}

const leaves = input.map(value => value.node);

const map = new Map<Taptree, number>(); // Map of leaf to actual distance
let currentDistance = 1;
let currentArray: Array<Taptree[] | Taptree> = tree as any;
let nextArray: Array<Taptree[] | Taptree> = [];
while (currentArray.length > 0) {
currentArray.forEach(value => {
if (Array.isArray(value)) {
nextArray = nextArray.concat(value);
return;
}
map.set(value, currentDistance);
});

currentDistance += 1; // New level
currentArray = nextArray;
nextArray = [];
}

const actualDistances = leaves.map(value => map.get(value));
assert.deepEqual(actualDistances, expectedDistances);
};
}
142 changes: 140 additions & 2 deletions test/integration/taproot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import { describe, it } from 'mocha';
import { PsbtInput, TapLeafScript } from 'bip174/src/lib/interfaces';
import { regtestUtils } from './_regtest';
import * as bitcoin from '../..';
import { Taptree } from '../../src/types';
import { toXOnly, tapTreeToList, tapTreeFromList } from '../../src/psbt/bip371';
import { Taptree, HuffmanTapTreeNode } from '../../src/types';
import {
toXOnly,
tapTreeToList,
tapTreeFromList,
createTapTreeUsingHuffmanConstructor,
} from '../../src/psbt/bip371';
import { witnessStackToScriptWitness } from '../../src/psbt/psbtutils';
import { TapLeaf } from 'bip174/src/lib/interfaces';

Expand Down Expand Up @@ -598,6 +603,139 @@ describe('bitcoinjs-lib (transaction with taproot)', () => {
});
}
});

it('can verify script-tree built using huffman constructor', async () => {
const internalKey = bip32.fromSeed(rng(64), regtest);
const leafKey = bip32.fromSeed(rng(64), regtest);

const leafScriptAsm = `${toXOnly(leafKey.publicKey).toString(
'hex',
)} OP_CHECKSIG`;
const leafScript = bitcoin.script.fromASM(leafScriptAsm);

const nodes: HuffmanTapTreeNode[] = [
{
weight: 5,
node: {
output: bitcoin.script.fromASM(
'50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG',
),
},
},
{
weight: 5,
node: {
output: bitcoin.script.fromASM(
'50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac1 OP_CHECKSIG',
),
},
},
{
weight: 1,
node: {
output: bitcoin.script.fromASM(
'2258b1c3160be0864a541854eec9164a572f094f7562628281a8073bb89173a7 OP_CHECKSIG',
),
},
},
{
weight: 1,
node: {
output: bitcoin.script.fromASM(
'50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac2 OP_CHECKSIG',
),
},
},
{
weight: 4,
node: {
output: bitcoin.script.fromASM(
'50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac3 OP_CHECKSIG',
),
},
},
{
weight: 4,
node: {
output: bitcoin.script.fromASM(
'50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4 OP_CHECKSIG',
),
},
},
{
weight: 7,
node: {
output: leafScript,
},
},
];

const scriptTree: Taptree = createTapTreeUsingHuffmanConstructor(nodes);

const redeem = {
output: leafScript,
redeemVersion: 192,
};

const { output, witness } = bitcoin.payments.p2tr({
internalPubkey: toXOnly(internalKey.publicKey),
scriptTree,
redeem,
network: regtest,
});

// amount from faucet
const amount = 42e4;
// amount to send
const sendAmount = amount - 1e4;
// get faucet
const unspent = await regtestUtils.faucetComplex(output!, amount);

const psbt = new bitcoin.Psbt({ network: regtest });
psbt.addInput({
hash: unspent.txId,
index: 0,
witnessUtxo: { value: amount, script: output! },
});
psbt.updateInput(0, {
tapLeafScript: [
{
leafVersion: redeem.redeemVersion,
script: redeem.output,
controlBlock: witness![witness!.length - 1],
},
],
});

const sendInternalKey = bip32.fromSeed(rng(64), regtest);
const sendPubKey = toXOnly(sendInternalKey.publicKey);
const { address: sendAddress } = bitcoin.payments.p2tr({
internalPubkey: sendPubKey,
scriptTree,
network: regtest,
});

psbt.addOutput({
value: sendAmount,
address: sendAddress!,
tapInternalKey: sendPubKey,
tapTree: { leaves: tapTreeToList(scriptTree) },
});

psbt.signInput(0, leafKey);
psbt.finalizeInput(0);
const tx = psbt.extractTransaction();
const rawTx = tx.toBuffer();
const hex = rawTx.toString('hex');

await regtestUtils.broadcast(hex);
await regtestUtils.verify({
txId: tx.getId(),
address: sendAddress!,
vout: 0,
value: sendAmount,
});
});
});

function buildLeafIndexFinalizer(
Expand Down

0 comments on commit 72438b6

Please sign in to comment.