Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions bip-0360/ref-impl/coordinate/js/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# Build outputs
dist/
build/
*.tsbuildinfo

# TypeScript
*.js.map
*.d.ts.map

# Environment variables
.env
.env.local
.env.*.local

# IDE / Editor files
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store

# OS files
Thumbs.db
desktop.ini

# Testing
coverage/
.nyc_output/
*.lcov

# Logs
logs/
*.log

# Temporary files
*.tmp
*.temp
.cache/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# parcel-bundler cache
.parcel-cache

# Next.js (if ever used)
.next
out

# Vercel (if ever used)
.vercel

# Turbo (if ever used)
.turbo

package-lock.json
26 changes: 26 additions & 0 deletions bip-0360/ref-impl/coordinate/js/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

= BIP 360 Javascript Reference Implementation

This implementation uses `coordinate-lib-js` from the Anduro Project, installed directly from GitHub:

* `coordinate-lib-js`: Bitcoin library for the Coordinate sidechain (Anduro Network)
* `@jbride/bitcoinpqc-wasm`: Post-quantum cryptography support
* `ecpair`: Elliptic curve key pair management
* `tiny-secp256k1`: Secp256k1 elliptic curve operations
:numbered:

== procedure

-----
# Verify Node.js version
$ node --version # Should be v22.x.x or higher

$ npm install

# compile Typecript
$ npx tsc


# run tests
$ node src/p2tsh-example.ts
-----
23 changes: 23 additions & 0 deletions bip-0360/ref-impl/coordinate/js/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "js",
"version": "1.0.0",
"type": "module",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^24.10.0",
"typescript": "^5.9.3"
},
"dependencies": {
"coordinate-lib-js": "github:AnduroProject/coordinate-lib-js",
"@jbride/bitcoinpqc-wasm": "^0.1.1",
"ecpair": "^3.0.0",
"tiny-secp256k1": "^2.2.4"
}
}
200 changes: 200 additions & 0 deletions bip-0360/ref-impl/coordinate/js/src/p2tsh-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// src/p2tsh-example.ts
// Example demonstrating P2TSH (Pay-to-Taproot-Script-Hash) address construction

import { payments } from 'coordinate-lib-js';
import * as bitcoinCrypto from 'coordinate-lib-js/src/crypto';
import * as bscript from 'coordinate-lib-js/src/script';
import type { Taptree } from 'coordinate-lib-js/src/types';
import ECPairFactory, { type ECPairInterface } from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import { randomBytes } from 'crypto';

const { p2tsh } = payments;

// Initialize ECPair with the ECC library
const ECPair = ECPairFactory(ecc);

// Create a secure RNG function
const rng = (size: number) => randomBytes(size);

function signAndVerify(
keyPair: ECPairInterface,
xOnlyPubkey: Uint8Array,
message: Buffer,
) {
const hash = Buffer.from(bitcoinCrypto.hash256(message));
const schnorrSignature = Buffer.from(keyPair.signSchnorr(hash));
const signatureWithSighashDefault = Buffer.concat([schnorrSignature, Buffer.from([0x00])]);
const verified = keyPair.verifySchnorr(hash, schnorrSignature);

return {
message,
hash,
signature: schnorrSignature,
signatureWithSighashDefault,
verified,
};
}

