mirror of
https://github.com/bitcoin/bips.git
synced 2026-04-20 16:28:39 +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:
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!');
|
||||
Reference in New Issue
Block a user