Skip to content

Instantly share code, notes, and snippets.

@reardencode
Last active February 24, 2024 05:26
Show Gist options
  • Save reardencode/9dbc60a6d6e1591905d25bf4d123dfdd to your computer and use it in GitHub Desktop.
Save reardencode/9dbc60a6d6e1591905d25bf4d123dfdd to your computer and use it in GitHub Desktop.
Test Scripts
const { ECPairFactory } = require('ecpair');
const btc = require('./src');
const ecc = require('tiny-secp256k1');
const { secp256k1, schnorr } = require('@noble/curves/secp256k1');
const psbtutils = require('./src/psbt/psbtutils');
const { reverseBuffer } = require('./src/bufferutils');
const { REVERSE_OPS: rOps } = require('./src/ops');
btc.initEccLib(ecc);
const ECPair = ECPairFactory(ecc);
const data = {
small: Buffer.from('checksigfromstack', 'ascii'),
valid: Buffer.from(
'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
'hex',
),
invalid: Buffer.from(
'feadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
'hex',
),
};
const { address } = btc.payments.p2tr({
internalPubkey: Buffer.from(
schnorr.getPublicKey(secp256k1.utils.randomPrivateKey()),
),
network: btc.networks.regtest,
});
const sk = secp256k1.utils.randomPrivateKey();
const pubkeys = {
schnorr: schnorr.getPublicKey(sk),
ecdsa: secp256k1.getPublicKey(sk),
};
const sigs = {
small: schnorr.sign(data.small, sk),
schnorr: schnorr.sign(data.valid, sk),
ecdsa: secp256k1.sign(data.valid, sk, { lowS: true }).toDERRawBytes(),
zero: Buffer.of(),
};
const mkScript = (type, dataType, pkType) => {
const pk = pubkeys[pkType];
const m = data[dataType];
const baseScript = Buffer.concat([
Buffer.of(m.length),
m,
Buffer.of(pk.length),
pk,
]);
switch (type) {
case 'verify':
return Buffer.concat([
baseScript,
Buffer.of(0xb4),
Buffer.of(btc.opcodes.OP_2DROP),
]);
case 'plain':
return Buffer.concat([baseScript, Buffer.of(0xcc)]);
case 'zero-success':
return Buffer.concat([
baseScript,
Buffer.of(0xcc),
Buffer.of(btc.opcodes.OP_0),
Buffer.of(btc.opcodes.OP_EQUAL),
]);
default:
throw new Error('Invalid script type');
}
};
const nums = Buffer.from(
'50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0',
'hex',
);
const inputs = {
valid: 'e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5',
invalid: 'a2522fa96033c5736f3142ff616426cd03a3d0f077f609e22c5a33a96e04e597',
};
const mkPsbt = (type, script, inputType, sigType) => {
let opts = { network: btc.networks.regtest };
switch (type) {
case 'p2sh':
case 'p2wsh':
opts = { ...opts, redeem: { output: script } };
break;
case 'p2tr':
opts = {
...opts,
internalPubkey: nums,
scriptTree: { output: script },
redeem: { output: script },
};
break;
default:
throw new Error('Invalid payment type');
}
const p = btc.payments[type](opts);
const psbt = new btc.Psbt({ network: btc.networks.regtest });
psbt.addInput({
hash: inputs[inputType],
index: 0,
witnessUtxo: { value: 155000, script: p.output },
});
psbt.addOutput({ value: 150000, address });
const sig = sigs[sigType];
switch (type) {
case 'p2sh':
psbt.updateInput(0, { redeemScript: p.redeem.output });
psbt.finalizeInput(0, (inputIndex, input, _) => ({
finalScriptSig: btc.script.compile([
Buffer.from(sig),
input.redeemScript,
]),
}));
break;
case 'p2wsh':
psbt.updateInput(0, { witnessScript: p.redeem.output });
psbt.finalizeInput(0, (inputIndex, input, _) => ({
finalScriptWitness: psbtutils.witnessStackToScriptWitness([
sig,
input.witnessScript,
]),
}));
break;
case 'p2tr':
psbt.updateInput(0, {
tapLeafScript: [
{
leafVersion: 0xc0,
script: p.redeem.output,
controlBlock: p.witness[p.witness.length - 1],
},
],
});
psbt.finalizeInput(0, (inputIndex, input, _) => ({
finalScriptWitness: psbtutils.witnessStackToScriptWitness([
sig,
input.tapLeafScript[0].script,
input.tapLeafScript[0].controlBlock,
]),
}));
break;
default:
throw new Error('Invalid payment type');
}
return psbt;
};
const printTest = ({ name, psbt, flags }) => {
const prevOut = psbt.txInputs[0];
const asm = btc.script.decompile(psbt.data.inputs[0].witnessUtxo.script);
let spk = asm
.map(it =>
Buffer.isBuffer(it)
? `0x${it.length.toString(16)} 0x${it.toString('hex')}`
: it === btc.opcodes.OP_0
? '0'
: it === btc.opcodes.OP_1
? '1'
: `${rOps[it]}`,
)
.join(' ');
console.log(` ["${name}"],`);
console.log(` [[["${reverseBuffer(prevOut.hash).toString('hex')}",`);
console.log(` ${prevOut.index},`);
console.log(` "${spk}",`);
console.log(` ${psbt.data.inputs[0].witnessUtxo.value}]],`);
console.log(`"${psbt.extractTransaction().toHex()}",`);
console.log(` "${flags}"],`);
};
const validTests = [
() => {
const script = mkScript('plain', 'valid', 'schnorr');
const psbt = mkPsbt('p2tr', script, 'valid', 'schnorr');
return {
name: 'Test OP_CHECKSIGFROMSTACK',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('plain', 'valid', 'ecdsa');
const psbt = mkPsbt('p2tr', script, 'valid', 'schnorr');
const asm = btc.script.decompile(psbt.data.inputs[0].witnessUtxo.script);
return {
name: 'Test OP_CHECKSIGFROMSTACK succeeds with unknown key type',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK,DISCOURAGE_UPGRADABLE_PUBKEYTYPE',
};
},
() => {
const script = mkScript('zero-success', 'valid', 'schnorr');
const psbt = mkPsbt('p2tr', script, 'valid', 'zero');
return {
name: 'Test OP_CHECKSIGFROMSTACK yields 0 for 0-sig',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('plain', 'small', 'schnorr');
const psbt = mkPsbt('p2tr', script, 'valid', 'small');
return {
name: 'Test OP_CHECKSIGFROMSTACK, shorter message',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'valid', 'schnorr');
const psbt = mkPsbt('p2tr', script, 'valid', 'schnorr');
return {
name: 'Test Taproot OP_CHECKSIGFROMSTACKVERIFY',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'valid', 'ecdsa');
const psbt = mkPsbt('p2wsh', script, 'valid', 'ecdsa');
return {
name: 'Test P2WSH OP_CHECKSIGFROMSTACKVERIFY ECDSA',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'valid', 'schnorr');
const psbt = mkPsbt('p2wsh', script, 'valid', 'schnorr');
return {
name: 'Test P2WSH OP_CHECKSIGFROMSTACKVERIFY BIP340',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'small', 'schnorr');
const psbt = mkPsbt('p2wsh', script, 'valid', 'small');
return {
name: 'Test P2WSH OP_CHECKSIGFROMSTACKVERIFY BIP340 shorter message',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'valid', 'schnorr');
const psbt = mkPsbt('p2sh', script, 'valid', 'schnorr');
return {
name: 'Test P2SH OP_CHECKSIGFROMSTACKVERIFY BIP340',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
];
//validTests.forEach(test => printTest(test()));
const invalidTests = [
() => {
const script = mkScript('zero-success', 'invalid', 'schnorr');
const psbt = mkPsbt('p2tr', script, 'invalid', 'schnorr');
return {
name: 'Test OP_CHECKSIGFROMSTACK, fails immediately with sig for wrong data',
psbt,
flags: 'P2SH,WITNESS,TAPROOT,CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'small', 'ecdsa');
const psbt = mkPsbt('p2wsh', script, 'invalid', 'ecdsa');
return {
name: 'Test OP_CHECKSIGFROMSTACKVERIFY ECDSA, fails for small data',
psbt,
flags: 'P2SH,TAPROOT,CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'invalid', 'schnorr');
const psbt = mkPsbt('p2wsh', script, 'invalid', 'schnorr');
return {
name: 'Test OP_CHECKSIGFROMSTACKVERIFY fails with sig for wrong data',
psbt,
flags: 'P2SH,WITNESS,CHECKSIGFROMSTACK',
};
},
];
invalidTests.forEach(test => printTest(test()));
const btc = require('./src');
const ecc = require('tiny-secp256k1');
const psbtutils = require('./src/psbt/psbtutils');
btc.initEccLib(ecc);
const { address } = btc.payments.p2tr({
internalPubkey: Buffer.from(
'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
'hex',
),
network: btc.networks.regtest,
});
const leafScript = Buffer.from(
'cb2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac087',
'hex',
);
const nums = Buffer.from(
'50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0',
'hex',
);
const p = btc.payments.p2tr({
internalPubkey: nums,
scriptTree: { output: leafScript },
redeem: { output: leafScript },
network: btc.networks.regtest,
});
const psbt = new btc.Psbt({ network: btc.networks.regtest });
psbt.addInput({
hash: 'e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5',
index: 0,
witnessUtxo: { value: 155000, script: p.output },
});
psbt.updateInput(0, {
tapLeafScript: [
{
leafVersion: 0xc0,
script: p.redeem.output,
controlBlock: p.witness[p.witness.length - 1],
},
],
});
psbt.addOutput({ value: 150000, address });
psbt.finalizeInput(0, (inputIndex, input, _) => ({
finalScriptWitness: psbtutils.witnessStackToScriptWitness([
input.tapLeafScript[0].script,
input.tapLeafScript[0].controlBlock,
]),
}));
console.log({
pubkey: p.pubkey.toString('hex'),
tx: psbt.extractTransaction().toHex(),
leafScript: p.scriptTree.output.toString('hex'),
});
const { ECPairFactory } = require('ecpair');
const btc = require('./src');
const ecc = require('tiny-secp256k1');
const { secp256k1, schnorr } = require('@noble/curves/secp256k1');
const psbtutils = require('./src/psbt/psbtutils');
const { reverseBuffer } = require('./src/bufferutils');
const { REVERSE_OPS: rOps } = require('./src/ops');
btc.initEccLib(ecc);
const ECPair = ECPairFactory(ecc);
const data = {
small: Buffer.from('checksigfromstack', 'ascii'),
valid: Buffer.from(
'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
'hex',
),
invalid: Buffer.from(
'feadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
'hex',
),
};
const { address } = btc.payments.p2tr({
internalPubkey: Buffer.from(
schnorr.getPublicKey(secp256k1.utils.randomPrivateKey()),
),
network: btc.networks.regtest,
});
const sk = secp256k1.utils.randomPrivateKey();
const pubkeys = {
schnorr: schnorr.getPublicKey(sk),
ecdsa: secp256k1.getPublicKey(sk),
};
const sigs = {
small: schnorr.sign(data.small, sk),
schnorr: schnorr.sign(data.valid, sk),
ecdsa: secp256k1.sign(data.valid, sk, { lowS: true }).toDERRawBytes(),
zero: Buffer.of(),
};
const mkScript = (type, dataType, pkType) => {
const pk = pubkeys[pkType];
const m = data[dataType];
const baseScript = Buffer.concat([
Buffer.of(m.length),
m,
Buffer.of(pk.length),
pk,
]);
switch (type) {
case 'verify':
return Buffer.concat([
baseScript,
Buffer.of(0xb4),
Buffer.of(btc.opcodes.OP_2DROP),
]);
case 'plain':
return Buffer.concat([baseScript, Buffer.of(0xcc)]);
case 'zero-success':
return Buffer.concat([
baseScript,
Buffer.of(0xcc),
Buffer.of(btc.opcodes.OP_0),
Buffer.of(btc.opcodes.OP_EQUAL),
]);
default:
throw new Error('Invalid script type');
}
};
const nums = Buffer.from(
'50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0',
'hex',
);
const inputs = {
valid: 'e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5',
invalid: 'a2522fa96033c5736f3142ff616426cd03a3d0f077f609e22c5a33a96e04e597',
};
const mkPsbt = (type, script, inputType, sigType) => {
let opts = { network: btc.networks.regtest };
switch (type) {
case 'p2sh':
case 'p2wsh':
opts = { ...opts, redeem: { output: script } };
break;
case 'p2tr':
opts = {
...opts,
internalPubkey: nums,
scriptTree: { output: script },
redeem: { output: script },
};
break;
default:
throw new Error('Invalid payment type');
}
const p = btc.payments[type](opts);
const psbt = new btc.Psbt({ network: btc.networks.regtest });
psbt.addInput({
hash: inputs[inputType],
index: 0,
witnessUtxo: { value: 155000, script: p.output },
});
psbt.addOutput({ value: 150000, address });
const sig = sigs[sigType];
switch (type) {
case 'p2sh':
psbt.updateInput(0, { redeemScript: p.redeem.output });
psbt.finalizeInput(0, (inputIndex, input, _) => ({
finalScriptSig: btc.script.compile([
Buffer.from(sig),
input.redeemScript,
]),
}));
break;
case 'p2wsh':
psbt.updateInput(0, { witnessScript: p.redeem.output });
psbt.finalizeInput(0, (inputIndex, input, _) => ({
finalScriptWitness: psbtutils.witnessStackToScriptWitness([
sig,
input.witnessScript,
]),
}));
break;
case 'p2tr':
psbt.updateInput(0, {
tapLeafScript: [
{
leafVersion: 0xc0,
script: p.redeem.output,
controlBlock: p.witness[p.witness.length - 1],
},
],
});
psbt.finalizeInput(0, (inputIndex, input, _) => ({
finalScriptWitness: psbtutils.witnessStackToScriptWitness([
sig,
input.tapLeafScript[0].script,
input.tapLeafScript[0].controlBlock,
]),
}));
break;
default:
throw new Error('Invalid payment type');
}
return psbt;
};
const printTest = ({ name, psbt, flags }) => {
const prevOut = psbt.txInputs[0];
const asm = btc.script.decompile(psbt.data.inputs[0].witnessUtxo.script);
let spk = asm
.map(it =>
Buffer.isBuffer(it)
? `0x${it.length.toString(16)} 0x${it.toString('hex')}`
: it === btc.opcodes.OP_0
? '0'
: it === btc.opcodes.OP_1
? '1'
: `${rOps[it]}`,
)
.join(' ');
console.log(` ["${name}"],`);
console.log(` [[["${reverseBuffer(prevOut.hash).toString('hex')}",`);
console.log(` ${prevOut.index},`);
console.log(` "${spk}",`);
console.log(` ${psbt.data.inputs[0].witnessUtxo.value}]],`);
console.log(`"${psbt.extractTransaction().toHex()}",`);
console.log(` "${flags}"],`);
};
const validTests = [
() => {
const script = mkScript('plain', 'valid', 'schnorr');
const psbt = mkPsbt('p2tr', script, 'valid', 'schnorr');
return {
name: 'Test OP_CHECKSIGFROMSTACK',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('plain', 'valid', 'ecdsa');
const psbt = mkPsbt('p2tr', script, 'valid', 'schnorr');
const asm = btc.script.decompile(psbt.data.inputs[0].witnessUtxo.script);
return {
name: 'Test OP_CHECKSIGFROMSTACK succeeds with unknown key type',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK,DISCOURAGE_UPGRADABLE_PUBKEYTYPE',
};
},
() => {
const script = mkScript('zero-success', 'valid', 'schnorr');
const psbt = mkPsbt('p2tr', script, 'valid', 'zero');
return {
name: 'Test OP_CHECKSIGFROMSTACK yields 0 for 0-sig',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('plain', 'small', 'schnorr');
const psbt = mkPsbt('p2tr', script, 'valid', 'small');
return {
name: 'Test OP_CHECKSIGFROMSTACK, shorter message',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'valid', 'schnorr');
const psbt = mkPsbt('p2tr', script, 'valid', 'schnorr');
return {
name: 'Test Taproot OP_CHECKSIGFROMSTACKVERIFY',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'valid', 'ecdsa');
const psbt = mkPsbt('p2wsh', script, 'valid', 'ecdsa');
return {
name: 'Test P2WSH OP_CHECKSIGFROMSTACKVERIFY ECDSA',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'valid', 'schnorr');
const psbt = mkPsbt('p2wsh', script, 'valid', 'schnorr');
return {
name: 'Test P2WSH OP_CHECKSIGFROMSTACKVERIFY BIP340',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'small', 'schnorr');
const psbt = mkPsbt('p2wsh', script, 'valid', 'small');
return {
name: 'Test P2WSH OP_CHECKSIGFROMSTACKVERIFY BIP340 shorter message',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'valid', 'schnorr');
const psbt = mkPsbt('p2sh', script, 'valid', 'schnorr');
return {
name: 'Test P2SH OP_CHECKSIGFROMSTACKVERIFY BIP340',
psbt,
flags: 'DISCOURAGE_CHECKSIGFROMSTACK',
};
},
];
//validTests.forEach(test => printTest(test()));
const invalidTests = [
() => {
const script = mkScript('zero-success', 'invalid', 'schnorr');
const psbt = mkPsbt('p2tr', script, 'invalid', 'schnorr');
return {
name: 'Test OP_CHECKSIGFROMSTACK, fails immediately with sig for wrong data',
psbt,
flags: 'P2SH,WITNESS,TAPROOT,CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'small', 'ecdsa');
const psbt = mkPsbt('p2wsh', script, 'invalid', 'ecdsa');
return {
name: 'Test OP_CHECKSIGFROMSTACKVERIFY ECDSA, fails for small data',
psbt,
flags: 'P2SH,TAPROOT,CHECKSIGFROMSTACK',
};
},
() => {
const script = mkScript('verify', 'invalid', 'schnorr');
const psbt = mkPsbt('p2wsh', script, 'invalid', 'schnorr');
return {
name: 'Test OP_CHECKSIGFROMSTACKVERIFY fails with sig for wrong data',
psbt,
flags: 'P2SH,WITNESS,CHECKSIGFROMSTACK',
};
},
];
invalidTests.forEach(test => printTest(test()));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment