mirror of
https://github.com/bitcoin/bips.git
synced 2026-03-16 15:55:37 +00:00
BIP360: Pay to Merkle Root (P2MR) (#1670)
Review comments and assistance by: Armin Sabouri <armins88@gmail.com> D++ <82842780+dplusplus1024@users.noreply.github.com> Jameson Lopp <jameson.lopp@gmail.com> jbride <jbride2001@yahoo.com> Joey Yandle <xoloki@gmail.com> Jon Atack <jon@atack.com> Jonas Nick <jonasd.nick@gmail.com> Kyle Crews <kylecrews@Kyles-Mac-Studio.local> Mark "Murch" Erhardt <murch@murch.one> notmike-5 <notmike-5@users.noreply.github.com> Vojtěch Strnad <43024885+vostrnad@users.noreply.github.com> Co-authored-by: Ethan Heilman <ethan.r.heilman@gmail.com> Co-authored-by: Isabel Foxen Duke <110147802+Isabelfoxenduke@users.noreply.github.com>
This commit is contained in:
76
bip-0360/ref-impl/js/.gitignore
vendored
Normal file
76
bip-0360/ref-impl/js/.gitignore
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# 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
|
||||
|
||||
18
bip-0360/ref-impl/js/README.adoc
Normal file
18
bip-0360/ref-impl/js/README.adoc
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
= BIP 360 Javascript Reference Implementation
|
||||
|
||||
:numbered:
|
||||
|
||||
== procedure
|
||||
|
||||
-----
|
||||
$ npm install
|
||||
|
||||
# compile Typecript
|
||||
$ npx tsc
|
||||
|
||||
|
||||
# run tests
|
||||
$ node src/p2mr-example.ts
|
||||
$ node src/test-npm-pqc-package.js
|
||||
-----
|
||||
245
bip-0360/ref-impl/js/package-lock.json
generated
Normal file
245
bip-0360/ref-impl/js/package-lock.json
generated
Normal file
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"name": "js",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "js",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@jbride/bitcoinjs-lib": "^7.0.0-rc.0-p2mr-0.0",
|
||||
"@jbride/bitcoinpqc-wasm": "^0.1.1",
|
||||
"ecpair": "^3.0.0",
|
||||
"tiny-secp256k1": "^2.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.10.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@jbride/bitcoinjs-lib": {
|
||||
"version": "7.0.0-rc.0-p2mr-0.0",
|
||||
"resolved": "https://registry.npmjs.org/@jbride/bitcoinjs-lib/-/bitcoinjs-lib-7.0.0-rc.0-p2mr-0.0.tgz",
|
||||
"integrity": "sha512-JRL0LTBVFBhfHhEciJi8I7C5jGoL24b07rwQthe+3UAGbv2LLOqKRYAD3dc5XD4Maanh1awV+MUELgc7OFgjYw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.2.0",
|
||||
"bech32": "^2.0.0",
|
||||
"bip174": "^3.0.0-rc.0",
|
||||
"bs58check": "^4.0.0",
|
||||
"uint8array-tools": "^0.0.9",
|
||||
"valibot": "^0.38.0",
|
||||
"varuint-bitcoin": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jbride/bitcoinpqc-wasm": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@jbride/bitcoinpqc-wasm/-/bitcoinpqc-wasm-0.1.1.tgz",
|
||||
"integrity": "sha512-aftRuCBYXPEYzvWBVG84Vgum/ba4zkpUQrNA3jsWK2ir/DzxY7WmReskFEuPprc8X/LQ0/2lxJZyzqGRxDuO8w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz",
|
||||
"integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base-x": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz",
|
||||
"integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bech32": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
|
||||
"integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bip174": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bip174/-/bip174-3.0.0.tgz",
|
||||
"integrity": "sha512-N3vz3rqikLEu0d6yQL8GTrSkpYb35NQKWMR7Hlza0lOj6ZOlvQ3Xr7N9Y+JPebaCVoEUHdBeBSuLxcHr71r+Lw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uint8array-tools": "^0.0.9",
|
||||
"varuint-bitcoin": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bs58": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz",
|
||||
"integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base-x": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bs58check": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-4.0.0.tgz",
|
||||
"integrity": "sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.2.0",
|
||||
"bs58": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ecpair": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ecpair/-/ecpair-3.0.0.tgz",
|
||||
"integrity": "sha512-kf4JxjsRQoD4EBzpYjGAcR0t9i/4oAeRPtyCpKvSwyotgkc6oA4E4M0/e+kep7cXe+mgxAvoeh/jdgH9h5+Wxw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uint8array-tools": "^0.0.8",
|
||||
"valibot": "^0.37.0",
|
||||
"wif": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ecpair/node_modules/uint8array-tools": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.8.tgz",
|
||||
"integrity": "sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ecpair/node_modules/valibot": {
|
||||
"version": "0.37.0",
|
||||
"resolved": "https://registry.npmjs.org/valibot/-/valibot-0.37.0.tgz",
|
||||
"integrity": "sha512-FQz52I8RXgFgOHym3XHYSREbNtkgSjF9prvMFH1nBsRyfL6SfCzoT1GuSDTlbsuPubM7/6Kbw0ZMQb8A+V+VsQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-secp256k1": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.4.tgz",
|
||||
"integrity": "sha512-FoDTcToPqZE454Q04hH9o2EhxWsm7pOSpicyHkgTwKhdKWdsTUuqfP5MLq3g+VjAtl2vSx6JpXGdwA2qpYkI0Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uint8array-tools": "0.0.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-secp256k1/node_modules/uint8array-tools": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz",
|
||||
"integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uint8array-tools": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.9.tgz",
|
||||
"integrity": "sha512-9vqDWmoSXOoi+K14zNaf6LBV51Q8MayF0/IiQs3GlygIKUYtog603e6virExkjjFosfJUBI4LhbQK1iq8IG11A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/valibot": {
|
||||
"version": "0.38.0",
|
||||
"resolved": "https://registry.npmjs.org/valibot/-/valibot-0.38.0.tgz",
|
||||
"integrity": "sha512-RCJa0fetnzp+h+KN9BdgYOgtsMAG9bfoJ9JSjIhFHobKWVWyzM3jjaeNTdpFK9tQtf3q1sguXeERJ/LcmdFE7w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/varuint-bitcoin": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-2.0.0.tgz",
|
||||
"integrity": "sha512-6QZbU/rHO2ZQYpWFDALCDSRsXbAs1VOEmXAxtbtjLtKuMJ/FQ8YbhfxlaiKv5nklci0M6lZtlZyxo9Q+qNnyog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uint8array-tools": "^0.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/varuint-bitcoin/node_modules/uint8array-tools": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.8.tgz",
|
||||
"integrity": "sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wif": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wif/-/wif-5.0.0.tgz",
|
||||
"integrity": "sha512-iFzrC/9ne740qFbNjTZ2FciSRJlHIXoxqk/Y5EnE08QOXu1WjJyCCswwDTYbohAOEnlCtLaAAQBhyaLRFh2hMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bs58check": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
bip-0360/ref-impl/js/package.json
Normal file
23
bip-0360/ref-impl/js/package.json
Normal file
@@ -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": {
|
||||
"@jbride/bitcoinjs-lib": "^7.0.0-rc.0-p2mr-0.0",
|
||||
"@jbride/bitcoinpqc-wasm": "^0.1.1",
|
||||
"ecpair": "^3.0.0",
|
||||
"tiny-secp256k1": "^2.2.4"
|
||||
}
|
||||
}
|
||||
200
bip-0360/ref-impl/js/src/p2mr-example.ts
Normal file
200
bip-0360/ref-impl/js/src/p2mr-example.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
// src/p2mr-example.ts
|
||||
// Example demonstrating P2MR (Pay-to-Taproot-Script-Hash) address construction
|
||||
|
||||
import { payments } from '@jbride/bitcoinjs-lib';
|
||||
import * as bitcoinCrypto from '@jbride/bitcoinjs-lib/src/crypto';
|
||||
import * as bscript from '@jbride/bitcoinjs-lib/src/script';
|
||||
import type { Taptree } from '@jbride/bitcoinjs-lib/src/types';
|
||||
import ECPairFactory, { type ECPairInterface } from 'ecpair';
|
||||
import * as ecc from 'tiny-secp256k1';
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
const { p2mr } = 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 P2MR 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: P2MR 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 P2MR payment
|
||||
const payment = p2mr({
|
||||
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('P2MR 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('P2MR 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 P2MR address from a script tree with multiple leaves
|
||||
* This demonstrates a more complex script tree structure.
|
||||
*/
|
||||
function example2_multiLeafScriptTree() {
|
||||
console.log('=== Example 2: P2MR 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 P2MR payment
|
||||
const payment = p2mr({
|
||||
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('P2MR 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('P2MR demo - example 2 leaf 1', 'utf8');
|
||||
const message2 = Buffer.from('P2MR 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 P2MR address from a hash and redeem script
|
||||
* This demonstrates creating a P2MR when you have the hash directly.
|
||||
*/
|
||||
function example3_fromHashAndRedeem() {
|
||||
console.log('=== Example 3: P2MR 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 P2MR payment
|
||||
const payment = p2mr({
|
||||
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('P2MR Address:', payment.address);
|
||||
console.log('Output script:', bscript.toASM(payment.output!));
|
||||
const message = Buffer.from('P2MR 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('P2MR Address Construction Examples\n');
|
||||
console.log('=====================================\n');
|
||||
|
||||
example1_simpleScriptTree();
|
||||
example2_multiLeafScriptTree();
|
||||
example3_fromHashAndRedeem();
|
||||
|
||||
console.log('=====================================');
|
||||
console.log('All examples completed!');
|
||||
197
bip-0360/ref-impl/js/src/test-npm-pqc-package.js
Normal file
197
bip-0360/ref-impl/js/src/test-npm-pqc-package.js
Normal file
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Node.js test script for Bitcoin PQC WASM module (High-Level API)
|
||||
*
|
||||
* Usage: node test-npm-package.js
|
||||
*
|
||||
* This script tests the high-level TypeScript wrapper API (index.js) from the
|
||||
* command line, which provides a cleaner interface than the low-level API.
|
||||
*/
|
||||
|
||||
import { randomBytes } from 'node:crypto';
|
||||
|
||||
// Load the high-level WASM module
|
||||
let bitcoinpqc;
|
||||
let Algorithm;
|
||||
|
||||
try {
|
||||
const module = await import('@jbride/bitcoinpqc-wasm');
|
||||
bitcoinpqc = module.bitcoinpqc || module.default;
|
||||
Algorithm = module.Algorithm;
|
||||
|
||||
if (!bitcoinpqc || !Algorithm) {
|
||||
throw new Error('Failed to import bitcoinpqc or Algorithm from @jbride/bitcoinpqc-wasm');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load WASM module:', error);
|
||||
console.error('Make sure you have installed the @jbride/bitcoinpqc-wasm package before running this test.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Helper function to generate random bytes
|
||||
function generateRandomBytes(length) {
|
||||
const array = new Uint8Array(length);
|
||||
const bytes = randomBytes(length);
|
||||
array.set(bytes);
|
||||
return array;
|
||||
}
|
||||
|
||||
// Test function
|
||||
async function testAlgorithm(algorithm, name) {
|
||||
console.log(`\nTesting ${name} algorithm:`);
|
||||
console.log('------------------------');
|
||||
|
||||
try {
|
||||
// Get key and signature sizes
|
||||
const pkSize = bitcoinpqc.publicKeySize(algorithm);
|
||||
const skSize = bitcoinpqc.secretKeySize(algorithm);
|
||||
const sigSize = bitcoinpqc.signatureSize(algorithm);
|
||||
|
||||
console.log(`Public key size: ${pkSize} bytes`);
|
||||
console.log(`Secret key size: ${skSize} bytes`);
|
||||
console.log(`Signature size: ${sigSize} bytes`);
|
||||
|
||||
// Generate random data for key generation
|
||||
const randomData = generateRandomBytes(128);
|
||||
|
||||
// Generate a key pair
|
||||
const keygenStart = Date.now();
|
||||
const keypair = bitcoinpqc.generateKeypair(algorithm, randomData);
|
||||
const keygenDuration = Date.now() - keygenStart;
|
||||
console.log(`Key generation time: ${keygenDuration} ms`);
|
||||
|
||||
// Create a message to sign
|
||||
const messageText = 'This is a test message for PQC signature verification';
|
||||
const message = Buffer.from(messageText, 'utf8');
|
||||
const messageUint8 = new Uint8Array(message);
|
||||
console.log(`Message to sign: "${messageText}"`);
|
||||
console.log(`Message length: ${message.length} bytes`);
|
||||
|
||||
// Sign the message
|
||||
const signStart = Date.now();
|
||||
let signature;
|
||||
try {
|
||||
signature = bitcoinpqc.sign(keypair.secretKey, messageUint8, algorithm);
|
||||
const signDuration = Date.now() - signStart;
|
||||
console.log(`Signing time: ${signDuration} ms`);
|
||||
console.log(`Actual signature size: ${signature.size} bytes`);
|
||||
} catch (error) {
|
||||
const signDuration = Date.now() - signStart;
|
||||
console.log(`Signing failed after ${signDuration} ms`);
|
||||
console.log(`Error: ${error.message}`);
|
||||
if (algorithm === Algorithm.SLH_DSA_SHAKE_128S) {
|
||||
console.log('');
|
||||
console.log('⚠️ NOTE: SLH-DSA-SHAKE-128s signing is currently experiencing');
|
||||
console.log(' issues when compiled to WebAssembly. This appears to be a');
|
||||
console.log(' bug in the SPHINCS+ reference implementation when compiled');
|
||||
console.log(' to WASM. ML-DSA-44 (Dilithium) works correctly.');
|
||||
console.log('');
|
||||
console.log(' Key generation succeeded, but signing failed.');
|
||||
console.log(' This is a known limitation of the browser/WASM build.');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Verify the signature
|
||||
const verifyStart = Date.now();
|
||||
const verifyResult = bitcoinpqc.verify(
|
||||
keypair.publicKey,
|
||||
messageUint8,
|
||||
signature,
|
||||
algorithm
|
||||
);
|
||||
const verifyDuration = Date.now() - verifyStart;
|
||||
|
||||
if (verifyResult) {
|
||||
console.log('Signature verified successfully!');
|
||||
} else {
|
||||
console.log('ERROR: Signature verification failed!');
|
||||
}
|
||||
console.log(`Verification time: ${verifyDuration} ms`);
|
||||
|
||||
// Try to verify with a modified message
|
||||
const modifiedMessageText = 'This is a MODIFIED message for PQC signature verification';
|
||||
const modifiedMessage = Buffer.from(modifiedMessageText, 'utf8');
|
||||
const modifiedMessageUint8 = new Uint8Array(modifiedMessage);
|
||||
console.log(`Modified message: "${modifiedMessageText}"`);
|
||||
const modifiedVerifyResult = bitcoinpqc.verify(
|
||||
keypair.publicKey,
|
||||
modifiedMessageUint8,
|
||||
signature,
|
||||
algorithm
|
||||
);
|
||||
|
||||
if (modifiedVerifyResult) {
|
||||
console.log('ERROR: Signature verified for modified message!');
|
||||
} else {
|
||||
console.log('Correctly rejected signature for modified message');
|
||||
}
|
||||
|
||||
console.log('✓ Test passed!\n');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`❌ Error: ${error.message}`);
|
||||
if (error.stack) {
|
||||
console.error(error.stack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
console.log('Bitcoin PQC Library Example (Node.js - High-Level API)');
|
||||
console.log('======================================================\n');
|
||||
console.log('This example tests the post-quantum signature algorithms designed for BIP-360 and the Bitcoin QuBit soft fork.');
|
||||
console.log('Using the high-level TypeScript wrapper API (index.js).\n');
|
||||
|
||||
// Initialize the module
|
||||
try {
|
||||
console.log('Initializing WASM module...');
|
||||
await bitcoinpqc.init({
|
||||
onRuntimeInitialized: () => {
|
||||
console.log('✓ WASM module initialized successfully!\n');
|
||||
},
|
||||
print: (text) => {
|
||||
// Enable WASM print output for debugging
|
||||
console.log('WASM:', text);
|
||||
},
|
||||
printErr: (text) => {
|
||||
console.error('WASM Error:', text);
|
||||
},
|
||||
// Node.js-specific: provide crypto.getRandomValues
|
||||
getRandomValues: (arr) => {
|
||||
const bytes = randomBytes(arr.length);
|
||||
arr.set(bytes);
|
||||
return arr;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize module:', error);
|
||||
if (error.stack) {
|
||||
console.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const results = [];
|
||||
|
||||
// Test ML-DSA-44
|
||||
results.push(await testAlgorithm(Algorithm.ML_DSA_44, 'ML-DSA-44'));
|
||||
|
||||
// Test SLH-DSA-Shake-128s
|
||||
results.push(await testAlgorithm(Algorithm.SLH_DSA_SHAKE_128S, 'SLH-DSA-Shake-128s'));
|
||||
|
||||
// Summary
|
||||
console.log('\n======================================================');
|
||||
console.log('Test Summary:');
|
||||
console.log(` ML-DSA-44: ${results[0] ? '✓ PASSED' : '✗ FAILED'}`);
|
||||
console.log(` SLH-DSA-Shake-128s: ${results[1] ? '✓ PASSED' : '✗ FAILED'}`);
|
||||
console.log('======================================================\n');
|
||||
|
||||
const exitCode = results.every(r => r) ? 0 : 1;
|
||||
process.exit(exitCode);
|
||||
}
|
||||
|
||||
// Start
|
||||
runTests();
|
||||
45
bip-0360/ref-impl/js/tsconfig.json
Normal file
45
bip-0360/ref-impl/js/tsconfig.json
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user