/**
* Example 1: Construct a P2TSH address from a script tree with a single leaf
* This is the simplest case - a script tree containing one script.
*/
function example1_simpleScriptTree() {
console.log('=== Example 1: P2TSH from simple script tree ===');

// Generate a key pair
const keyPair = ECPair.makeRandom({ rng });
const pubkey = keyPair.publicKey;
const xOnlyPubkey = ecc.xOnlyPointFromPoint(pubkey);

// Compile the script: x-only pubkey OP_CHECKSIG (BIP342 Schnorr signature)
const script = bscript.compile([Buffer.from(xOnlyPubkey), bscript.OPS.OP_CHECKSIG]);

// Create a script tree with one leaf
const scriptTree = {
output: script,
};

// Construct the P2TSH payment
const payment = p2tsh({
scriptTree: scriptTree,
});

console.log('Generated compressed pubkey:', pubkey.toString('hex'));
console.log('X-only pubkey:', Buffer.from(xOnlyPubkey).toString('hex'));
console.log('Script tree:', { output: bscript.toASM(script) });
console.log('P2TSH Address:', payment.address);
console.log('Output script:', bscript.toASM(payment.output!));
console.log('Merkle root hash:', payment.hash ? Buffer.from(payment.hash).toString('hex') : undefined);
const message = Buffer.from('P2TSH demo - example 1', 'utf8');
const result = signAndVerify(keyPair, xOnlyPubkey, message);

console.log('Message:', result.message.toString('utf8'));
console.log('Hash256(message):', result.hash.toString('hex'));
console.log('Schnorr signature (64-byte):', result.signature.toString('hex'));
console.log('Signature + default sighash (65-byte witness element):', result.signatureWithSighashDefault.toString('hex'));
console.log('Signature valid:', result.verified);
console.log('Witness stack for spend:', [result.signatureWithSighashDefault.toString('hex'), bscript.toASM(script)]);
console.log();
}

/**
* Example 2: Construct a P2TSH address from a script tree with multiple leaves
* This demonstrates a more complex script tree structure.
*/
function example2_multiLeafScriptTree() {
console.log('=== Example 2: P2TSH from multi-leaf script tree ===');

// Generate two different key pairs for the leaves
const keyPair1 = ECPair.makeRandom({ rng });
const keyPair2 = ECPair.makeRandom({ rng });
const pubkey1 = keyPair1.publicKey;
const pubkey2 = keyPair2.publicKey;
const xOnlyPubkey1 = ecc.xOnlyPointFromPoint(pubkey1);
const xOnlyPubkey2 = ecc.xOnlyPointFromPoint(pubkey2);

const script1 = bscript.compile([Buffer.from(xOnlyPubkey1), bscript.OPS.OP_CHECKSIG]);
const script2 = bscript.compile([Buffer.from(xOnlyPubkey2), bscript.OPS.OP_CHECKSIG]);

// Create a script tree with two leaves (array of two leaf objects)
const scriptTree: Taptree = [
{ output: script1 },
{ output: script2 },
];

// Construct the P2TSH payment
const payment = p2tsh({
scriptTree: scriptTree,
});

console.log('Generated compressed public keys:');
console.log(' Pubkey 1:', pubkey1.toString('hex'));
console.log(' Pubkey 2:', pubkey2.toString('hex'));
console.log('X-only pubkeys:');
console.log(' X-only 1:', Buffer.from(xOnlyPubkey1).toString('hex'));
console.log(' X-only 2:', Buffer.from(xOnlyPubkey2).toString('hex'));
console.log('Script tree leaves:');
console.log(' Leaf 1:', bscript.toASM(script1));
console.log(' Leaf 2:', bscript.toASM(script2));
console.log('P2TSH Address:', payment.address);
console.log('Output script:', bscript.toASM(payment.output!));
console.log('Merkle root hash:', payment.hash ? Buffer.from(payment.hash).toString('hex') : undefined);
const message1 = Buffer.from('P2TSH demo - example 2 leaf 1', 'utf8');
const message2 = Buffer.from('P2TSH demo - example 2 leaf 2', 'utf8');
const result1 = signAndVerify(keyPair1, xOnlyPubkey1, message1);
const result2 = signAndVerify(keyPair2, xOnlyPubkey2, message2);

console.log('Leaf 1 signature info:');
console.log(' Message:', result1.message.toString('utf8'));
console.log(' Hash256(message):', result1.hash.toString('hex'));
console.log(' Schnorr signature (64-byte):', result1.signature.toString('hex'));
console.log(' Signature + default sighash (65-byte):', result1.signatureWithSighashDefault.toString('hex'));
console.log(' Signature valid:', result1.verified);
console.log(' Witness stack:', [result1.signatureWithSighashDefault.toString('hex'), bscript.toASM(script1)]);

console.log('Leaf 2 signature info:');
console.log(' Message:', result2.message.toString('utf8'));
console.log(' Hash256(message):', result2.hash.toString('hex'));
console.log(' Schnorr signature (64-byte):', result2.signature.toString('hex'));
console.log(' Signature + default sighash (65-byte):', result2.signatureWithSighashDefault.toString('hex'));
console.log(' Signature valid:', result2.verified);
console.log(' Witness stack:', [result2.signatureWithSighashDefault.toString('hex'), bscript.toASM(script2)]);
console.log();
}

