2024-11-21 01:24:10 -05:00
<!DOCTYPE html>
< html >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, user-scalable=no" >
< script src = "https://unpkg.com/@dashincubator/ripemd160/ripemd160.js" ></ script >
2025-11-11 17:37:16 +00:00
< script src = "https://supertestnet.github.io/bankify/super_nostr.js" ></ script >
2024-11-21 01:24:10 -05:00
< script src = "https://unpkg.com/@cmdcode/tapscript@1.4.0" ></ script >
2025-11-11 19:21:01 +00:00
< script src = "https://bundle.run/noble-secp256k1@1.2.14" ></ script >
<!--
2025-11-11 17:36:40 +00:00
<script src="file:///home/supertestnet/bitcoin_projects/testnet_generator/super_nostr.js"></script>
<script src="file:///home/supertestnet/bitcoin_projects/testnet_generator/tapscript@1.5.3"></script>
<script src="file:///home/supertestnet/bitcoin_projects/testnet_generator/noble-secp256k1@1.2.14"></script>
2025-11-11 17:37:16 +00:00
-->
2024-11-21 01:24:10 -05:00
< style >
* {
box-sizing : border-box ;
font-size : 1.15 rem ;
font-family : Arial , sans-serif ;
}
body {
margin : 0 ;
}
. inner_html {
max-width : 800 px ;
padding : 3 rem 1 rem ;
margin : auto ;
line-height : 1.25 ;
padding : 0 ;
}
. inner_body {
margin : 3 rem 1 rem ;
word-wrap : break-word ;
}
h1 {
font-size : 2 rem ;
}
h2 , h2 * {
font-size : 1.5 rem ;
}
input , textarea {
line-height : 1.25 ;
width : 100 % ;
height : 1.8 rem ;
font-size : 1.15 rem ;
border : 1 px solid grey ;
}
textarea {
height : 10 rem ;
}
. hidden {
display : none !important ;
}
. list_container {
overflow-y : scroll ;
}
. no_blocks_msg {
height : 248 px ;
line-height : 248 px ;
text-align : center ;
}
. list_of_blocks {
display : flex ;
justify-content : center ;
}
. block {
display : inline-block ;
height : 11.5 rem ;
width : 192 px ;
margin : 1 rem ;
cursor : pointer ;
overflow : hidden ;
flex-shrink : 0 ;
}
. block : hover {
background-color : pink ;
}
. block_inner {
display : inline-block ;
position : relative ;
height : 9.5 rem ;
width : 7.5 rem ;
margin : 1.5 rem ;
}
. block_back {
border : 1 px solid black ;
height : 6 rem ;
width : 6 rem ;
float : right ;
display : inline ;
background-color : #232838 ;
border-left : 0 ;
border-bottom : 0 ;
}
. block_front {
border : 1 px solid black ;
height : 6 rem ;
width : 6 rem ;
display : flex ;
align-items : center ;
position : absolute ;
bottom : 2 rem ;
background-color : #3953c6 ;
color : white ;
text-align : center ;
}
. block_front div {
width : 100 % ;
}
. block_num {
position : absolute ;
bottom : 0 rem ;
width : 80 % ;
text-align : center ;
}
. wallet {
border : 1 px solid black ;
border-radius : 1 rem ;
padding : 1 rem ;
text-align : center ;
margin-top : 1 rem ;
}
. auto_generate_label {
display : inline-block ;
}
. wallet_btns , . broadcast_btn , . auto_generate_label ,
2025-03-17 20:48:11 -04:00
. auto_go , . broadcast_success_label , . make_sharable {
2024-11-21 01:24:10 -05:00
margin-top : 1 rem ;
}
. block_info , . tx_info_label , . addy_info_label {
display : flex ;
justify-content : space-between ;
}
. block_explorer , . tx , . input , . output ,
2025-03-30 14:11:05 -04:00
. tx_explorer , . addy_explorer , . btc_script {
2024-11-21 01:24:10 -05:00
border : 1 px solid black ;
border-radius : 1 rem ;
padding : 1 rem ;
margin-bottom : 1 rem ;
}
. close_explorer {
cursor : pointer ;
}
. explorer_block_num , . explorer_txid ,
. explorer_addy , . close_explorer {
padding : 1 rem ;
}
. explorer_block_num , . explorer_addy ,
. explorer_txid {
padding-left : 0 ;
}
. txid_link , . block_link , . addy_link {
color : blue ;
text-decoration : underline ;
cursor : pointer ;
}
2025-03-15 22:25:47 -04:00
. pink {
background-color : pink ;
}
. lightblue {
background-color : lightblue ;
}
. yellow {
background-color : yellow ;
}
. green {
background-color : lightgreen ;
}
. white {
background-color : white ;
}
. grey {
background-color : #cccccc ;
}
2025-03-30 14:11:05 -04:00
. script_arguments_label , . script_program_label {
font-weight : bold ;
}
. btc_script {
. margin-bottom : auto ;
}
2024-11-21 01:24:10 -05:00
@ media screen and ( max-width : 600px ) {
}
</ style >
2025-03-17 20:48:11 -04:00
< style >
. black-bg {
width : 100 % ;
position : fixed ;
top : 0 ;
left : 0 ;
background-color : black ;
opacity : .5 ;
width : 100 vw ;
height : 100 vh ;
}
. modal {
position : fixed ;
box-sizing : border-box ;
top : 50 % ;
left : 50 % ;
transform : translate ( -50 % , -50 % );
width : 90 % ;
max-width : 560 px ;
background-color : white ;
border-radius : 1 rem ;
padding : 20 px ;
color : black ;
text-align : center ;
word-wrap : break-word ;
}
. modal * {
color : black ;
}
. modal input , . modal textarea {
max-width : 90 % ;
}
</ style >
< script >
var modalVanish = () => {
$ ( ".black-bg" ). classList . add ( "hidden" );
$ ( ".modal" ). classList . add ( "hidden" );
}
var showModal = content => {
$ ( ".modal" ). innerHTML = `<div class="x_modal" style="position: absolute;right: 1rem;top: 0.5rem;font-size: 2rem; cursor: pointer; color: black;" onclick="modalVanish()">×</div>` ;
$ ( ".modal" ). innerHTML += `<div style="overflow-y: auto; max-height: 80vh; margin-top: 1.5rem;"> ${ content } </div>` ;
$ ( ".black-bg" ). classList . remove ( "hidden" );
$ ( ".modal" ). classList . remove ( "hidden" );
}
</ script >
2024-11-21 01:24:10 -05:00
< script >
var $ = document . querySelector . bind ( document );
var $$ = document . querySelectorAll . bind ( document );
var url_params = new URLSearchParams ( window . location . search );
var url_keys = url_params . keys ();
var $_GET = {}
for ( var key of url_keys ) $_GET [ key ] = url_params . get ( key );
2025-03-15 22:25:47 -04:00
var hash_arr = window . location . href . substring ( window . location . href . indexOf ( "#" ) ). split ( "#" );
hash_arr . splice ( 0 , 1 );
var params = {}
hash_arr . forEach ( item => {
var vals = item . split ( "=" );
params [ vals [ 0 ] ] = vals [ 1 ];
});
2024-11-21 01:24:10 -05:00
</ script >
</ head >
< body >
< div class = "list_container" >
< div class = "no_blocks_msg" > Generate a block!</ div >
< div class = "list_of_blocks" ></ div >
</ div >
< div class = "inner_html" >
< div class = "inner_body" >
< h1 > Testnet generator</ h1 >
< div class = "block_explorer hidden" >
< h2 class = "block_info" >< span class = "explorer_block_num" ></ span >< span class = "close_explorer" > × </ span ></ h2 >
< p > Transaction count: < span class = "tx_count" > 0</ span ></ p >
< p > Timestamp: < span class = "block_timestamp" > None</ span ></ p >
< p > Subsidy: < span class = "block_subsidy" > 50 btc</ span ></ p >
< p > Fees: < span class = "block_fees" > 0 btc</ span ></ p >
< p style = "font-weight: bold;" > Transactions</ p >
< div class = "txs" ></ div >
</ div >
< div class = "tx_explorer hidden" >
< h2 class = "tx_info_label" >< span class = "explorer_txid" ></ span >< span class = "close_explorer" > × </ span ></ h2 >
< p > Number of inputs: < span class = "inputs_count" > 0</ span ></ p >
< p > Number of outputs: < span class = "outputs_count" > 0</ span ></ p >
< p > Mined in block: < span class = "mined_in_block" > None</ span ></ p >
< p > Timestamp: < span class = "tx_timestamp" > None</ span ></ p >
< p > Tx size: < span class = "tx_size" > 0 bytes</ span ></ p >
< p > Tx fee: < span class = "tx_fee" > 0 btc</ span ></ p >
< p > Feerate: < span class = "tx_feerate" > 0 sats per byte</ span ></ p >
< div class = "tx_info" ></ div >
</ div >
< div class = "addy_explorer hidden" >
< h2 class = "addy_info_label" >< span class = "explorer_addy" ></ span >< span class = "close_explorer" > × </ span ></ h2 >
< p class = "full_address" ></ p >
< p > Times this address received money: < span class = "received_count" > 0</ span ></ p >
< p > Times this address sent money: < span class = "sent_count" > 0</ span ></ p >
< p > Total amount received: < span class = "total_received" > 0</ span ></ p >
< p > Total amount sent: < span class = "total_sent" > 0</ span ></ p >
< p > Address balance: < span class = "addy_balance" > 0</ span ></ p >
< div class = "addy_tx_info" ></ div >
</ div >
< div class = "stuff_thats_not_an_explorer" >
2025-03-15 22:25:47 -04:00
< div >< button class = "generate_block" disabled > Generate block</ button ></ div >
2025-03-17 20:48:11 -04:00
< div class = "options" >
< div class = "auto_gen" >
< span class = "auto_generate_label" > Auto generator: </ span >
< select class = "auto_generate" >
< option > --- Auto generate ---</ option >
< option > 1/2 second</ option >
< option > 1 second</ option >
< option > 3 seconds</ option >
< option > 5 seconds</ option >
< option > 10 seconds</ option >
< option > 30 seconds</ option >
< option > 1 minute</ option >
< option > 2 minutes</ option >
< option > 5 minutes</ option >
< option > 10 minutes</ option >
</ select >
< button class = "auto_go" disabled > Go</ button >
</ div >
< div class = "make_sharable" >
< button class = "make_sharable_testnet" > Make a sharable testnet</ button >
</ div >
2024-11-21 01:24:10 -05:00
</ div >
< div class = "wallet" >
< h2 > Your wallet</ h2 >
< p > Your address:</ p >
< p class = "your_addy" ></ p >
< div class = "balance_div" > Balance: < span class = "balance" > 0</ span > btc</ div >
< div class = "wallet_btns" >< button class = "send_btn" > Send</ button ></ div >
</ div >
< h2 class = "txs_in_mempool_label" > Transactions in mempool: < span class = "txs_in_mempool" > 0</ span ></ h2 >
< p > Enter a transaction you want to broadcast</ p >
< textarea class = "tx_broadcaster" ></ textarea >
< div class = "broadcast_btn" >< button > Broadcast</ button ></ div >
< div class = "broadcast_success_label hidden" > Success: < span class = "broadcast_success_txid txid_link" ></ span ></ div >
</ div >
</ div >
</ div >
< script >
var testnet_generator = {
blocks: {},
utxo_set: {},
mempool: [],
utxos: {},
addresses: {},
2025-11-11 17:36:40 +00:00
// relays: [ "wss://no.str.cr" ],
relays: [ "ws://127.0.0.1:6969/" ],
2025-03-17 20:48:11 -04:00
blocks_relayed: [],
2025-03-15 22:25:47 -04:00
addykind: "testnet",
2024-11-21 01:24:10 -05:00
hexToBytes: hex => Uint8Array.from( hex.match( /.{1,2}/g ).map( byte => parseInt( byte, 16 ) ) ),
bytesToHex: bytes => bytes.reduce( ( str, byte ) => str + byte.toString( 16 ).padStart( 2, "0" ), "" ),
getPrivkey: () => testnet_generator.bytesToHex( nobleSecp256k1.utils.randomPrivateKey() ),
getPubkey: privkey => nobleSecp256k1.getPublicKey( privkey, true ).substring( 2 ),
2025-03-15 22:25:47 -04:00
getAddy: privkey => {
if ( !privkey ) privkey = testnet_generator.getPrivkey();
2024-11-21 01:24:10 -05:00
var pubkey = testnet_generator.getPubkey( privkey );
2025-03-15 22:25:47 -04:00
var addy = tapscript.Address.p2tr.fromPubKey( pubkey, testnet_generator.addykind );
2024-11-21 01:24:10 -05:00
return [ addy, privkey ];
},
intToHex: int => {
var hex = BigInt( int ).toString( 16 );
if ( hex.length % 2 ) hex = "0" + hex;
return hex;
},
reverseHexString: s => s.match(/[a-fA-F0-9]{2}/g).reverse().join(''),
littleEndianSignedHexToDecimal: hex => {
var rev = testnet_generator.reverseHexString( hex );
var first_byte = rev.substring( 0, 2 );
first_byte_as_decimal = parseInt( first_byte, 16 );
if ( first_byte_as_decimal < 128 ) {
return parseInt ( rev , 16 );
} else {
// get the number of bytes in the hex string
var num_of_bytes_in_hex_string = rev.length / 2 ;
// get the corresponding max X-byte number
var maxnum = ( 2 ** ( num_of_bytes_in_hex_string * 8 ) );
// halve it
var maxnum_halved = maxnum / 2 ;
// subtract the hex number from that
return maxnum_halved - parseInt ( rev , 16 );
}
},
2024-11-21 12:24:13 -05:00
hexToBinary: hex = > {
var array_hex = hex.match( /\ w { 2 }/ g );
var array_bin = [];
array_hex . forEach ( item = > array_bin.push( ( parseInt ( item , 16 ). toString ( 2 ) ). padStart ( 8 , ' 0 ' ) ) );
return array_bin . join ( "" );
},
2024-11-21 01:24:10 -05:00
isValidHex: hex = > {
if ( ! hex ) return ;
var length = hex.length;
if ( length % 2 ) return ;
try {
var bigint = BigInt( " 0x " + hex , " hex " );
} catch ( e ) {
return ;
}
var prepad = bigint.toString( 16 );
var i ; for ( i = 0; i < length ; i ++ ) prepad = "0" + prepad ;
var padding = prepad.slice( -Math . abs ( length ) );
return ( padding = == hex );
},
2025-03-15 22:25:47 -04:00
isValidAddress: address = > {
try {
return !! tapscript . Address . decode ( address ). script ;
} catch ( e ) { return ;}
return ;
},
2024-11-21 01:24:10 -05:00
isZeroNegative: zero = > {
var isZero = zero === 0 ;
var isNegative = 1 / zero = == -Infinity ;
return isNegative && isZero ;
},
waitSomeTime: num = > new Promise ( resolve = > setTimeout( resolve , num ) ),
sha256: s = > {
if ( typeof s = = " string " ) s = new TextEncoder (). encode ( s );
return crypto . subtle . digest ( ' SHA-256 ', s ). then ( hashBuffer = > {
var hashArray = Array.from( new Uint8Array ( hashBuffer ) );
var hashHex = hashArray
. map ( bytes = > bytes.toString( 16 ). padStart ( 2 , ' 0 ' ) )
. join ( '' );
return hashHex ;
});
},
sha1: s = > {
if ( typeof s = = " string " ) s = new TextEncoder (). encode ( s );
return crypto . subtle . digest ( ' SHA-1 ', s ). then ( hashBuffer = > {
var hashArray = Array.from( new Uint8Array ( hashBuffer ) );
var hashHex = hashArray
. map ( bytes = > bytes.toString( 16 ). padStart ( 2 , ' 0 ' ) )
. join ( '' );
return hashHex ;
});
},
rmd160: s = > {
if ( typeof s = = " string " ) s = new TextEncoder (). encode ( s );
var hash = RIPEMD160.create();
hash . update ( new Uint8Array ( s ) );
return testnet_generator . bytesToHex ( hash . digest () );
},
getVin: ( txid , vout , amnt , addy ) = > ({
txid,
vout,
prevout: {
value: amnt,
2025-03-15 22:25:47 -04:00
scriptPubKey: testnet_generator.isValidAddress( addy ) ? tapscript.Address.toScriptPubKey( addy ) : addy,
2024-11-21 01:24:10 -05:00
},
}),
getVout: ( amnt, addy ) => ({
value: amnt,
2025-03-15 22:25:47 -04:00
scriptPubKey: testnet_generator.isValidAddress( addy ) ? tapscript.Address.toScriptPubKey( addy ) : addy,
2024-11-21 01:24:10 -05:00
}),
satsToBitcoin: sats => {
var btc = String( sats ).padStart( 8, "0" ).slice( 0,-8 ) + "." + String( sats ).padStart( 8, "0" ).slice( -8 );
if ( btc.endsWith( "00000" ) ) {
btc = btc.substring( 0, btc.length - 5 );
var i; for ( i=0; i< 5 ; i ++ ) {
if ( btc . endsWith ( " 0 " ) ) btc = btc.substring( 0 , btc . length - 1 );
}
if ( btc . endsWith ( "." ) ) btc = btc.substring( 0 , btc . length - 1 );
if ( ! btc ) btc = 0;
}
return btc ;
},
showTx: ( txhex , idx ) = > {
2025-03-30 14:51:05 -04:00
var unique = testnet_generator.getPrivkey();
2024-11-21 01:24:10 -05:00
if ( idx === undefined ) idx = 1;
2025-03-15 22:25:47 -04:00
var background_color = "pink";
if ( idx % 2 ) background_color = "lightblue";
2024-11-21 01:24:10 -05:00
var txs_html = ``;
var tx = tapscript.Tx.decode( txhex );
var txid = tapscript.Tx.util.getTxid( tx );
var display_fee = !!idx ? "" : "hidden";
var tx_html = `
2025-03-30 14:51:05 -04:00
< div class = "tx ${background_color} tx_${txid} unique_${unique}" >
2025-03-15 22:25:47 -04:00
< p > Tx: < span class = "txnum" > ${idx}</ span ></ p >
2024-11-21 01:24:10 -05:00
< div > Txid: < span class = "txid txid_link" data-txhex = "${txhex}" data-idx = "${idx}" > ${txid}</ span ></ div >
< div class = "${display_fee}" > Fee: < span class = "fee" > 0</ span > btc</ div >
< div class = "inputs" ></ div >
< div class = "outputs" ></ div >
</ div >
`;
var div = document.createElement( 'div' );
div.innerHTML = tx_html;
var inputs_html = ``;
var outputs_html = ``;
tx.vin.forEach( ( input, index ) => {
if ( !index ) inputs_html = `< p style = "text-align: center;" > Inputs</ p > `;
var input_id = `${input[ "txid" ]}_${input[ "vout" ]}`;
if ( !idx ) var from_addy = "";
else var from_addy = testnet_generator.utxo_set[ input_id ].addy;
2025-03-15 22:25:47 -04:00
try {
from_addy = tapscript.Address.fromScriptPubKey( from_addy, testnet_generator.addykind );
} catch( e ) {}
2024-11-21 01:24:10 -05:00
if ( !idx ) var amnt_sent = "";
else var amnt_sent = testnet_generator.utxo_set[ input_id ].amnt;
var input_txid = input[ "txid" ];
var input_vout = input[ "vout" ];
var input_idx = 0;
if ( `${input[ "txid" ]}_${input[ "vout" ]}` in testnet_generator.utxo_set ) input_idx = testnet_generator.utxo_set[ `${input[ "txid" ]}_${input[ "vout" ]}` ][ "idx" ];
if ( !idx ) var input_txhex = "";
else var input_txhex = testnet_generator.utxo_set[ `${input[ "txid" ]}_${input[ "vout" ]}` ][ "txhex" ];
2025-03-15 22:25:47 -04:00
var vin_bg_color = "yellow";
if ( index % 2 ) vin_bg_color = "green";
if ( !idx ) inputs_html = inputs_html + `< div class = "input ${vin_bg_color}" > Coinbase</ div > `;
2024-11-21 01:24:10 -05:00
else inputs_html = inputs_html + `
2025-03-15 22:25:47 -04:00
< div class = "input ${vin_bg_color}" >
2024-11-21 01:24:10 -05:00
< p > "From" address: < span class = "from_address addy_link" > ${from_addy}</ span ></ p >
< p > Amount sent from this address in this tx: < span class = "amnt_sent" > ${testnet_generator.satsToBitcoin( amnt_sent )} btc</ span ></ p >
< p > Tx that originally created this input: < span class = "input_txid txid_link" data-txhex = "${input_txhex}" data-idx = "${input_idx}" > ${input_txid}</ span ></ p >
< p > Vout of that tx: < span class = "input_vout" > ${input[ "vout" ]}</ span ></ p >
2025-03-30 14:51:05 -04:00
< p >< button class = "view_script" onclick = "testnet_generator.viewScript( '${input_txid}_${input[ " vout " ]}', '${ txid } _ ${ index }', '${ unique }' );" > View bitcoin script</ button ></ p >
2025-03-30 14:11:05 -04:00
< div class = "btc_script hidden" >
< p class = "script_arguments_label" > Arguments</ p >
2025-03-30 14:51:05 -04:00
< div class = "script_arguments" ></ div >
2025-03-30 14:11:05 -04:00
< p class = "script_program_label" > Program</ p >
2025-03-30 14:51:05 -04:00
< div class = "script_program" ></ div >
2025-03-30 14:11:05 -04:00
</ div >
2024-11-21 01:24:10 -05:00
</ div >
`;
});
div.getElementsByClassName( "inputs" )[ 0 ].innerHTML = inputs_html;
tx.vout.forEach( ( output, index ) => {
if ( !index ) outputs_html = `< p style = "text-align: center;" > Outputs</ p > `;
var output_id = `${txid}_${index}`;
var addy = testnet_generator.utxo_set[ output_id ].addy;
2025-03-15 22:25:47 -04:00
try {
addy = tapscript.Address.fromScriptPubKey( addy, testnet_generator.addykind );
} catch( e ) {}
2024-11-21 01:24:10 -05:00
var amnt = testnet_generator.utxo_set[ output_id ].amnt;
2025-03-15 22:25:47 -04:00
var vout_bg_color = "white";
if ( index % 2 ) vout_bg_color = "grey";
2024-11-21 01:24:10 -05:00
outputs_html = outputs_html + `
2025-03-15 22:25:47 -04:00
< div class = "output ${vout_bg_color}" >
2025-02-25 17:52:48 -05:00
< p > Vout: < span class = "which_vout_is_this" > ${index}</ span ></ p >
2024-11-21 01:24:10 -05:00
< p > "To" address: < span class = "to_address addy_link" > ${addy}</ span ></ p >
< p > Amount received by this address in this tx: < span class = "amnt_received" > ${testnet_generator.satsToBitcoin( amnt )} btc</ span ></ p >
</ div >
`;
});
div.getElementsByClassName( "outputs" )[ 0 ].innerHTML = outputs_html;
var sum_of_inputs = 0;
tx.vin.forEach( input => {
if ( !idx ) return;
var input_id = `${input[ "txid" ]}_${input[ "vout" ]}`;
var amnt = testnet_generator.utxo_set[ input_id ].amnt;
sum_of_inputs = sum_of_inputs + amnt;
});
var sum_of_outputs = 0;
tx.vout.forEach( output => sum_of_outputs = sum_of_outputs + Number( output[ "value" ] ) );
if ( !idx ) sum_of_outputs = 0;
div.getElementsByClassName( "fee" )[ 0 ].innerText = testnet_generator.satsToBitcoin( sum_of_inputs - sum_of_outputs );
txs_html = txs_html + div.innerHTML;
return txs_html;
},
2025-03-17 20:48:11 -04:00
showSoloTx: ( txid, txhex, idx, block_mined_if_coinbase ) => {
2024-11-21 01:24:10 -05:00
$( '.explorer_txid' ).innerText = `Tx ${txid.substring( 0, 4 )}...${txid.substring( txid.length - 4 )}`;
$( '.tx_info' ).innerHTML = testnet_generator.showTx( txhex, idx );
$$( '.close_explorer' ).forEach( close_btn => {
close_btn.onclick = () => {
showPage( 'stuff_thats_not_an_explorer' );
}
});
var tx = tapscript.Tx.decode( txhex );
var inputs_count = tx.vin.length;
$( '.inputs_count' ).innerText = inputs_count;
var outputs_count = tx.vout.length;
$( '.outputs_count' ).innerText = outputs_count;
if ( !isNaN( testnet_generator.utxo_set[ `${txid}_0` ].block_created ) ) {
var mined_in_block = testnet_generator.utxo_set[ `${txid}_0` ].block_created;
$( '.mined_in_block' ).innerHTML = `${mined_in_block} [< span class = "block_link" data-blocknum = "${mined_in_block}" > view block</ span > ]`;
$( '.block_link' ).onclick = e => {testnet_generator.showBlockDetails( e.target.getAttribute( "data-blocknum" ) )}
var blockhash = testnet_generator.intToHex( mined_in_block ).padStart( 64, "0" );
var timestamp = testnet_generator.blocks[ blockhash ][ "timestamp" ];
$( '.tx_timestamp' ).innerText = String( new Date( timestamp * 1000 ) );
} else {
var mined_in_block = "not mined yet";
2025-03-17 20:48:11 -04:00
if ( block_mined_if_coinbase ) mined_in_block = block_mined_if_coinbase;
2024-11-21 01:24:10 -05:00
$( '.mined_in_block' ).innerHTML = `${mined_in_block}`;
}
2025-11-11 19:21:01 +00:00
var tx_size = tapscript.Tx.util.getTxSize( tapscript.Tx.create( txhex ) );
2024-11-21 01:24:10 -05:00
$( '.tx_size' ).innerText = `${tx_size} bytes`;
var sum_of_inputs = 0;
tx.vin.forEach( input => {
if ( !idx ) return;
var input_id = `${input[ "txid" ]}_${input[ "vout" ]}`;
var amnt = testnet_generator.utxo_set[ input_id ].amnt;
sum_of_inputs = sum_of_inputs + amnt;
});
var sum_of_outputs = 0;
tx.vout.forEach( output => sum_of_outputs = sum_of_outputs + Number( output[ "value" ] ) );
if ( !idx ) sum_of_outputs = 0;
var tx_fee_in_sats = sum_of_inputs - sum_of_outputs;
$( '.tx_fee' ).innerText = testnet_generator.satsToBitcoin( tx_fee_in_sats );
$( '.tx_feerate' ).innerText = `${( tx_fee_in_sats / tx_size ).toFixed( 2 )} sats per byte`;
window.scrollTo( 0, 0 );
$$( '.txid_link' ).forEach( item => {
item.onclick = e => {
var txid = e.target.innerText;
var txhex = e.target.getAttribute( "data-txhex" );
var idx = Number( e.target.getAttribute( "data-idx" ) );
showPage( 'tx_explorer' );
2025-03-17 20:48:11 -04:00
testnet_generator.showSoloTx( txid, txhex, idx, block_mined_if_coinbase );
2024-11-21 01:24:10 -05:00
}
});
2025-03-30 14:57:55 -04:00
$$( '.addy_link' ).forEach( item => {
item.onclick = e => {
var addy = e.target.innerText;
showPage( 'addy_explorer' );
testnet_generator.showAddyInfo( addy );
}
});
2024-11-21 01:24:10 -05:00
},
2025-03-15 22:25:47 -04:00
showAddyInfo: addy => {
2024-11-21 01:24:10 -05:00
$( '.explorer_addy' ).innerText = `${addy.substring( 0, 4 )}...${addy.substring( addy.length - 4 )}`;
$( '.full_address' ).innerText = addy;
2025-03-15 22:25:47 -04:00
var spky = addy !== "51024e73" ? tapscript.Script.encode( tapscript.Address.toScriptPubKey( addy ) ).hex.substring( 2 ) : "51024e73";
$( '.received_count' ).innerText = testnet_generator.addresses[ spky ].received.length;
$( '.sent_count' ).innerText = testnet_generator.addresses[ spky ].sent.length;
2024-11-21 01:24:10 -05:00
var sent = [];
var total_sent = 0;
var total_sent_to_self = 0;
2025-03-15 22:25:47 -04:00
testnet_generator.addresses[ spky ].sent.forEach( input_id => {
2024-11-21 01:24:10 -05:00
var txhex = testnet_generator.utxo_set[ input_id.substring( 0, 65 ) + "0" ].txhex;
sent.push( [ txhex, 1, undefined ] );
var tx = tapscript.Tx.decode( txhex );
var input_i_seek = Number( input_id.substring( 65 ) );
var input = tx.vin[ input_i_seek ];
var utxo_id = `${input[ "txid" ]}_${input[ "vout" ]}`;
var recipients = [];
tx.vout.forEach( ( vout, index ) => {
2025-03-15 22:25:47 -04:00
var recipient = vout[ "scriptPubKey" ];
if ( recipient !== spky ) return;
2024-11-21 01:24:10 -05:00
total_sent_to_self = total_sent_to_self + Number( vout[ "value" ] );
});
var txinfo = testnet_generator.utxo_set[ utxo_id ];
total_sent = total_sent + txinfo.amnt;
});
$( '.total_sent' ).innerText = `${testnet_generator.satsToBitcoin( total_sent - total_sent_to_self )} btc`;
var received = [];
var total_received = 0;
2025-03-15 22:25:47 -04:00
testnet_generator.addresses[ spky ].received.forEach( output_id => {
2024-11-21 01:24:10 -05:00
var txinfo = testnet_generator.utxo_set[ output_id ];
received.push( [ txinfo.txhex, txinfo.idx, txinfo.block_created ] );
total_received = total_received + txinfo.amnt;
});
$( '.total_received' ).innerText = `${testnet_generator.satsToBitcoin( total_received - total_sent_to_self )} btc`;
$( '.addy_balance' ).innerText = `${testnet_generator.satsToBitcoin( ( total_received - total_sent_to_self ) - ( total_sent - total_sent_to_self ) )} btc`;
var txs_html = `< p style = "text-align: center; font-weight: bold;" > Transactions where this address received money</ p > `;
received.forEach( tx => {
var [ txhex, idx, blocknum ] = tx;
txs_html = txs_html + testnet_generator.showTx( txhex, idx, blocknum );
});
txs_html = txs_html + `< p style = "text-align: center; font-weight: bold;" > Transactions where this address sent money</ p > `;
sent.forEach( tx => {
var [ txhex, idx, blocknum ] = tx;
txs_html = txs_html + testnet_generator.showTx( txhex, idx, blocknum );
});
$( '.addy_tx_info' ).innerHTML = txs_html;
$$( '.close_explorer' ).forEach( close_btn => {
close_btn.onclick = () => {
showPage( 'stuff_thats_not_an_explorer' );
}
});
window.scrollTo( 0, 0 );
$$( '.txid_link' ).forEach( item => {
item.onclick = e => {
var txid = e.target.innerText;
var txhex = e.target.getAttribute( "data-txhex" );
var idx = Number( e.target.getAttribute( "data-idx" ) );
showPage( 'tx_explorer' );
2025-03-17 20:48:11 -04:00
var block_mined_if_coinbase = null;
//TODO: fix the block_mined_if_coinbase value
var block_mined_if_coinbase = null;
if ( idx === 0 ) block_mined_if_coinbase = 5;
testnet_generator.showSoloTx( txid, txhex, idx, block_mined_if_coinbase );
2024-11-21 01:24:10 -05:00
}
});
},
showBlockDetails: num => {
var blockhash = testnet_generator.intToHex( num ).padStart( 64, "0" );
var timestamp = testnet_generator.blocks[ blockhash ][ "timestamp" ];
var txs = testnet_generator.blocks[ blockhash ][ "txs" ];
showPage( 'block_explorer' );
$( '.explorer_block_num' ).innerText = `Block ${num}`;
$$( '.close_explorer' ).forEach( close_btn => {
close_btn.onclick = () => {
showPage( 'stuff_thats_not_an_explorer' );
}
});
$( '.tx_count' ).innerText = txs.length;
$( '.block_timestamp' ).innerText = String( new Date( timestamp * 1000 ) );
$( '.block_fees' ).innerText = `${testnet_generator.satsToBitcoin( testnet_generator.blocks[ blockhash ][ "fees" ] )} btc`;
var txs_html = ``;
txs.forEach( ( txhex, idx ) => {
txs_html = txs_html + testnet_generator.showTx( txhex, idx, num );
});
$( '.txs' ).innerHTML = txs_html;
$$( '.txid_link' ).forEach( item => {
item.onclick = e => {
var txid = e.target.innerText;
var txhex = e.target.getAttribute( "data-txhex" );
var idx = Number( e.target.getAttribute( "data-idx" ) );
showPage( 'tx_explorer' );
testnet_generator.showSoloTx( txid, txhex, idx, num );
}
});
$$( '.addy_link' ).forEach( item => {
item.onclick = e => {
var addy = e.target.innerText;
showPage( 'addy_explorer' );
testnet_generator.showAddyInfo( addy );
}
});
},
2025-03-15 22:25:47 -04:00
processExistingBlocks: async () => {
$( '.no_blocks_msg' ).innerText = "syncing...";
var privkey = null;
if ( params.hasOwnProperty( "privkey" ) ) privkey = params.privkey;
var pubkey = privkey ? testnet_generator.getPubkey( privkey ) : params.pubkey;
var relay = testnet_generator.relays[ 0 ];
var num_of_blocks_expected = 1;
var events = await super_nostr.getEvents( relay, null, [ pubkey ], [ 10426 ], null, null, 1 );
if ( events.length ) num_of_blocks_expected = Number( events[ 0 ].content );
2025-03-17 20:48:11 -04:00
else return alert( 'error: the block headers could not be synced, so the blocks cannot be synced either. refresh the page to try again' );
2025-03-15 22:25:47 -04:00
$( '.no_blocks_msg' ).innerText = `synced 0 out of ${num_of_blocks_expected} blocks`;
2025-03-17 20:48:11 -04:00
var outerLoop = async ( since = null ) => {
var since_to_use_in_outer_loop = since;
var syncLoop = async ( since = null ) => {
var ids = null;
var authors = [ pubkey ];
var kinds = [ 60426 ];
var until = null;
var limit = 15;
var all_events = [];
var innerLoop = async since => {
var events = await super_nostr.getEvents( relay, ids, authors, kinds, until, since, limit );
var events_already_known = 0;
events.forEach( event => {
var event_ids = [];
all_events.forEach( evt => event_ids.push( evt.id ) );
if ( event_ids.includes( event.id ) ) {
events_already_known = events_already_known + 1;
return;
}
all_events.push( event );
});
$( '.no_blocks_msg' ).innerText = `synced ${all_events.length} out of ${num_of_blocks_expected} blocks`;
if ( !events.length || events_already_known === events.length ) return;
if ( all_events.length === num_of_blocks_expected ) {
$( '.no_blocks_msg' ).innerText = `synced ${num_of_blocks_expected} out of ${num_of_blocks_expected} blocks`;
await testnet_generator.waitSomeTime( 200 );
2025-03-15 22:25:47 -04:00
return;
}
2025-03-17 20:48:11 -04:00
var since = events[ 0 ].created_at - 1;
since_to_use_in_outer_loop = since;
await testnet_generator.waitSomeTime( 1 );
await innerLoop( since );
2025-03-15 22:25:47 -04:00
}
await innerLoop( since );
2025-03-17 20:48:11 -04:00
return all_events;
}
var all_events = await syncLoop( since );
if ( !all_events.length && $( '.generate_block' ) ) $( '.generate_block' ).disabled = false;
all_events.sort( ( a, b ) => parseInt( a.tags[ 0 ][ 1 ], 16 ) - parseInt( b.tags[ 0 ][ 1 ], 16 ) );
var i; for ( i=0; i< all_events.length ; i ++ ) {
var data = all_events[ i ];
var first_block_id = data.tags[ 0 ][ 1 ];
var blocks = JSON.parse( data . content );
var j ; for ( j = 0; j < blocks . length ; j ++ ) {
var block_data = blocks[ j ];
var blocknum = parseInt( first_block_id , 16 ) + j ;
var block_id = testnet_generator.intToHex( blocknum ). padStart ( 64 , " 0 " );
if ( Object . keys ( testnet_generator . blocks ). includes ( block_id ) ) continue ;
testnet_generator . blocks_relayed . push ( block_id );
testnet_generator . makeBlock ( block_id , block_data );
}
}
if ( $( '. generate_block ' ) ) $( '. generate_block ' ). disabled = false;
if ( params . hasOwnProperty ( " pubkey " ) ) {
await testnet_generator . waitSomeTime ( 5_000 );
await outerLoop ( since_to_use_in_outer_loop );
} else {
testnet_generator . relayBlocks ();
2024-11-21 01:24:10 -05:00
}
2025-03-15 22:25:47 -04:00
}
2025-03-17 20:48:11 -04:00
outerLoop ();
},
validateTx: async ( txhex , allow_zero_fee , parent_tx ) = > {
var tx = null;
try {
tx = tapscript.Tx.decode( txhex );
} catch ( e ) {}
if ( !tx || typeof tx != "object" ) return 'err 0, that tx was invalid for unknown reasons';
try {
2025-03-15 22:25:47 -04:00
var sum_of_inputs = 0;
tx.vin.forEach( input => {
var input_id = `${input[ "txid" ]}_${input[ "vout" ]}`;
2025-03-17 20:48:11 -04:00
var input_to_work_with = testnet_generator.utxo_set[ input_id ];
if ( !input_to_work_with && !!parent_tx ) {
var txid_of_parent = tapscript.Tx.util.getTxid( parent_tx );
if ( txid_of_parent === input[ "txid" ] ) {
var parent_tx_to_work_with = tapscript.Tx.decode( parent_tx );
if ( parent_tx_to_work_with.vout.length < input [ " vout " ] ) throw ( ' input does not exist ' );
input_to_work_with = parent_tx_to_work_with.vout[ input [ " vout " ] ];
}
}
var amnt = input_to_work_with.hasOwnProperty( " amnt " ) ? input_to_work_with . amnt : Number ( input_to_work_with . value );
2025-03-15 22:25:47 -04:00
sum_of_inputs = sum_of_inputs + amnt ;
});
var sum_of_outputs = 0;
2025-03-17 20:48:11 -04:00
tx . vout . forEach ( output = > sum_of_outputs = sum_of_outputs + Number ( output [ " value " ] ) );
var tx_fee_in_sats = sum_of_inputs - sum_of_outputs ;
2025-11-11 19:21:01 +00:00
var tx_size = tapscript.Tx.util.getTxSize( tapscript . Tx . create ( txhex ) );
2025-03-17 20:48:11 -04:00
var feerate = Number( ( tx_fee_in_sats / tx_size ). toFixed ( 2 ) );
} catch ( e ) {
console . log ( tx );
return ' err 1 , that tx was invalid for unknown reasons ';
}
if ( feerate < 0 ) return ' nice try , attempting to print money via tx fees ';
if ( feerate = == 0 && ! allow_zero_fee ) return ' that tx paid no fee ';
if ( feerate < 1 && ! allow_zero_fee ) return ' that tx paid a fee between 0 and 1 sat per byte , which is not allowed ';
var input_that_isnt_real = -1;
var input_with_insufficient_witness_length = -1;
var input_with_too_many_witness_items = -1;
var input_with_invalid_sig = -1;
var input_whose_stack_is_too_small = -1;
var input_with_improperly_formatted_sig = -1;
var input_with_improperly_formatted_element = -1;
var input_with_improperly_formatted_element = -1;
var input_whose_script_failed_a_verify_opcode = -1;
var input_with_op_nop = -1;
var input_whose_script_left_multiple_elements_on_the_stack = -1;
var input_whose_script_ended_on_false = -1;
var input_with_if_branch_preceded_by_something_other_than_1_or_0 = -1;
var input_with_script_containing_op_return = -1;
var input_whose_script_treats_a_string_as_an_integer = -1;
var input_whose_script_tried_to_get_a_nonexistent_element_from_the_stack = -1;
var input_whose_script_failed_CLTV_due_to_not_enough_time_passed = -1;
var input_whose_script_uses_a_negative_number_for_CLTV_or_CSV = -1;
var input_whose_locktime_value_does_not_match_the_CLTV_value = -1;
var input_whose_script_uses_CLTV_but_the_sequence_number_is_maxxed_out = -1;
var input_whose_script_failed_CSV = -1;
var input_whose_script_failed_relative_timelock = -1;
var i ; for ( i = 0; i < tx . vin . length ; i ++ ) {
var input = tx.vin[ i ];
var index = i;
var input_id = `${input[ " txid " ]} _ ${ input [ " vout " ]}`;
var input_exists = false;
if ( input_id in testnet_generator . utxo_set ) input_exists = true;
else input_exists = false;
var item = testnet_generator.utxo_set[ input_id ];
if ( item && item . spent ) input_exists = false;
var input_to_work_with = null;
if ( ! item && parent_tx ) {
var txid_of_parent = tapscript.Tx.util.getTxid( parent_tx );
if ( txid_of_parent = == input [ " txid " ] ) {
var parent_tx_to_work_with = tapscript.Tx.decode( parent_tx );
if ( parent_tx_to_work_with . vout . length < input [ " vout " ] ) {
input_that_isnt_real = index;
break ;
}
input_to_work_with = parent_tx_to_work_with.vout[ input [ " vout " ] ];
input_to_work_with . addy = input_to_work_with.scriptPubKey;
input_to_work_with . amnt = Number( input_to_work_with . value );
input_to_work_with . block_created = Object.keys( testnet_generator . blocks ). length - 1 ;
try {
input_to_work_with . addy = tapscript.Address.fromScriptPubKey( input_to_work_with . addy );
} catch ( e ) {}
2025-03-15 22:25:47 -04:00
}
2025-03-17 20:48:11 -04:00
}
if ( ! input_exists && ! input_to_work_with ) {
input_that_isnt_real = index;
break ;
}
if ( item ) input_to_work_with = item;
var addy = input_to_work_with.addy;
var amnt_to_use_in_moments = input_to_work_with.amnt;
try {
addy = tapscript.Address.fromScriptPubKey( addy , testnet_generator . addykind );
} catch ( e ) {}
var scpk = testnet_generator.isValidAddress( addy ) ? tapscript . Address . toScriptPubKey ( addy ) : addy ;
tx . vin [ i ]. prevout = {
value: amnt_to_use_in_moments ,
scriptPubKey: scpk ,
}
}
if ( input_that_isnt_real > = 0 ) return `that tx was invalid because input ${input_that_isnt_real} tries to spend a utxo that does not exist or is already spent by another transaction in the mempool`;
//note: locktime is encoded as little endian
var locktime = tx.locktime;
var blockheight = Object.keys( testnet_generator.blocks ).length - 1;
if ( locktime < 500000000 && locktime > blockheight ) return `that tx was invalid because its locktime is set to a value greater than the current blockheight -- wait longer! The current blockheight is ${blockheight} and your locktime is set to ${locktime} so you need to wait til then`;
var current_block = Object.keys( testnet_generator.blocks ).sort()[ Object.keys( testnet_generator.blocks ).sort().length - 1 ];
var current_blocktime = testnet_generator.blocks[ current_block ].timestamp;
if ( locktime >= 500000000 && locktime > current_blocktime ) return `that tx was invalid because its locktime is set to a value greater than the median timestamp of the last 11 blocks (actually in this implementation I don't do the median stuff, I just check the latest block's timestamp) -- wait longer! The current timestamp is ${current_blocktime} (which is ${String( new Date( current_blocktime * 1000 ) )}) and your locktime is set to ${locktime} (which is ${String( new Date( locktime * 1000 ) )}) so you need to wait til then`;
var i; for ( i=0; i< tx.vin.length ; i ++ ) {
var input = tx.vin[ i ];
var index = i;
var input_id = `${input[ " txid " ]} _ ${ input [ " vout " ]}`;
// var input_to_work_with_here = input_to_work_with ? input_to_work_with : item ;
// var addy = input_to_work_with_here.addy;
var addy = input.prevout.scriptPubKey;
var type = "p2tr" ;
if ( addy !== " 51024e73 " ) {
addy = testnet_generator.isValidAddress( addy ) ? addy : tapscript . Address . fromScriptPubKey ( addy , testnet_generator . addykind );
var type = tapscript.Address.decode( addy ). type ;
}
var sequence_binary = testnet_generator.hexToBinary( input . sequence );
var first_bit = Number( sequence_binary [ 0 ] );
var tenth_bit = Number( sequence_binary [ 9 ] );
var sequence = testnet_generator.littleEndianSignedHexToDecimal( input . sequence );
var sequence_value = null;
if ( ! first_bit ) {
// the " disable flag " is not set so we are about to enforce the sequence number as a relative locktime
var mined_in_block = input_to_work_with.block_created;
var current_blockheight = Object.keys( testnet_generator . blocks ). length - 1 ;
sequence_value = input.sequence.substring( 4 );
if ( sequence_value . substring ( 0 , 2 ) != " 00 " ) {
sequence_value = parseInt( sequence_value , 16 );
} else {
sequence_value = parseInt( sequence_value . substring ( 2 ), 16 );
}
if ( ! tenth_bit ) {
// the " type " bit is not set so this is a block-based relative timelock rather
// than a timestamp-based relative timelock
if ( ! mined_in_block && parseInt ( sequence_binary , 2 ) > 0 ) {
input_whose_script_failed_relative_timelock = index;
break;
} else if ( mined_in_block ) {
var num_of_blocks_since_parent = current_blockheight - mined_in_block;
if ( num_of_blocks_since_parent > 0 ) num_of_blocks_since_parent = num_of_blocks_since_parent + 1;
if ( num_of_blocks_since_parent < sequence_value ) {
input_whose_script_failed_relative_timelock = index;
break ;
}
}
} else {
// the " type " bit is set so this is a timestmap-based relative timelock rather
// than a block-based relative timelock
if ( ! mined_in_block && parseInt ( sequence_binary , 2 ) > 0 ) {
input_whose_script_failed_relative_timelock = index;
break;
} else if ( mined_in_block ) {
var id_of_parent_block = testnet_generator.intToHex( mined_in_block ).padStart( 64, "0" );
var mined_at_timestamp = testnet_generator.blocks[ id_of_parent_block ].timestamp;
var time_to_wait = sequence_value * 512;
var now = Math.floor( Date.now() / 1000 );
var time_since_parent = now - mined_at_timestamp;
if ( time_since_parent < time_to_wait ) {
input_whose_script_failed_relative_timelock = index;
break ;
}
}
}
}
if ( type = == " p2tr " ) {
if ( addy = == " 51024e73 " ) {
// p2a addresses can ' t have witness items
if ( input . witness . length ) {
input_with_too_many_witness_items = index;
break ;
}
continue ;
}
// ensure at least one element exists on the witness stack
if ( ! input . witness . length ) {
console . log ( input , addy );
input_with_insufficient_witness_length = index;
break ;
}
// if only one element exists on the witness stack , treat it as a sig and check that it is valid for the taproot address when treated as a pubkey
if ( input . witness . length = == 1 ) {
var sig = input.witness[ 0 ];
var sighash = tapscript.Signer.taproot.hash( tx , index );
var pubkey = tapscript.Address.decode( addy ). script [ 1 ];
var sig_is_valid = await nobleSecp256k1 . schnorr . verify ( sig , sighash , pubkey );
if ( ! sig_is_valid ) input_with_invalid_sig = index;
} else {
// if there are multiple elements on the witness stack , treat the second-to-last element as the script and execute it with the elements prior to that as params
var script = input.witness[ input . witness . length - 2 ];
var params = [];
input . witness . forEach ( ( element , element_index ) = > {
if ( element === "OP_0" || element === "OP_FALSE" || element === "" ) element = 0;
if ( element === "OP_1" || element === "OP_TRUE" ) element = 1;
if ( element_index < input.witness.length - 2 ) params . push ( element );
});
var script_as_asm = tapscript.Script.fmt.toAsm( script );
var tapleaf = tapscript.Tap.encodeScript( script_as_asm );
var ifs_stack = [true];
var alt_stack = [];
console . log ( " params: " );
console . log ( params );
console . log ( " script: " );
console . log ( script_as_asm );
var exeloop = async () = > {
var element = script_as_asm[ 0 ];
console.log( element );
if ( !ifs_stack.includes( false ) && !element.startsWith( "OP_" ) ) {
if ( !testnet_generator.isValidHex( element ) && element !== "" ) {
input_with_improperly_formatted_element = index;
return;
}
//if the element is 0, push 0
if ( element === "" ) element = 0;
//convert hex values less than 4 bytes to the corresponding number
if ( element !== 0 && element.length / 2 < 5 ) element = testnet_generator.littleEndianSignedHexToDecimal( element );
params . push ( element );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_CHECKSIG " ) {
// console . log ( " params: " );
// console . log ( JSON . parse ( JSON . stringify ( params ) ) );
// console . log ( " script: " );
// console . log ( JSON . parse ( JSON . stringify ( script_as_asm ) ) );
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var pubkey = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var sig = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
// console . log ( " pubkey: " );
// console . log ( pubkey );
// console . log ( " sig: " );
// console . log ( sig );
var sig_length_is_good = false;
if ( sig . length = == 128 || sig . length = == 130 || ! sig . length ) sig_length_is_good = true;
if ( sig . length = == 130 && sig . substring ( 128 ) != " 01 " && sig . substring ( 128 ) != " 02 " && sig . substring ( 128 ) != " 03 " && sig . substring ( 128 ) != " 81 " && sig . substring ( 128 ) != " 82 " && sig . substring ( 128 ) != " 83 " ) sig_length_is_good = false;
if ( ! sig_length_is_good || ( sig . length && ! testnet_generator . isValidHex ( sig ) ) ) input_with_improperly_formatted_sig = index;
if ( ! sig . length ) {
params . push ( 0 );
} else {
var sigflag = 1; // sighash_all
2025-04-13 13:35:51 -06:00
if ( sig . length = == 130 && sig . substring ( 128 ) === " 02 " ) sigflag = 2; // sighash_none
if ( sig . length = == 130 && sig . substring ( 128 ) === " 03 " ) sigflag = 3; // sighash_single
2025-04-13 03:57:57 -06:00
if ( sig . length = == 130 && sig . substring ( 128 ) === " 81 " ) sigflag = 128 | 1 ; // sighash_all | anyone_can_pay
2025-04-13 13:35:51 -06:00
if ( sig . length = == 130 && sig . substring ( 128 ) === " 82 " ) sigflag = 128 | 2 ; // sighash_none | anyone_can_pay
if ( sig . length = == 130 && sig . substring ( 128 ) === " 83 " ) sigflag = 128 | 3 ; // sighash_single | anyone_can_pay
2025-03-17 20:48:11 -04:00
if ( sig . length = == 130 ) sig = sig.substring( 0 , 128 );
if ( sigflag != 1 ) var sighash = tapscript.Signer.taproot.hash( tx , index , { extension: tapleaf , sigflag }). hex ;
else var sighash = tapscript.Signer.taproot.hash( tx , index , { extension: tapleaf } ). hex ;
var sig_is_valid = await nobleSecp256k1 . schnorr . verify ( sig , sighash , pubkey );
if ( sig_is_valid ) {
params . push ( 1 );
}
else {
input_with_invalid_sig = index;
return ;
}
}
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_CHECKSIGVERIFY " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var pubkey = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var sig = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
var sig_length_is_good = false;
if ( sig . length = == 128 || sig . length = == 130 || ! sig . length ) sig_length_is_good = true;
if ( sig . length = == 130 && sig . substring ( 128 ) != " 01 " && sig . substring ( 128 ) != " 02 " && sig . substring ( 128 ) != " 03 " && sig . substring ( 128 ) != " 81 " && sig . substring ( 128 ) != " 82 " && sig . substring ( 128 ) != " 83 " ) sig_length_is_good = false;
if ( ! sig_length_is_good || ( sig . length && ! testnet_generator . isValidHex ( sig ) ) ) input_with_improperly_formatted_sig = index;
if ( ! sig . length ) {
input_whose_script_failed_a_verify_opcode = index;
return ;
} else {
var sigflag = 1; // sighash_all
2025-04-13 13:35:51 -06:00
if ( sig . length = == 130 && sig . substring ( 128 ) === " 02 " ) sigflag = 2; // sighash_none
if ( sig . length = == 130 && sig . substring ( 128 ) === " 03 " ) sigflag = 3; // sighash_single
2025-04-13 03:57:57 -06:00
if ( sig . length = == 130 && sig . substring ( 128 ) === " 81 " ) sigflag = 128 | 1 ; // sighash_all | anyone_can_pay
2025-04-13 13:35:51 -06:00
if ( sig . length = == 130 && sig . substring ( 128 ) === " 82 " ) sigflag = 128 | 2 ; // sighash_none | anyone_can_pay
if ( sig . length = == 130 && sig . substring ( 128 ) === " 83 " ) sigflag = 128 | 3 ; // sighash_single | anyone_can_pay
2025-03-17 20:48:11 -04:00
if ( sig . length = == 130 ) sig = sig.substring( 0 , 128 );
if ( sigflag != 1 ) var sighash = tapscript.Signer.taproot.hash( tx , index , { extension: tapleaf , sigflag }). hex ;
else var sighash = tapscript.Signer.taproot.hash( tx , index , { extension: tapleaf } ). hex ;
var sig_is_valid = await nobleSecp256k1 . schnorr . verify ( sig , sighash , pubkey );
if ( ! sig_is_valid ) {
input_whose_script_failed_a_verify_opcode = index;
return ;
}
}
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_CHECKSIGADD " ) {
// console . log ( " params: " );
// console . log ( params );
// console . log ( " script: " );
// console . log ( script_as_asm );
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var pubkey = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var num_to_add_to = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var sig = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
var sig_length_is_good = false;
if ( sig . length = == 128 || sig . length = == 130 || ! sig . length ) sig_length_is_good = true;
if ( sig . length = == 130 && sig . substring ( 128 ) != " 01 " && sig . substring ( 128 ) != " 02 " && sig . substring ( 128 ) != " 03 " && sig . substring ( 128 ) != " 81 " && sig . substring ( 128 ) != " 82 " && sig . substring ( 128 ) != " 83 " ) sig_length_is_good = false;
if ( ! sig_length_is_good || ( sig . length && ! testnet_generator . isValidHex ( sig ) ) ) input_with_improperly_formatted_sig = index;
// console . log ( " num_to_add_to: ", num_to_add_to , " pubkey: ", pubkey , " sig: ", sig );
if ( ! sig . length ) {
params . push ( num_to_add_to );
} else {
var sigflag = 1; // sighash_all
2025-04-13 13:35:51 -06:00
if ( sig . length = == 130 && sig . substring ( 128 ) === " 02 " ) sigflag = 2; // sighash_none
if ( sig . length = == 130 && sig . substring ( 128 ) === " 03 " ) sigflag = 3; // sighash_single
2025-04-13 03:57:57 -06:00
if ( sig . length = == 130 && sig . substring ( 128 ) === " 81 " ) sigflag = 128 | 1 ; // sighash_all | anyone_can_pay
2025-04-13 13:35:51 -06:00
if ( sig . length = == 130 && sig . substring ( 128 ) === " 82 " ) sigflag = 128 | 2 ; // sighash_none | anyone_can_pay
if ( sig . length = == 130 && sig . substring ( 128 ) === " 83 " ) sigflag = 128 | 3 ; // sighash_single | anyone_can_pay
2025-03-17 20:48:11 -04:00
if ( sig . length = == 130 ) sig = sig.substring( 0 , 128 );
if ( sigflag != 1 ) var sighash = tapscript.Signer.taproot.hash( tx , index , { extension: tapleaf , sigflag }). hex ;
else var sighash = tapscript.Signer.taproot.hash( tx , index , { extension: tapleaf } ). hex ;
var sig_is_valid = await nobleSecp256k1 . schnorr . verify ( sig , sighash , pubkey );
// console . log ( " sig is valid , right ?", sig_is_valid , " pubkey: ", pubkey , " sighash: ", sighash , " sig: ", sig );
if ( sig_is_valid ) {
// console . log ( ' I will increment this: ', num_to_add_to );
num_to_add_to = num_to_add_to + 1 ;
params . push ( num_to_add_to );
// console . log ( ' done , now it is this: ', num_to_add_to );
} else {
input_with_invalid_sig = index;
return ;
}
}
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_0 " || element = == " OP_FALSE " || element = == "" ) {
params . push ( 0 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_1 " || element = == " OP_TRUE " ) {
params . push ( 1 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_2 " ) {
params . push ( 2 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_3 " ) {
params . push ( 3 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_4 " ) {
params . push ( 4 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_5 " ) {
params . push ( 5 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_6 " ) {
params . push ( 6 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_7 " ) {
params . push ( 7 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_8 " ) {
params . push ( 8 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_9 " ) {
params . push ( 9 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_10 " ) {
params . push ( 10 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_11 " ) {
params . push ( 11 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_12 " ) {
params . push ( 12 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_13 " ) {
params . push ( 13 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_14 " ) {
params . push ( 14 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_15 " ) {
params . push ( 15 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_16 " ) {
params . push ( 16 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_EQUAL " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( item_2 = == item_1 ) params . push ( 1 );
else params . push ( 0 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_EQUALVERIFY " ) {
// console . log ( " params: " );
// console . log ( params );
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( item_2 !== item_1 ) {
input_whose_script_failed_a_verify_opcode = index;
return ;
}
// console . log ( " params: " );
// console . log ( params );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_GREATERTHAN " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var num_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var num_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( num_2 > num_1 ) params.push( 1 );
else params.push( 0 );
}
else if ( !ifs_stack.includes( false ) && element === "OP_LESSTHAN" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var num_1 = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var num_2 = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( num_2 < num_1 ) params . push ( 1 );
else params . push ( 0 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_GREATERTHANOREQUAL " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var num_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var num_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
// console . log ( num_2 , num_1 , " the first number should be greater than or equal to the second number " );
if ( num_2 > = num_1 ) params.push( 1 );
else params.push( 0 );
}
else if ( !ifs_stack.includes( false ) && element === "OP_LESSTHANOREQUAL" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var num_1 = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var num_2 = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( num_2 < = num_1 ) params.push( 1 );
else params.push( 0 );
}
else if ( !ifs_stack.includes( false ) && element === "OP_SHA1" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_to_hash = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( typeof item_to_hash === "string" ) var as_bytes = testnet_generator.hexToBytes( item_to_hash );
else {
item_to_hash = item_to_hash.toString( 16 );
if ( item_to_hash % 2 ) item_to_hash = "0" + item_to_hash;
var as_bytes = testnet_generator.hexToBytes( item_to_hash );
}
var hashed = await testnet_generator.sha1( as_bytes );
params.push( hashed );
}
else if ( !ifs_stack.includes( false ) && element === "OP_SHA256" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_to_hash = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( typeof item_to_hash === "string" ) var as_bytes = testnet_generator.hexToBytes( item_to_hash );
else {
item_to_hash = item_to_hash.toString( 16 );
if ( item_to_hash % 2 ) item_to_hash = "0" + item_to_hash;
var as_bytes = testnet_generator.hexToBytes( item_to_hash );
}
var hashed = await testnet_generator.sha256( as_bytes );
params.push( hashed );
}
else if ( !ifs_stack.includes( false ) && element === "OP_HASH256" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_to_hash = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( typeof item_to_hash === "string" ) var as_bytes = testnet_generator.hexToBytes( item_to_hash );
else {
item_to_hash = item_to_hash.toString( 16 );
if ( item_to_hash % 2 ) item_to_hash = "0" + item_to_hash;
var as_bytes = testnet_generator.hexToBytes( item_to_hash );
}
var hashed = await testnet_generator.sha256( as_bytes );
var as_bytes = testnet_generator.hexToBytes( hashed );
hashed = await testnet_generator.sha256( as_bytes );
params.push( hashed );
}
else if ( !ifs_stack.includes( false ) && element === "OP_RIPEMD160" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_to_hash = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( typeof item_to_hash === "string" ) var as_bytes = testnet_generator.hexToBytes( item_to_hash );
else {
item_to_hash = item_to_hash.toString( 16 );
if ( item_to_hash % 2 ) item_to_hash = "0" + item_to_hash;
var as_bytes = testnet_generator.hexToBytes( item_to_hash );
}
var hashed = testnet_generator.rmd160( as_bytes );
params.push( hashed );
}
else if ( !ifs_stack.includes( false ) && element === "OP_HASH160" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_to_hash = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( typeof item_to_hash === "string" ) var as_bytes = testnet_generator.hexToBytes( item_to_hash );
else {
item_to_hash = item_to_hash.toString( 16 );
if ( item_to_hash % 2 ) item_to_hash = "0" + item_to_hash;
var as_bytes = testnet_generator.hexToBytes( item_to_hash );
}
var hashed = await testnet_generator.sha256( as_bytes );
var as_bytes = testnet_generator.hexToBytes( hashed );
hashed = testnet_generator.rmd160( as_bytes );
params.push( hashed );
}
else if ( !ifs_stack.includes( false ) && element === "OP_SWAP" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_1 = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_2 = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
params.push( item_1 );
params.push( item_2 );
}
else if ( !ifs_stack.includes( false ) && element === "OP_DROP" ) {
// console.log( "before acting:" );
// console.log( params );
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
params.reverse();
params.splice( 0, 1 );
params.reverse();
// console.log( "results:" );
// console.log( params );
}
else if ( !ifs_stack.includes( false ) && element === "OP_2DROP" ) {
// console.log( "before acting:" );
// console.log( params );
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
params.reverse();
params.splice( 0, 1 );
params.reverse();
// console.log( "results:" );
// console.log( params );
}
else if ( !ifs_stack.includes( false ) && element === "OP_1NEGATE" ) {
params.push( -1 );
}
else if ( !ifs_stack.includes( false ) && element === "OP_NOP" ) {
input_with_op_nop = index;
return;
}
else if ( element === "OP_IF" ) {
// console.log( "params:" );
// console.log( params );
//add a boolean to the ifs_stack that is false if the previous element
//was something other than OP_1 or OP_TRUE, or if any element in
//the ifs_stack is false (because that means we are in an if_branch
//that should not be executed) -- otherwise true
if ( ifs_stack.includes( false ) ) {
ifs_stack.push( false );
} else {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_to_check = params[ params.length - 1 ];
// console.log( "item_to_check:", item_to_check );
if ( item_to_check !== 1 && item_to_check !== 0 ) {
input_with_if_branch_preceded_by_something_other_than_1_or_0 = index;
return;
}
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( item_to_check !== 1 ) ifs_stack.push( false );
else ifs_stack.push( true );
}
// console.log( "ifs_stack:" );
// console.log( ifs_stack );
}
else if ( element === "OP_NOTIF" ) {
// console.log( "params:" );
// console.log( params );
if ( ifs_stack.includes( false ) ) {
ifs_stack.push( false );
} else {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_to_check = params[ params.length - 1 ];
// console.log( "item_to_check:", item_to_check );
if ( item_to_check !== 1 && item_to_check !== 0 ) {
input_with_if_branch_preceded_by_something_other_than_1_or_0 = index;
return;
}
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( item_to_check !== 0 ) ifs_stack.push( false );
else ifs_stack.push( true );
}
// console.log( "ifs_stack:" );
// console.log( ifs_stack );
}
else if ( element === "OP_ELSE" ) {
//flip the last element in the ifs_stack
ifs_stack[ ifs_stack.length - 1 ] = !ifs_stack[ ifs_stack.length - 1 ];
// console.log( "ifs_stack:" );
// console.log( ifs_stack );
}
else if ( element === "OP_ENDIF" ) {
// console.log( "params:" );
// console.log( params );
//drop the last element in the ifs_stack
ifs_stack.pop();
// console.log( "ifs_stack:" );
// console.log( ifs_stack );
}
else if ( !ifs_stack.includes( false ) && element === "OP_VERIFY" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_to_check = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( item_to_check !== 1 ) {
input_whose_script_failed_a_verify_opcode = index;
return;
}
}
else if ( !ifs_stack.includes( false ) && element === "OP_RETURN" ) {
input_with_script_containing_op_return = index;
return;
}
else if ( !ifs_stack.includes( false ) && element === "OP_0NOTEQUAL" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_to_check = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( typeof item_to_check !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index;
return;
}
if ( item_to_check === 0 ) params.push( 0 );
else params.push( 1 );
}
else if ( !ifs_stack.includes( false ) && element === "OP_TOALTSTACK" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_to_push = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
alt_stack.unshift( item_to_push );
}
else if ( !ifs_stack.includes( false ) && element === "OP_FROMALTSTACK" ) {
if ( !alt_stack.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_to_pull = alt_stack[ 0 ];
params.push( item_to_pull );
}
else if ( !ifs_stack.includes( false ) && element === "OP_IFDUP" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_to_dup = params[ params.length - 1 ];
var copy = JSON.parse( JSON.stringify( item_to_dup ) );
if ( item_to_dup !== 0 ) params.push( copy );
}
else if ( !ifs_stack.includes( false ) && element === "OP_DEPTH" ) {
params.push( params.length );
}
else if ( !ifs_stack.includes( false ) && element === "OP_NIP" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_1 = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_2 = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
params.push( item_2 );
}
else if ( !ifs_stack.includes( false ) && element === "OP_OVER" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_1 = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var item_2 = params[ params.length - 1 ];
var copy = JSON.parse( JSON.stringify( item_to_dup ) );
params.reverse();
params.splice( 0, 1 );
params.reverse();
params.push( item_2 );
params.push( item_1 );
params.push( copy );
}
else if ( !ifs_stack.includes( false ) && element === "OP_PICK" ) {
if ( !params.length ) {
input_whose_stack_is_too_small = index;
return;
}
var num_to_pick = params[ params.length - 1 ];
params.reverse();
params.splice( 0, 1 );
params.reverse();
if ( typeof num_to_pick !== "number" ) {
input_whose_script_treats_a_string_as_an_integer = index;
return;
}
if ( num_to_pick > 2147483647 ) {
input_whose_script_treats_a_string_as_an_integer = index;
return;
}
if ( num_to_pick < -2147483647 ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( params . length < num_to_pick ) {
input_whose_script_tried_to_get_a_nonexistent_element_from_the_stack = index;
return ;
}
var item_to_dup = params[ params . length - ( num_to_pick + 1 ) ];
var copy = JSON.parse( JSON . stringify ( item_to_dup ) );
params . push ( copy );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_ROLL " ) {
// console . log ( " before acting: " );
// console . log ( params );
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var num_to_pick = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof num_to_pick !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( num_to_pick > 2147483647 ) {
input_whose_script_treats_a_string_as_an_integer = index;
return;
}
if ( num_to_pick < -2147483647 ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( params . length < num_to_pick ) {
input_whose_script_tried_to_get_a_nonexistent_element_from_the_stack = index;
return ;
}
var item_to_move = params[ params . length - ( num_to_pick + 1 ) ];
var copy = JSON.parse( JSON . stringify ( item_to_move ) );
// console . log ( " results: " );
// console . log ( params );
params . splice ( params . length - ( num_to_pick + 1 ), 1 );
params . push ( copy );
// console . log ( params );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_ROT " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_3 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( item_2 );
params . push ( item_3 );
params . push ( item_1 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_TUCK " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_2 = params[ params . length - 1 ];
var copy = JSON.parse( JSON . stringify ( item_2 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( copy );
params . push ( item_1 );
params . push ( item_2 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_DUP " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_to_dup = params[ params . length - 1 ];
var copy = JSON.parse( JSON . stringify ( item_to_dup ) );
params . push ( copy );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_2DUP " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_to_dup_1 = params[ params . length - 1 ];
var copy_1 = JSON.parse( JSON . stringify ( item_to_dup_1 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_to_dup_2 = params[ params . length - 1 ];
var copy_2 = JSON.parse( JSON . stringify ( item_to_dup_2 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( copy_2 );
params . push ( copy_1 );
params . push ( copy_2 );
params . push ( copy_1 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_3DUP " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_to_dup_1 = params[ params . length - 1 ];
var copy_1 = JSON.parse( JSON . stringify ( item_to_dup_1 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_to_dup_2 = params[ params . length - 1 ];
var copy_2 = JSON.parse( JSON . stringify ( item_to_dup_2 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_to_dup_3 = params[ params . length - 1 ];
var copy_3 = JSON.parse( JSON . stringify ( item_to_dup_3 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( copy_3 );
params . push ( copy_2 );
params . push ( copy_1 );
params . push ( copy_3 );
params . push ( copy_2 );
params . push ( copy_1 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_2OVER " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_4 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_3 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_2 = params[ params . length - 1 ];
var copy_2 = JSON.parse( JSON . stringify ( item_2 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_1 = params[ params . length - 1 ];
var copy_1 = JSON.parse( JSON . stringify ( item_1 ) );
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( item_1 );
params . push ( item_2 );
params . push ( item_3 );
params . push ( item_4 );
params . push ( copy_1 );
params . push ( copy_2 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_2ROT " ) {
// console . log ( " before acting: " );
// console . log ( params );
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_6 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_5 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_4 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_3 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( item_3 );
params . push ( item_4 );
params . push ( item_5 );
params . push ( item_6 );
params . push ( item_1 );
params . push ( item_2 );
// console . log ( " results: " );
// console . log ( params );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_2SWAP " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_4 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_3 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
params . push ( item_3 );
params . push ( item_4 );
params . push ( item_1 );
params . push ( item_2 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_SIZE " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var item = params[ params . length - 1 ];
if ( typeof item = == " number " ) {
if ( item > = -2147483647 && item < 2147483648 ) var size = 4;
if ( item > = -8388607 && item < 8388608 ) var size = 3;
if ( item > = -32767 && item < 32768 ) var size = 2;
if ( item > = -127 && item < 128 ) var size = 1;
if ( item = == 0 ) var size = 0;
} else {
var size = item.length / 2 ;
}
params . push ( size );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_1ADD " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input = params[ params . length - 1 ];
if ( typeof input !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
params . push ( input + 1 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_1SUB " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input = params[ params . length - 1 ];
if ( typeof input !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
params . push ( input - 1 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_NEGATE " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input = params[ params . length - 1 ];
if ( typeof input !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
params . push ( -input );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_ABS " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input = params[ params . length - 1 ];
if ( typeof input !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( input < 0 || ( input = == 0 && testnet_generator . isZeroNegative ( input ) ) ) params . push ( -input );
else params . push ( input );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_NOT " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input = params[ params . length - 1 ];
if ( typeof input !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( input = == 0 ) var output = 1;
else var output = 0;
params . push ( output );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_ADD " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
params . push ( input_1 + input_2 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_SUB " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
params . push ( input_1 - input_2 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_BOOLAND " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( input_1 !== 1 ) input_1 = 0;
if ( input_2 !== 1 ) input_2 = 0;
params . push ( input_1 && input_2 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_BOOLOR " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( input_1 !== 1 ) input_1 = 0;
if ( input_2 !== 1 ) input_2 = 0;
params . push ( input_1 | input_2 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_NUMEQUAL " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
params . push ( input_1 = == input_2 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_NUMEQUALVERIFY " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( input_1 !== input_2 ) {
input_whose_script_failed_a_verify_opcode = index;
return ;
}
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_NUMNOTEQUAL " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
params . push ( input_1 !== input_2 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_MIN " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
params . push ( Math . min ( input_1 , input_2 ) );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_MAX " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_1 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_1 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input_2 = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input_2 !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
params . push ( Math . max ( input_1 , input_2 ) );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_WITHIN " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof input !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var min = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof min !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var max = params[ params . length - 1 ];
params . reverse ();
params . splice ( 0 , 1 );
params . reverse ();
if ( typeof max !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( input > = min && input < max ) params . push ( 1 );
else params . push ( 0 );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_CHECKLOCKTIMEVERIFY " ) {
// console . log ( " before acting: " );
// console . log ( params );
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input = params[ params . length - 1 ];
if ( typeof input !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( input > locktime ) {
input_whose_script_failed_CLTV_due_to_not_enough_time_passed = index;
return;
}
if ( input < 0 || ( input = == 0 && testnet_generator . isZeroNegative ( input ) ) ) {
input_whose_script_uses_a_negative_number_for_CLTV_or_CSV = index;
return ;
}
if ( input > = 500000000 && locktime < 500000000 || locktime > = 500000000 && input < 500000000 ) {
input_whose_locktime_value_does_not_match_the_CLTV_value = index;
return ;
}
if ( sequence = == -2147483647 ) {
input_whose_script_uses_CLTV_but_the_sequence_number_is_maxxed_out = index;
return ;
}
// console . log ( " results: " );
// console . log ( params );
}
else if ( ! ifs_stack . includes ( false ) && element = == " OP_CHECKSEQUENCEVERIFY " ) {
if ( ! params . length ) {
input_whose_stack_is_too_small = index;
return ;
}
var input = params[ params . length - 1 ];
if ( typeof input !== " number " ) {
input_whose_script_treats_a_string_as_an_integer = index;
return ;
}
if ( first_bit ) {
input_whose_script_failed_CSV = index;
return ;
}
if ( sequence_value < input ) {
input_whose_script_failed_CSV = index;
return ;
}
if ( input < 0 || ( input = == 0 && testnet_generator . isZeroNegative ( input ) ) ) {
input_whose_script_uses_a_negative_number_for_CLTV_or_CSV = index;
return ;
}
if ( input > = 500000000 && locktime < 500000000 || locktime > = 500000000 && input < 500000000 ) {
input_whose_locktime_value_does_not_match_the_CLTV_value = index;
return ;
}
if ( sequence = == " ffffffff " ) {
input_whose_script_uses_CLTV_but_the_sequence_number_is_maxxed_out = index;
return ;
}
}
else console . log ( " the previous opcode or element was unsupported or we are in an if_branch that isn ' t supposed to execute " );
script_as_asm . splice ( 0 , 1 );
if ( script_as_asm . length ) await exeloop ();
}
await exeloop ();
console . log ( ' done ' );
if ( params . length != 1 ) {
input_whose_script_left_multiple_elements_on_the_stack = index;
console . log ( params );
break ;
}
if ( params [ 0 ] !== 1 ) {
input_whose_script_ended_on_false = index;
console . log ( params );
break ;
}
}
}
}
// TODO: ensure there are never more than 1000 stack elements
// including the elements on the alt_stack
// TODO: ensure stack elements are 520 bytes or fewer
// TODO: ensure outputs are valid
// TODO: ensure outputs sum to less than or equal to inputs
// TODO: if the tx is non-standard , warn the user but give an option to anull standardness rules
if ( input_whose_script_failed_CLTV_due_to_not_enough_time_passed > = 0 ) return `that tx was invalid because input ${input_whose_script_failed_CLTV_due_to_not_enough_time_passed} has a script containing OP_CHECKLOCKTIMEVERIFY and not enough time passed or the locktime field was set lower than is required`;
if ( input_whose_script_uses_a_negative_number_for_CLTV_or_CSV >= 0 ) return `that tx was invalid because input ${input_whose_script_uses_a_negative_number_for_CLTV_or_CSV} has a script containing OP_CHECKLOCKTIMEVERIFY and tried to pass a negative number to it`;
if ( input_whose_locktime_value_does_not_match_the_CLTV_value >= 0 ) return `that tx was invalid because input ${input_whose_locktime_value_does_not_match_the_CLTV_value} has a locktime value does not match the value passed to OP_CHECKLOCKTIMEVERIFY in the script -- i.e. one of them a blockheight and the other is a unix timestamp`;
if ( input_whose_script_uses_CLTV_but_the_sequence_number_is_maxxed_out >= 0 ) return `that tx was invalid because input ${input_whose_script_uses_CLTV_but_the_sequence_number_is_maxxed_out} uses OP_CHECKLOCKTIMEVERIFY in the script but its sequence number is set to 0xffffffff -- this is not allowed, try setting the sequence number to 0xfffffffd or less`;
if ( input_whose_script_failed_CSV >= 0 ) return `that tx was invalid because input ${input_whose_script_failed_CSV} uses OP_CHECKSEQUENCEVERIFY in the script but its value is lower than the sequence number of that input`;
if ( input_with_if_branch_preceded_by_something_other_than_1_or_0 >= 0 ) return `that tx was invalid because input ${input_with_if_branch_preceded_by_something_other_than_1_or_0} had an if_branch that was preceded by something other than a 1 or a 0`;
if ( input_with_insufficient_witness_length >= 0 ) return `that tx was invalid because input ${input_with_insufficient_witness_length} needed at least one element on the witness stack and it wasn't there`;
if ( input_with_invalid_sig >= 0 ) return `that tx was invalid because input ${input_with_invalid_sig} had an invalid sig`;
if ( input_whose_stack_is_too_small >= 0 ) return `that tx was invalid because input ${input_whose_stack_is_too_small} ran into an opcode that required more elements on the stack than were available there`;
if ( input_with_improperly_formatted_sig >= 0 ) return `that tx was invalid because input ${input_with_improperly_formatted_sig} ran into an opcode that required a signature, and the signature passed to it was improperly formatted`;
if ( input_with_improperly_formatted_element >= 0 ) return `that tx was invalid because input ${input_with_improperly_formatted_element} had a non-opcode element on the witness stack that was formatted as something other than a hex string`;
if ( input_whose_script_failed_a_verify_opcode >= 0 ) return `that tx was invalid because the script for input ${input_whose_script_failed_a_verify_opcode} had an opcode with "verify" in it (one of these: OP_CHECKSIGVERIFY, OP_CHECKMULTISIGVERIFY, OP_EQUALVERIFY, OP_NUMEQUALVERIFY, or OP_VERIFY) and "verify" failed`;
if ( input_with_op_nop >= 0 ) return `that tx was invalid because the script for input ${input_with_op_nop} had an op_nop opcode, which is non-standard`;
if ( input_whose_script_left_multiple_elements_on_the_stack >= 0 ) return `that tx was invalid because input ${input_whose_script_left_multiple_elements_on_the_stack} left multiple elements on the stack, and at the end of script execution there is only supposed to be 1 element left on the stack`;
if ( input_whose_script_ended_on_false >= 0 ) return `that tx was invalid because input ${input_whose_script_ended_on_false} ended with a value other than 1 on the stack`;
if ( input_with_script_containing_op_return >= 0 ) return `that tx was invalid because input ${input_with_script_containing_op_return} ran into an op_return while executing the script`;
if ( input_whose_script_treats_a_string_as_an_integer >= 0 ) return `that tx was invalid because input ${input_whose_script_treats_a_string_as_an_integer} has a script that tried to treat a string as an integer`;
if ( input_whose_script_tried_to_get_a_nonexistent_element_from_the_stack >= 0 ) return `that tx was invalid because input ${input_whose_script_tried_to_get_a_nonexistent_element_from_the_stack} has a script that tried to get a nonexistent element from the stack`;
if ( input_whose_script_failed_relative_timelock >= 0 ) return `that tx was invalid because input ${input_whose_script_failed_relative_timelock} has a non-zero relative timelock in its sequence number, which means the tx must wait a certain amount of time after that input utxo gets mined, and not enough time has passed`;
},
broadcast: async ( txhex, skip_validation ) => {
if ( !skip_validation ) {
if ( !txhex || typeof txhex === "object" ) txhex = $( '.tx_broadcaster' ).value;
var tx_array = txhex.split( "," );
2025-11-10 01:07:41 +00:00
if ( tx_array.length > 2 ) return `too many txs in this package -- only 2 are allowed`;
2025-03-17 20:48:11 -04:00
if ( tx_array.length > 1 ) {
var allow_zero_fee = true;
var first_tx_is_invalid = await testnet_generator.validateTx( tx_array[ 0 ], allow_zero_fee );
2025-11-10 01:07:41 +00:00
if ( first_tx_is_invalid ) return first_tx_is_invalid;
2025-03-17 20:48:11 -04:00
var allow_zero_fee = false;
var second_tx_is_invalid = await testnet_generator.validateTx( tx_array[ 1 ], allow_zero_fee, tx_array[ 0 ] );
2025-11-10 01:07:41 +00:00
if ( second_tx_is_invalid ) return second_tx_is_invalid;
2025-03-17 20:48:11 -04:00
var parent_tx = tapscript.Tx.decode( tx_array[ 0 ] );
var sum_of_parent_inputs = 0;
var all_parents_confirmed = true;
parent_tx.vin.forEach( input => {
var input_id = `${input[ "txid" ]}_${input[ "vout" ]}`;
var input_to_work_with = testnet_generator.utxo_set[ input_id ];
if ( input_to_work_with.block_created === undefined ) all_parents_confirmed = false;
var amnt = input_to_work_with.amnt;
sum_of_parent_inputs = sum_of_parent_inputs + amnt;
});
2025-11-10 01:07:41 +00:00
if ( !all_parents_confirmed ) return 'you cannot broadcast this package because it tries to spend an unconfirmed output, which is not allowed';
2025-03-17 20:48:11 -04:00
var sum_of_parent_outputs = 0;
parent_tx.vout.forEach( output => sum_of_parent_outputs = sum_of_parent_outputs + Number( output[ "value" ] ) );
var parent_tx_fee_in_sats = sum_of_parent_inputs - sum_of_parent_outputs;
2025-11-11 19:21:01 +00:00
var parent_tx_size = tapscript.Tx.util.getTxSize( tapscript.Tx.create( tx_array[ 0 ] ) );
2025-03-17 20:48:11 -04:00
var child_tx = tapscript.Tx.decode( tx_array[ 1 ] );
var sum_of_child_inputs = 0;
child_tx.vin.forEach( input => {
var input_id = `${input[ "txid" ]}_${input[ "vout" ]}`;
var input_to_work_with = testnet_generator.utxo_set[ input_id ];
if ( !input_to_work_with && !!parent_tx ) {
var txid_of_parent = tapscript.Tx.util.getTxid( parent_tx );
if ( txid_of_parent === input[ "txid" ] ) {
input_to_work_with = parent_tx.vout[ input[ "vout" ] ];
}
}
var amnt = input_to_work_with.hasOwnProperty( "amnt" ) ? input_to_work_with.amnt : Number( input_to_work_with.value );
sum_of_child_inputs = sum_of_child_inputs + amnt;
});
var sum_of_child_outputs = 0;
child_tx.vout.forEach( output => sum_of_child_outputs = sum_of_child_outputs + Number( output[ "value" ] ) );
var child_tx_fee_in_sats = sum_of_child_inputs - sum_of_child_outputs;
2025-11-11 19:21:01 +00:00
var child_tx_size = tapscript.Tx.util.getTxSize( tapscript.Tx.create( tx_array[ 1 ] ) );
2025-03-17 20:48:11 -04:00
var total_fee = parent_tx_fee_in_sats + child_tx_fee_in_sats;
var total_size = parent_tx_size + child_tx_size;
var effective_fee_rate = total_fee / total_size;
2025-11-10 01:07:41 +00:00
if ( effective_fee_rate < 1 ) return ' the effective fee rate was less than 1 sat per byte , try again with a child that pays a larger fee ';
2025-03-17 20:48:11 -04:00
var skip_validation = true;
2025-11-10 01:07:41 +00:00
var txid = await testnet_generator . broadcast ( tapscript . Tx . encode ( parent_tx ). hex , skip_validation );
2025-03-17 20:48:11 -04:00
testnet_generator . broadcast ( tapscript . Tx . encode ( child_tx ). hex , skip_validation );
2025-11-10 01:07:41 +00:00
return txid ;
2025-03-17 20:48:11 -04:00
}
var reason_why_tx_is_invalid = await testnet_generator . validateTx ( txhex );
2025-11-10 01:07:41 +00:00
if ( reason_why_tx_is_invalid ) return reason_why_tx_is_invalid ;
2025-03-17 20:48:11 -04:00
}
testnet_generator . mempool . push ( txhex );
var txid = tapscript.Tx.util.getTxid( txhex );
$( '. broadcast_success_label ' ). classList . remove ( " hidden " );
$( '. broadcast_success_txid ' ). innerText = txid;
$( '. broadcast_success_txid ' ). setAttribute ( " data-txhex ", txhex );
$( '. broadcast_success_txid ' ). setAttribute ( " data-idx ", 1 );
$( '. broadcast_success_txid ' ). onclick = () = > {
showPage( 'tx_explorer' );
testnet_generator.showSoloTx( txid, txhex, 1 );
};
testnet_generator.processTxhex( txhex );
2025-11-10 01:07:41 +00:00
return txid;
2025-03-17 20:48:11 -04:00
},
showBlock: ( num, count ) => {
var div = document.createElement( "div" );
div.innerHTML = `
< div class = "block" >
< div class = "block_inner" >
< div class = "block_back" ></ div >
2025-11-11 17:36:40 +00:00
< div class = "block_test1" style = "height: 2rem;background-color: #3953c6;aspect-ratio: 1;rotate: 45deg;border-left: 1px solid black;position: absolute;top: .47rem;left: 0.47rem;" ></ div >
< div class = "block_test2" style = "height: 5.8rem;background-color: #3953c6;position: absolute;top: 1px;left: 1.5rem;width: 5.9rem;" ></ div >
< div class = "block_test3" style = "height: 2.4rem;width:1px;background-color: black;rotate: 45deg;position: absolute;top: -.3rem;right: .85rem;" ></ div >
< div class = "block_test4" style = "height: 2rem;background-color: #3953c6;aspect-ratio: 1;rotate: 45deg;border-right: 1px solid black;position: absolute;bottom: 2.50rem;right: 0.5rem;" ></ div >
2025-03-17 20:48:11 -04:00
< div class = "block_front" >< div > tx count:< br > ${count}</ div ></ div >
< div class = "block_num" > ${num}</ div >
</ div >
</ div >
`;
var block = div.firstElementChild;
if ( $( '.block' ) ) {
var blockwidth = 229;
var desired_width = Math.max( ( blockwidth * ( $$( '.block' ).length + 1 ) ), window.innerWidth );
$( '.list_of_blocks' ).style.width = desired_width + "px";
$( '.list_container' ).scrollTo({
top: 0,
left: $( '.list_of_blocks' ).offsetWidth,
behavior: "smooth",
2025-03-15 22:25:47 -04:00
});
2025-03-17 20:48:11 -04:00
}
$( '.no_blocks_msg' ).classList.add( "hidden" );
block.onclick = () => {testnet_generator.showBlockDetails( num );}
$( '.list_of_blocks' ).append( block );
},
calcBalance: () => {
var bal = 0;
var my_utxos = {}
Object.keys( testnet_generator.utxo_set ).forEach( utxo_id => {
if ( testnet_generator.utxo_set[ utxo_id ].spent ) return;
var utxo = testnet_generator.utxo_set[ utxo_id ];
var real_spky = utxo[ "addy" ];
var addy = tapscript.Address.toScriptPubKey( $( '.your_addy' ).innerText );
var expected_spky = tapscript.Script.encode( addy ).hex.substring( 2 );
if ( real_spky === expected_spky ) {
bal = bal + utxo[ "amnt" ];
my_utxos[ utxo_id ] = {
utxo_id,
txid: utxo[ "txid" ],
vout: utxo[ "vout" ],
amnt: utxo[ "amnt" ],
addy: utxo[ "addy" ],
skey: $( '.your_addy' ).getAttribute( "data-privkey" ),
}
}
2024-11-21 01:24:10 -05:00
});
2025-03-17 20:48:11 -04:00
testnet_generator.utxos = my_utxos;
$( '.balance' ).innerText = testnet_generator.satsToBitcoin( bal );
},
calcMempool: () => $( '.txs_in_mempool' ).innerText = testnet_generator.mempool.length,
processTxhex: ( txhex, idx = undefined, blocknum = undefined ) => {
var tx = tapscript.Tx.decode( txhex );
var txid = tapscript.Tx.util.getTxid( tx );
var i; for ( i=0; i< tx.vin.length ; i ++ ) {
var input = tx.vin[ i ];
var index = i;
var input_exists = false;
var input_id = `${input[ " txid " ]} _ ${ input [ " vout " ]}`;
if ( testnet_generator . utxo_set . hasOwnProperty ( input_id ) ) {
if ( testnet_generator . utxo_set [ input_id ]. spent ) continue ;
testnet_generator . utxo_set [ input_id ]. spent = true;
var addy = testnet_generator.utxo_set[ input_id ]. addy ;
var spky = tapscript.Script.encode( addy ). hex . substring ( 2 );
testnet_generator . addresses [ spky ][ " sent " ]. push ( `${ txid } _ ${ i }` );
}
}
var outputs_not_to_add = [];
tx . vout . forEach ( ( output , vout ) = > {
if ( testnet_generator.utxo_set.hasOwnProperty( `${txid}_${vout}` ) ) {
outputs_not_to_add.push( `${txid}_${vout}` );
return;
}
testnet_generator.utxo_set[ `${txid}_${vout}` ] = {
txid,
vout,
amnt: Number( output[ "value" ] ),
addy: output.scriptPubKey,
spent: false,
txhex,
idx,
block_created: blocknum,
block_spent: undefined,
}
2025-03-15 22:25:47 -04:00
});
2025-03-17 20:48:11 -04:00
var i; for ( i=0; i< tx.vout.length ; i ++ ) {
var output = tx.vout[ i ];
var addy = output.scriptPubKey;
var output_id = `${txid}_${i}`;
if ( outputs_not_to_add . includes ( output_id ) ) continue ;
if ( !( addy in testnet_generator . addresses ) ) testnet_generator . addresses [ addy ] = {
sent: [],
received: [],
}
testnet_generator . addresses [ addy ][ " received " ]. push ( output_id );
}
testnet_generator . calcBalance ();
testnet_generator . calcMempool ();
},
makeBlock: async ( block_id , block_data ) = > {
if ( !block_id ) {
var num = 0;
if ( $( '.block_num' ) ) num = Number( $$( '.block_num' )[ $$( '.block_num' ).length - 1 ].innerText ) + 1;
var generated_txid = testnet_generator.intToHex( num ).padStart( 64, "0" );
var vout = 0;
var amnt = 50 * 100_000_000;
var fees_from_mempool = 0;
testnet_generator.mempool.forEach( ( tx, idx ) => {
var sum_of_inputs = 0;
tx = tapscript.Tx.decode( tx );
var txid = tapscript.Tx.util.getTxid( tx );
tx.vin.forEach( input => {
var input_id = `${input[ "txid" ]}_${input[ "vout" ]}`;
var amnt = testnet_generator.utxo_set[ input_id ].amnt;
sum_of_inputs = sum_of_inputs + amnt;
testnet_generator.utxo_set[ input_id ].spent = true;
testnet_generator.utxo_set[ input_id ].block_spent = num;
});
var sum_of_outputs = 0;
2025-03-30 14:11:05 -04:00
console.log( txid, tx.vout.length );
2025-03-17 20:48:11 -04:00
tx.vout.forEach( ( output, index ) => {
sum_of_outputs = sum_of_outputs + Number( output[ "value" ] );
testnet_generator.utxo_set[ `${txid}_${index}` ] = {
txid,
vout: index,
amnt: Number( output[ "value" ] ),
addy: output[ "scriptPubKey" ],
spent: false,
txhex: tapscript.Tx.encode( tx ).hex,
idx: idx + 1,
block_created: num,
block_spent: undefined,
}
});
fees_from_mempool = fees_from_mempool + sum_of_inputs - sum_of_outputs;
});
amnt = amnt + fees_from_mempool;
var timestamp = Math.floor( Date.now() / 1000 );
} else {
var generated_txid = block_id;
var fees_from_mempool = block_data.fees;
var amnt = 50 * 100_000_000 + fees_from_mempool;
var vout = 0;
var num = parseInt( generated_txid, 16 );
var timestamp = block_data.timestamp;
}
2025-03-15 22:25:47 -04:00
var privkey = $( '.your_addy' ).getAttribute( "data-privkey" );
2025-03-17 20:48:11 -04:00
var to_pubkey = testnet_generator.getPubkey( privkey );
var to_addy = tapscript.Address.p2tr.fromPubKey( to_pubkey, testnet_generator.addykind );
var from_pubkey = "00".repeat( 32 );
var from_addy = tapscript.Address.p2tr.fromPubKey( from_pubkey, testnet_generator.addykind );
if ( !block_id ) {
var txdata = tapscript.Tx.create({
vin: [testnet_generator.getVin( generated_txid, vout, amnt, from_addy )],
vout: [testnet_generator.getVout( amnt, to_addy )],
});
var txid = tapscript.Tx.util.getTxid( txdata );
var txs = [ tapscript.Tx.encode( txdata ).hex ];
txs = [ ...txs, ...testnet_generator.mempool ];
testnet_generator.mempool = [];
} else {
var txs = block_data.txs;
}
testnet_generator.blocks[ generated_txid ] = {
timestamp,
fees: fees_from_mempool,
txs,
}
var spky = tapscript.Script.encode( tapscript.Address.toScriptPubKey( to_addy ) ).hex.substring( 2 );
txs.forEach( ( txhex, idx ) => testnet_generator.processTxhex( txhex, idx, num ) );
testnet_generator.calcBalance();
testnet_generator.showBlock( num, testnet_generator.blocks[ generated_txid ].txs.length );
testnet_generator.calcMempool();
},
relayBlocks: async () => {
if ( Object.keys( testnet_generator.blocks ).length < = testnet_generator.blocks_relayed.length ) {
console.log( 'no need to relay any blocks --', Object.keys( testnet_generator.blocks ).length, 'out of', testnet_generator.blocks_relayed.length, 'blocks relayed' );
await testnet_generator.waitSomeTime( 2_000 );
testnet_generator.relayBlocks();
return;
}
var last_block_relayed = testnet_generator.blocks_relayed[ testnet_generator.blocks_relayed.length - 1 ];
var all_blocks = Object.keys( testnet_generator.blocks );
var blocks_to_relay = [];
if ( !last_block_relayed ) {
if ( all_blocks.length > 15 ) all_blocks.length = 15;
blocks_to_relay.push( ...all_blocks );
} else {
Object.keys( testnet_generator.blocks ).forEach( block_id => {
var num_of_last_block_relayed = parseInt( last_block_relayed, 16 );
var blocknum = parseInt( block_id, 16 );
if ( blocknum > num_of_last_block_relayed ) blocks_to_relay.push( block_id );
});
}
var first_block_id = blocks_to_relay[ 0 ];
var data_for_nostr = [];
blocks_to_relay.forEach( block_id => data_for_nostr.push( testnet_generator.blocks[ block_id ] ) );
data_for_nostr = JSON.stringify( data_for_nostr );
console.log( 'relaying these blocks:' );
console.log( data_for_nostr );
var privkey = $( '.your_addy' ).getAttribute( "data-privkey" );
var event = await super_nostr.prepEvent( privkey, data_for_nostr, 60426, [ [ "e", first_block_id ] ] );
2025-03-15 22:25:47 -04:00
super_nostr.sendEvent( event, testnet_generator.relays[ 0 ] );
var event2 = await super_nostr.prepEvent( privkey, String( Object.keys( testnet_generator.blocks ).length ), 10426 );
super_nostr.sendEvent( event2, testnet_generator.relays[ 0 ] );
2025-03-17 20:48:11 -04:00
testnet_generator.blocks_relayed.push( ...blocks_to_relay );
await testnet_generator.waitSomeTime( 2_000 );
testnet_generator.relayBlocks();
},
2025-03-30 14:51:05 -04:00
viewScript: async ( txid_and_vout_of_utxo_being_spent, txid_and_vout_in_which_it_is_spent, unique ) => {
2025-03-30 14:11:05 -04:00
var vout_of_utxo_being_spent = Number( txid_and_vout_of_utxo_being_spent.split( "_" )[ 1 ] );
var txid_in_which_it_is_spent = txid_and_vout_in_which_it_is_spent.split( "_" )[ 0 ];
var vout_in_which_it_is_spent = Number( txid_and_vout_in_which_it_is_spent.split( "_" )[ 1 ] );
var spubkey_of_utxo_being_spent = tapscript.Tx.decode( testnet_generator.utxo_set[ txid_and_vout_of_utxo_being_spent ].txhex ).vout[ vout_of_utxo_being_spent ].scriptPubKey;
var witness_of_utxo_being_spent = tapscript.Tx.decode( testnet_generator.utxo_set[ `${txid_in_which_it_is_spent}_0` ].txhex ).vin[ vout_in_which_it_is_spent ].witness;
if ( !witness_of_utxo_being_spent.length ) {
var script_arguments = [];
var script_program = tapscript.Script.decode( spubkey_of_utxo_being_spent );
}
if ( witness_of_utxo_being_spent.length === 1 ) {
var script_arguments = [ witness_of_utxo_being_spent[ 0 ] ];
var script_program = [
tapscript.Script.decode( spubkey_of_utxo_being_spent )[ 1 ],
"OP_CHECKSIG",
];
}
if ( witness_of_utxo_being_spent.length > 1 ) {
var script_arguments = JSON.parse( JSON.stringify( witness_of_utxo_being_spent ) );
script_arguments.length = script_arguments.length - 2;
var script_program = tapscript.Script.decode( witness_of_utxo_being_spent[ witness_of_utxo_being_spent.length - 2 ] );
}
2025-03-30 14:51:05 -04:00
var script_div = $$( `.unique_${unique} .input .btc_script` )[ vout_in_which_it_is_spent ];
var button = $$( `.unique_${unique} .input .view_script` )[ vout_in_which_it_is_spent ];
2025-03-30 14:11:05 -04:00
if ( script_div.classList.contains( "hidden" ) ) {
script_div.classList.remove( "hidden" );
button.innerText = "Hide bitcoin script";
script_div.getElementsByClassName( "script_arguments" )[ 0 ].innerHTML = `< p class = "script_element" > ${script_arguments.join( `</ p >< p class = "script_element" > ` )}</ p > `;
script_div.getElementsByClassName( "script_program" )[ 0 ].innerHTML = `< p class = "script_element" > ${script_program.join( `</ p >< p class = "script_element" > ` )}</ p > `;
} else {
script_div.classList.add( "hidden" );
button.innerText = "Show bitcoin script";
}
},
2024-11-21 01:24:10 -05:00
}
2025-11-10 01:07:41 +00:00
var sendMoney = async () => {
2024-11-21 01:24:10 -05:00
var destino = prompt( `enter the address you want to send money to` );
if ( !destino ) return;
var amnt = Number( prompt( `enter an amount of sats you want to send there` ) );
if ( !amnt ) return;
var feerate = Number( prompt( `enter the feerate you want to use` ) );
if ( !feerate ) return;
var txfee = 300 * feerate;
var input_ids = getUtxos( amnt + txfee );
if ( input_ids === "unable" ) return alert( `you don't have enough money, consider generating some blocks` );
var inputs = [];
input_ids.forEach( utxo_id => inputs.push( testnet_generator.utxos[ utxo_id ] ) );
var vin = [];
inputs.forEach( input => vin.push( testnet_generator.getVin( input[ "txid" ], input[ "vout" ], input[ "amnt" ], input[ "addy" ] ) ) );
var txdata = tapscript.Tx.create({
vin,
vout: [testnet_generator.getVout( amnt, destino )],
});
var sum_of_inputs = 0;
inputs.forEach( input => sum_of_inputs = sum_of_inputs + input[ "amnt" ] );
var change = sum_of_inputs - amnt - txfee;
var change_addy = $( '.your_addy' ).innerText;
if ( change > 330 ) txdata.vout.push( testnet_generator.getVout( change, change_addy ) );
var i; for ( i=0; i< txdata.vin.length ; i ++ ) {
var privkey = inputs[ i ][ " skey " ];
var sig = tapscript.Signer.taproot.sign( privkey , txdata , i ). hex ;
txdata . vin [ i ]. witness = [ sig ];
}
var txhex = tapscript.Tx.encode( txdata ). hex ;
2025-11-10 01:07:41 +00:00
var error = await testnet_generator . broadcast ( txhex );
if ( error . length !== 64 || ! testnet_generator . isValidHex ( error ) ) return alert ( error );
2024-11-21 01:24:10 -05:00
// highlight the mempool count label
$( '. txs_in_mempool_label ' ). style . backgroundColor = "pink" ;
setTimeout ( () = > {
$( '.txs_in_mempool_label' ).style.transition = "all 1s ease-in";
$( '.txs_in_mempool_label' ).style.backgroundColor = "white";
}, 10 );
2025-03-17 20:48:11 -04:00
testnet_generator.calcMempool();
2024-11-21 01:24:10 -05:00
return txhex;
}
var getUtxos = amount => {
var utxo_set = [];
Object.keys( testnet_generator.utxos ).forEach( utxo_id => utxo_set.push( testnet_generator.utxos[ utxo_id ] ) );
var set_to_return = [];
var sum_so_far = 0;
var new_utxo_set = JSON.parse( JSON.stringify( utxo_set ) );
var unable = false;
var loop = () => {
if ( !new_utxo_set.length ) {
unable = true;
return;
}
var rand = Math.floor( Math.random() * new_utxo_set.length );
set_to_return.push( new_utxo_set[ rand ][ "utxo_id" ] );
sum_so_far = sum_so_far + new_utxo_set[ rand ][ "amnt" ];
new_utxo_set.splice( rand, 1 );
if ( sum_so_far < amount + 330 ) loop ();
}
loop ();
if ( unable ) return ` unable `;
return set_to_return ;
}
2025-03-17 20:48:11 -04:00
var showAddress = privkey = > {
if ( privkey ) var [ addy, privkey ] = testnet_generator.getAddy( privkey );
else var [ addy, privkey ] = testnet_generator.getAddy();
$( '.your_addy' ).innerText = addy;
$( '.your_addy' ).setAttribute( "data-privkey", privkey );
}
$( '.send_btn' ).onclick = sendMoney;
2025-03-15 22:25:47 -04:00
var privkey = params.hasOwnProperty( "privkey" ) ? params.privkey : undefined;
showAddress( privkey );
2025-03-17 20:48:11 -04:00
$( '.generate_block' ).onclick = () => {testnet_generator.makeBlock();}
2025-03-15 22:25:47 -04:00
if ( !privkey ) $( '.generate_block' ).disabled = false;
if ( params.hasOwnProperty( "pubkey" ) ) {
testnet_generator.processExistingBlocks();
$( '.generate_block' ).remove();
2025-03-17 20:48:11 -04:00
$( '.options' ).remove();
2025-03-15 22:25:47 -04:00
$( '.wallet' ).style.display = "none";
$( '.txs_in_mempool_label' ).style.display = "none";
}
2025-11-10 01:07:41 +00:00
$( '.broadcast_btn' ).onclick = async () => {
var txhex = $( '.tx_broadcaster' ).value;
var error = await testnet_generator.broadcast( txhex );
if ( error.length !== 64 || !testnet_generator.isValidHex( error ) ) return alert( error );
}
2025-03-17 20:48:11 -04:00
if ( $( '.auto_generate' ) ) $( '.auto_generate' ).onchange = () => {
autogen_value = $( '.auto_generate' ).value;
if ( autogen_value.includes( "---" ) ) $( '.auto_go' ).disabled = true;
else $( '.auto_go' ).removeAttribute( "disabled" );
2024-11-21 01:24:10 -05:00
}
2025-03-17 20:48:11 -04:00
var autogen_num = null;
var autogo = false;
if ( $( '.auto_go' ) ) $( '.auto_go' ).onclick = () => {
if ( $( '.auto_go' ).innerText === "Go" ) {
if ( $( '.auto_go' ).disabled ) return;
autogen_value = $( '.auto_generate' ).value;
var num = 500;
if ( autogen_value === "1 second" ) num = 1_000;
if ( autogen_value === "3 seconds" ) num = 3_000;
if ( autogen_value === "5 seconds" ) num = 5_000;
if ( autogen_value === "10 seconds" ) num = 10_000;
if ( autogen_value === "30 seconds" ) num = 30_000;
if ( autogen_value === "1 minute" ) num = 60_000;
if ( autogen_value === "2 minutes" ) num = 120_000;
if ( autogen_value === "5 minutes" ) num = 300_000;
if ( autogen_value === "10 minutes" ) num = 600_000;
autogen_num = num;
autogo = true;
autoLoop();
$( '.auto_go' ).innerText = "Stop";
} else {
autogo = false;
$( '.auto_go' ).innerText = "Go";
2025-03-15 22:25:47 -04:00
}
}
2025-03-17 20:48:11 -04:00
var autoLoop = async () => {
testnet_generator.makeBlock();
var keep_going = autogo;
await testnet_generator.waitSomeTime( autogen_num );
if ( !autogo ) keep_going = false;
if ( keep_going ) autoLoop();
}
var showPage = page => {
$( '.block_explorer' ).classList.add( "hidden" );
$( '.tx_explorer' ).classList.add( "hidden" );
$( '.addy_explorer' ).classList.add( "hidden" );
$( '.stuff_thats_not_an_explorer' ).classList.add( "hidden" );
$( `.${page}` ).classList.remove( "hidden" );
}
(async()=>{
if ( privkey ) {
var private_url = window.location.protocol + "//" + window.location.hostname + window.location.pathname + `#privkey=${privkey}`;
var sharable_url = window.location.protocol + "//" + window.location.hostname + window.location.pathname + `#pubkey=${testnet_generator.getPubkey( privkey )}`;
if ( params.hasOwnProperty( "first_time" ) ) {
window.onload = () => {
var html = `
< p > Save this and do not share it -- it is how you can return to this testnet to keep making blocks:</ p >
< p >< a href = "${private_url}" target = "_blank" > ${private_url}</ a ></ p >
< p > Share this:</ p >
< p >< a href = "${sharable_url}" target = "_blank" > ${sharable_url}</ a ></ p >
`;
showModal( html );
}
} else {
window.onload = () => {
$( '.make_sharable_testnet' ).innerText = 'Show sharable info';
$( '.make_sharable_testnet' ).onclick = () => {
var html = `
< p > Save this and do not share it -- it is how you can return to this testnet to keep making blocks:</ p >
< p >< a href = "${private_url}" target = "_blank" > ${private_url}</ a ></ p >
< p > Share this:</ p >
< p >< a href = "${sharable_url}" target = "_blank" > ${sharable_url}</ a ></ p >
`;
showModal( html );
2025-03-15 22:25:47 -04:00
}
2025-03-17 20:48:11 -04:00
}
2025-03-15 22:25:47 -04:00
}
2025-03-17 20:48:11 -04:00
var pubkey = testnet_generator.getPubkey( privkey );
var events = await super_nostr.getEvents( testnet_generator.relays[ 0 ], null, [ pubkey ], [ 10426 ], null, null, 1 );
if ( !events.length ) {
var event = await super_nostr.prepEvent( privkey, String( Object.keys( testnet_generator.blocks ).length ), 10426 );
super_nostr.sendEvent( event, testnet_generator.relays[ 0 ] );
}
await testnet_generator.waitSomeTime( 1_000 );
testnet_generator.processExistingBlocks();
2025-11-10 01:07:41 +00:00
var listenFunction = async socket => {
var subId = super_nostr.bytesToHex( crypto.getRandomValues( new Uint8Array( 8 ) ) );
var filter = {}
filter.kinds = [ 4 ];
filter.since = Math.floor( Date.now() / 1000 );
var subscription = [ "REQ", subId, filter ];
socket.send( JSON.stringify( subscription ) );
}
var handleFunction = async message => {
var [ type, subId, event ] = JSON.parse( message.data );
if ( !event || event === true ) return;
2025-11-10 02:27:55 +00:00
var relay = testnet_generator.relays[ 0 ];
var recipient = event.pubkey;
var content = await super_nostr.alt_decrypt( privkey, recipient, event.content );
2025-11-10 01:07:41 +00:00
var json = JSON.parse( content );
if ( json.msg_type === "broadcast" ) {
var txhex = json.msg_value;
var txid_or_error = await testnet_generator.broadcast( txhex );
var message = JSON.stringify({
msg_type: "broadcast_reply",
msg_value: txid_or_error,
});
2025-11-10 02:27:55 +00:00
var emsg = await super_nostr.alt_encrypt( privkey, recipient, message );
var event = await super_nostr.prepEvent( privkey, emsg, 4, [ [ "p", recipient ] ] );
super_nostr.sendEvent( event, relay );
}
if ( json.msg_type === "utxos" ) {
var address = json.msg_value;
var spub = tapscript.Script.encode( tapscript.Address.toScriptPubKey( address ) ).hex.substring( 2 );
var utxos = [];
if ( testnet_generator.addresses.hasOwnProperty( spub ) ) {
var utxo_types = Object.keys( testnet_generator.addresses[ spub ] );
utxo_types.forEach( utxo_type => {
testnet_generator.addresses[ spub ][ utxo_type ].forEach( utxo => {
var utxo_info = testnet_generator.utxo_set[ utxo ];
var txid = utxo_info.txid;
var vout = utxo_info.vout;
var value = utxo_info.amnt;
if ( isNaN( utxo_info.block_created ) ) {
var status = {
confirmed: false,
}
} else {
var block_hash = testnet_generator.intToHex( utxo_info.block_created ).padStart( 64, "0" );
var status = {
confirmed: true,
block_height: utxo_info.block_created,
block_hash,
block_time: testnet_generator.blocks[ block_hash ].timestamp,
}
}
utxos.push({ txid, vout, status, value });
});
});
}
var message = JSON.stringify({
msg_type: "utxos_reply",
msg_value: utxos,
});
2025-11-11 17:36:40 +00:00
var emsg = await super_nostr.alt_encrypt( privkey, recipient, message );
var event = await super_nostr.prepEvent( privkey, emsg, 4, [ [ "p", recipient ] ] );
super_nostr.sendEvent( event, relay );
}
if ( json.msg_type === "txraw" ) {
var txid = json.msg_value;
var txhex = 'tx not found';
if ( testnet_generator.utxo_set.hasOwnProperty( `${txid}_0` ) ) {
var { block_created, idx } = testnet_generator.utxo_set[ `${txid}_0` ];
if ( isNaN( block_created ) ) {
testnet_generator.mempool.every( tx => {
if ( tapscript.Tx.util.getTxid( tx ) !== txid ) return true;
txhex = tx;
});
} else {
var block_hash = testnet_generator.intToHex( block_created ).padStart( 64, "0" );
txhex = testnet_generator.blocks[ block_hash ].txs[ idx ];
}
}
var message = JSON.stringify({
msg_type: "txraw_reply",
msg_value: txhex,
});
2025-11-10 01:07:41 +00:00
var emsg = await super_nostr.alt_encrypt( privkey, recipient, message );
var event = await super_nostr.prepEvent( privkey, emsg, 4, [ [ "p", recipient ] ] );
super_nostr.sendEvent( event, relay );
}
}
var connection_id = await super_nostr.newPermanentConnection( testnet_generator.relays[ 0 ], listenFunction, handleFunction );
console.log( 'connected!', connection_id );
console.log( 'users can talk to me at this pubkey:' );
console.log( pubkey );
console.log( 'and this relay:' );
console.log( testnet_generator.relays[ 0 ] );
2025-03-15 22:25:47 -04:00
}
2025-03-17 20:48:11 -04:00
if ( !$( '.auto_generate' ) ) {
window.scrollTo( 0, 0 );
return;
}
$( '.auto_generate' ).value = "--- Auto generate ---";
$( '.auto_go' ).disabled = true;
$( '.tx_broadcaster' ).value = "";
window.scrollTo( 0, 0 );
})();
</ script >
< script >
2025-11-10 01:07:41 +00:00
var runScript = async () => {
2024-11-21 01:24:10 -05:00
var keypairs = [ testnet_generator . getAddy (), testnet_generator . getAddy (), testnet_generator . getAddy ()];
var script = [
"OP_3" ,
2024-11-21 15:18:48 -05:00
"OP_CHECKSEQUENCEVERIFY" ,
2024-11-21 01:24:10 -05:00
"OP_DROP" ,
];
keypairs . forEach ( ( keypair , idx ) => {
if ( ! idx ) script . push ( testnet_generator . getPubkey ( keypair [ 1 ] ), "OP_CHECKSIG" );
else script . push ( testnet_generator . getPubkey ( keypair [ 1 ] ), "OP_CHECKSIGADD" );
});
script . push ( "OP_1" , "OP_GREATERTHANOREQUAL" );
console . log ( "script:" );
console . log ( script );
var tree = [ tapscript . Tap . encodeScript ( script )];
var tapleaf = tree [ 0 ];
var backup_pubkey = "ab" . repeat ( 32 );
var [ tpubkey , cblock ] = tapscript . Tap . getPubKey ( backup_pubkey , { target : tapleaf });
var addy = tapscript . Address . p2tr . fromPubKey ( tpubkey , 'testnet' );
console . log ( 'send money to this address:' );
console . log ( addy );
2025-11-10 01:07:41 +00:00
var txhex = await sendMoney ();
2024-11-21 01:24:10 -05:00
if ( ! testnet_generator . isValidHex ( txhex ) ) return ;
var txid = tapscript . Tx . util . getTxid ( txhex );
var vout = 0 ;
var amnt = Number ( prompt ( `enter the amount you sent` ) );
// var txid = prompt( `enter the txid` );
// var vout = Number( prompt( `and the vout` ) );
// var amnt = Number( prompt( `and the amnt` ) );
var txdata = tapscript . Tx . create ({
vin : [ testnet_generator . getVin ( txid , vout , amnt , addy )],
vout : [ testnet_generator . getVout ( amnt - 500 , $ ( '.your_addy' ). innerText )],
});
2024-11-21 15:18:48 -05:00
txdata . vin [ 0 ]. sequence = 3 ;
2024-11-21 01:24:10 -05:00
txdata . vin . forEach ( ( input , index ) => {
// var sig_0 = tapscript.Signer.taproot.sign( keypairs[ 0 ][ 1 ], txdata, 0, { extension: tapleaf }).hex;
// sig_0 = ( BigInt( '0x' + sig_0 ) - BigInt( '0x01' ) ).toString( 16 );
// if ( sig_0.length % 2 ) sig_0 = "0" + sig_0;
var sig_0 = "" ;
var sig_1 = tapscript . Signer . taproot . sign ( keypairs [ 1 ][ 1 ], txdata , 0 , { extension : tapleaf }). hex ;
var sighash = tapscript . Signer . taproot . hash ( txdata , 0 , { extension : tapleaf }). hex ;
// var sig_2 = tapscript.Signer.taproot.sign( keypairs[ 2 ][ 1 ], txdata, 0, { extension: tapleaf }).hex;
var sig_2 = "" ;
// console.log( 44, "pubkey:", tapscript.Address.toScriptPubKey( keypairs[ 1 ][ 0 ] )[ 1 ], "sighash:", sighash, "sig:", sig_1 );
txdata . vin [ index ]. witness = [ sig_2 , sig_1 , sig_0 , script , cblock ];
});
console . log ( tapscript . Tx . encode ( txdata ). hex );
}
2025-03-15 22:25:47 -04:00
var useP2A = privkey_1 => {
if ( ! privkey_1 ) {
privkey_1 = testnet_generator . getPrivkey ();
var pubkey_1 = testnet_generator . getPubkey ( privkey_1 );
var address_1 = tapscript . Address . fromScriptPubKey ( [ 1 , pubkey_1 ], testnet_generator . addykind );
console . log ( `send money to this address:` );
console . log ( address_1 );
console . log ( `then run this command:` );
console . log ( `useP2A( " ${ privkey_1 } " );` );
alert ( `follow the instructions in your browser console` );
return ;
}
var pubkey_1 = testnet_generator . getPubkey ( privkey_1 );
var privkey_2 = testnet_generator . getPrivkey ();
var pubkey_2 = testnet_generator . getPubkey ( privkey_2 );
var address_1 = tapscript . Address . fromScriptPubKey ( [ 1 , pubkey_1 ], testnet_generator . addykind );
var address_2 = tapscript . Address . fromScriptPubKey ( [ 1 , pubkey_2 ], testnet_generator . addykind );
var txid = prompt ( `enter the txid by which you sent money to the address` );
if ( ! txid ) return ;
var vout = Number ( prompt ( `and the vout` ) );
var amnt = Number ( prompt ( `and the amount` ) );
var destino = $ ( '.your_addy' ). innerText ;
var using_p2a = true ;
var tx_a = tapscript . Tx . create ({
version : 3 ,
vin : [{
txid ,
vout ,
prevout : {
value : amnt ,
scriptPubKey : tapscript . Address . toScriptPubKey ( address_1 ),
}
}],
vout : [{
value : amnt ,
scriptPubKey : tapscript . Address . toScriptPubKey ( address_2 ),
}],
});
if ( using_p2a ) {
tx_a . vout . unshift ({
value : 240 ,
scriptPubKey : "51024e73" ,
});
tx_a . vout [ 1 ]. value = amnt - 240 ;
}
var sig_a = tapscript . Signer . taproot . sign ( privkey_1 , tx_a , 0 ). hex ;
tx_a . vin [ 0 ]. witness = [ sig_a ];
//create tx_b, which pays double fees to send from address_2 to anywhere
//ensure tx_b is a v3 transaction
var txid_a = tapscript . Tx . util . getTxid ( tx_a );
var tx_b = tapscript . Tx . create ({
version : 3 ,
vin : [{
txid : txid_a ,
vout : 0 ,
prevout : {
value : amnt ,
scriptPubKey : tapscript . Address . toScriptPubKey ( address_2 ),
}
}],
vout : [{
value : amnt - 1000 ,
scriptPubKey : tapscript . Address . toScriptPubKey ( destino ),
}],
});
if ( using_p2a ) {
tx_b . vin . unshift ({
txid : txid_a ,
vout : 0 ,
prevout : {
value : 240 ,
scriptPubKey : "51024e73" ,
}
});
tx_b . vin [ 1 ]. vout = 1 ;
tx_b . vin [ 1 ]. prevout . value = amnt - 240 ;
}
var txid_b = tapscript . Tx . util . getTxid ( tx_b );
var input_to_sign = 0 ;
if ( using_p2a ) input_to_sign = 1 ;
var sig_b = tapscript . Signer . taproot . sign ( privkey_2 , tx_b , input_to_sign ). hex ;
tx_b . vin [ input_to_sign ]. witness = [ sig_b ];
var txs = [ tapscript . Tx . encode ( tx_a ). hex , tapscript . Tx . encode ( tx_b ). hex ];
console . log ( txs . join ( "," ) );
}
2025-03-17 20:48:11 -04:00
if ( $ ( '.make_sharable_testnet' ) ) $ ( '.make_sharable_testnet' ). onclick = () => {
if ( window . location . href . includes ( "#" ) ) return ;
var conf = confirm ( 'are you sure you want to make a sharable testnet? The one you have now will get deleted and you will have to restart' );
if ( ! conf ) return ;
var privkey = $ ( '.your_addy' ). getAttribute ( "data-privkey" );
window . location . href += `#privkey= ${ privkey } #first_time=true` ;
window . location . reload ();
}
2025-03-15 22:25:47 -04:00
</ script >
2025-03-17 20:48:11 -04:00
< div class = "black-bg hidden" onclick = "modalVanish();" ></ div >
< div class = "modal hidden" ></ div >
2024-11-21 01:24:10 -05:00
</ body >
</ html >