/**
* Example 4: Construct a P2TSH address from a hash and redeem script
* This demonstrates creating a P2TSH when you have the hash directly.
*/
function example3_fromHashAndRedeem() {
console.log('=== Example 3: P2TSH from hash and redeem script ===');

// Generate a key pair
const keyPair = ECPair.makeRandom({ rng });
const pubkey = keyPair.publicKey;
const xOnlyPubkey = ecc.xOnlyPointFromPoint(pubkey);
const redeemScript = bscript.compile([Buffer.from(xOnlyPubkey), bscript.OPS.OP_CHECKSIG]);

// Use a known hash (from test fixtures)
const hash = Buffer.from(
'b424dea09f840b932a00373cdcdbd25650b8c3acfe54a9f4a641a286721b8d26',
'hex',
);

// Construct the P2TSH payment
const payment = p2tsh({
hash: hash,
redeem: {
output: redeemScript,
},
});

console.log('Generated compressed pubkey:', pubkey.toString('hex'));
console.log('X-only pubkey:', Buffer.from(xOnlyPubkey).toString('hex'));
console.log('Redeem script:', bscript.toASM(redeemScript));
console.log('Hash:', hash.toString('hex'));
console.log('P2TSH Address:', payment.address);
console.log('Output script:', bscript.toASM(payment.output!));
const message = Buffer.from('P2TSH demo - example 3', 'utf8');
const result = signAndVerify(keyPair, xOnlyPubkey, message);

console.log('Message:', result.message.toString('utf8'));
console.log('Hash256(message):', result.hash.toString('hex'));
console.log('Schnorr signature (64-byte):', result.signature.toString('hex'));
console.log('Signature + default sighash (65-byte):', result.signatureWithSighashDefault.toString('hex'));
console.log('Signature valid:', result.verified);
console.log('Witness stack:', [result.signatureWithSighashDefault.toString('hex'), bscript.toASM(redeemScript)]);
console.log();
}

// Run all examples
console.log('P2TSH Address Construction Examples\n');
console.log('=====================================\n');

example1_simpleScriptTree();
example2_multiLeafScriptTree();
example3_fromHashAndRedeem();

console.log('=====================================');
console.log('All examples completed!');
45 changes: 45 additions & 0 deletions bip-0360/ref-impl/coordinate/js/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
// Visit https://aka.ms/tsconfig to read more about this file
"include": [
"src/**/*"
],
"compilerOptions": {
// File Layout
"rootDir": "./src",
"outDir": "./dist",

// Environment Settings
// See also https://aka.ms/tsconfig/module
"module": "nodenext",
"target": "esnext",
"types": ["node"],
// For nodejs:
// "lib": ["esnext"],

// Other Outputs
"sourceMap": true,
"declaration": true,
"declarationMap": true,

// Stricter Typechecking Options
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,

// Style Options
// "noImplicitReturns": true,
// "noImplicitOverride": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noFallthroughCasesInSwitch": true,
// "noPropertyAccessFromIndexSignature": true,

// Recommended Options
"strict": true,
"jsx": "react-jsx",
"verbatimModuleSyntax": true,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true,
}
}
2 changes: 2 additions & 0 deletions bip-0360/ref-impl/coordinate/rust/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[registries.kellnr-denver-space]
index = "sparse+https://crates.denver.space/api/v1/crates/"
1 change: 1 addition & 0 deletions bip-0360/ref-impl/coordinate/rust/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.env
Loading