3297 lines
211 KiB
HTML
3297 lines
211 KiB
HTML
<!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>
|
|
<script src="https://supertestnet.github.io/bankify/super_nostr.js"></script>
|
|
<script src="https://unpkg.com/@cmdcode/tapscript@1.4.0"></script>
|
|
<script src="https://bundle.run/noble-secp256k1@1.2.14"></script>
|
|
<!--
|
|
<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>
|
|
-->
|
|
<style>
|
|
* {
|
|
box-sizing: border-box;
|
|
font-size: 1.15rem;
|
|
font-family: Arial, sans-serif;
|
|
}
|
|
body {
|
|
margin: 0;
|
|
}
|
|
.inner_html {
|
|
max-width: 800px;
|
|
padding: 3rem 1rem;
|
|
margin: auto;
|
|
line-height: 1.25;
|
|
padding: 0;
|
|
}
|
|
.inner_body {
|
|
margin: 3rem 1rem;
|
|
word-wrap: break-word;
|
|
}
|
|
h1 {
|
|
font-size: 2rem;
|
|
}
|
|
h2, h2 * {
|
|
font-size: 1.5rem;
|
|
}
|
|
input, textarea {
|
|
line-height: 1.25;
|
|
width: 100%;
|
|
height: 1.8rem;
|
|
font-size: 1.15rem;
|
|
border: 1px solid grey;
|
|
}
|
|
textarea {
|
|
height: 10rem;
|
|
}
|
|
.hidden {
|
|
display: none !important;
|
|
}
|
|
.list_container {
|
|
overflow-y: scroll;
|
|
}
|
|
.no_blocks_msg {
|
|
height: 248px;
|
|
line-height: 248px;
|
|
text-align: center;
|
|
}
|
|
.list_of_blocks {
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
.block {
|
|
display: inline-block;
|
|
height: 11.5rem;
|
|
width: 192px;
|
|
margin: 1rem;
|
|
cursor: pointer;
|
|
overflow: hidden;
|
|
flex-shrink: 0;
|
|
}
|
|
.block:hover {
|
|
background-color: pink;
|
|
}
|
|
.block_inner {
|
|
display: inline-block;
|
|
position: relative;
|
|
height: 9.5rem;
|
|
width: 7.5rem;
|
|
margin: 1.5rem;
|
|
}
|
|
.block_back {
|
|
border: 1px solid black;
|
|
height: 6rem;
|
|
width: 6rem;
|
|
float: right;
|
|
display: inline;
|
|
background-color: #232838;
|
|
border-left: 0;
|
|
border-bottom: 0;
|
|
}
|
|
.block_front {
|
|
border: 1px solid black;
|
|
height: 6rem;
|
|
width: 6rem;
|
|
display: flex;
|
|
align-items: center;
|
|
position: absolute;
|
|
bottom: 2rem;
|
|
background-color: #3953c6;
|
|
color: white;
|
|
text-align: center;
|
|
}
|
|
.block_front div {
|
|
width: 100%;
|
|
}
|
|
.block_side1 {
|
|
height: 2rem;
|
|
background-color: #02078e;
|
|
aspect-ratio: 1;
|
|
rotate: 45deg;
|
|
border-left: 1px solid black;
|
|
position: absolute;
|
|
top: .47rem;
|
|
left: 0.47rem;
|
|
}
|
|
.block_side2 {
|
|
height: 5.8rem;
|
|
background-color: #02078e;
|
|
position: absolute;
|
|
top: 1px;
|
|
left: 1.5rem;
|
|
width: 5.9rem;
|
|
}
|
|
.block_side3 {
|
|
height: 2.4rem;
|
|
width:1px;
|
|
background-color: black;
|
|
rotate: 45deg;
|
|
position: absolute;
|
|
top: -.3rem;
|
|
right: .85rem;
|
|
}
|
|
.block_side4 {
|
|
height: 2rem;
|
|
background-color: #02078e;
|
|
aspect-ratio: 1;
|
|
rotate: 45deg;
|
|
border-right: 1px solid black;
|
|
position: absolute;
|
|
bottom: 2.50rem;
|
|
right: 0.5rem;
|
|
}
|
|
.block_num {
|
|
position: absolute;
|
|
bottom: 0rem;
|
|
width: 80%;
|
|
text-align: center;
|
|
}
|
|
.wallet {
|
|
border: 1px solid black;
|
|
border-radius: 1rem;
|
|
padding: 1rem;
|
|
text-align: center;
|
|
margin-top: 1rem;
|
|
}
|
|
.auto_generate_label {
|
|
display: inline-block;
|
|
}
|
|
.wallet_btns, .broadcast_btn, .auto_generate_label,
|
|
.auto_go, .broadcast_success_label, .make_sharable {
|
|
margin-top: 1rem;
|
|
}
|
|
.block_info, .tx_info_label, .addy_info_label {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
.block_explorer, .tx, .input, .output,
|
|
.tx_explorer, .addy_explorer, .btc_script {
|
|
border: 1px solid black;
|
|
border-radius: 1rem;
|
|
padding: 1rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
.close_explorer {
|
|
cursor: pointer;
|
|
}
|
|
.explorer_block_num, .explorer_txid,
|
|
.explorer_addy, .close_explorer {
|
|
padding: 1rem;
|
|
}
|
|
.explorer_block_num,.explorer_addy,
|
|
.explorer_txid {
|
|
padding-left: 0;
|
|
}
|
|
.txid_link, .block_link, .addy_link {
|
|
color: blue;
|
|
text-decoration: underline;
|
|
cursor: pointer;
|
|
}
|
|
.pink {
|
|
background-color: pink;
|
|
}
|
|
.lightblue {
|
|
background-color: lightblue;
|
|
}
|
|
.yellow {
|
|
background-color: yellow;
|
|
}
|
|
.green {
|
|
background-color: lightgreen;
|
|
}
|
|
.white {
|
|
background-color: white;
|
|
}
|
|
.grey {
|
|
background-color: #cccccc;
|
|
}
|
|
.script_arguments_label, .script_program_label {
|
|
font-weight: bold;
|
|
}
|
|
.btc_script {
|
|
.margin-bottom: auto;
|
|
}
|
|
@media screen and (max-width: 600px) {
|
|
}
|
|
</style>
|
|
<style>
|
|
.black-bg {
|
|
width: 100%;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
background-color: black;
|
|
opacity: .5;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
}
|
|
.modal {
|
|
position: fixed;
|
|
box-sizing: border-box;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%,-50%);
|
|
width: 90%;
|
|
max-width: 560px;
|
|
background-color: white;
|
|
border-radius: 1rem;
|
|
padding: 20px;
|
|
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>
|
|
<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 );
|
|
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 ];
|
|
});
|
|
</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">
|
|
<div><button class="generate_block" disabled>Generate block</button></div>
|
|
<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>
|
|
</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: {},
|
|
relays: [ "wss://no.str.cr" ],
|
|
// relays: [ "ws://127.0.0.1:6969/" ],
|
|
blocks_relayed: [],
|
|
addykind: "testnet",
|
|
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" ), "" ),
|
|
hexToText: hex => {
|
|
var bytes = new Uint8Array( Math.ceil( hex.length / 2 ) );
|
|
var i; for ( i=0; i<hex.length; i++ ) bytes[ i ] = parseInt( hex.substr( i * 2, 2 ), 16 );
|
|
var text = new TextDecoder().decode( bytes );
|
|
return text;
|
|
},
|
|
getPrivkey: () => testnet_generator.bytesToHex( nobleSecp256k1.utils.randomPrivateKey() ),
|
|
getPubkey: privkey => nobleSecp256k1.getPublicKey( privkey, true ).substring( 2 ),
|
|
getAddy: privkey => {
|
|
if ( !privkey ) privkey = testnet_generator.getPrivkey();
|
|
var pubkey = testnet_generator.getPubkey( privkey );
|
|
var addy = tapscript.Address.p2tr.fromPubKey( pubkey, testnet_generator.addykind );
|
|
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 );
|
|
}
|
|
},
|
|
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( "" );
|
|
},
|
|
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 );
|
|
},
|
|
isValidAddress: address => {
|
|
try {
|
|
return !!tapscript.Address.decode( address ).script;
|
|
} catch( e ) {return;}
|
|
return;
|
|
},
|
|
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,
|
|
scriptPubKey: testnet_generator.isValidAddress( addy ) ? tapscript.Address.toScriptPubKey( addy ) : addy,
|
|
},
|
|
}),
|
|
getVout: ( amnt, addy ) => ({
|
|
value: amnt,
|
|
scriptPubKey: testnet_generator.isValidAddress( addy ) ? tapscript.Address.toScriptPubKey( addy ) : addy,
|
|
}),
|
|
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 ) => {
|
|
var unique = testnet_generator.getPrivkey();
|
|
if ( idx === undefined ) idx = 1;
|
|
var background_color = "pink";
|
|
if ( idx % 2 ) background_color = "lightblue";
|
|
var txs_html = ``;
|
|
var tx = tapscript.Tx.decode( txhex );
|
|
var txid = tapscript.Tx.util.getTxid( tx );
|
|
var display_fee = !!idx ? "" : "hidden";
|
|
var tx_html = `
|
|
<div class="tx ${background_color} tx_${txid} unique_${unique}">
|
|
<p>Tx: <span class="txnum">${idx}</span></p>
|
|
<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;
|
|
try {
|
|
from_addy = tapscript.Address.fromScriptPubKey( from_addy, testnet_generator.addykind );
|
|
} catch( e ) {}
|
|
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" ];
|
|
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>`;
|
|
else inputs_html = inputs_html + `
|
|
<div class="input ${vin_bg_color}">
|
|
<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>
|
|
<p><button class="view_script" onclick="testnet_generator.viewScript( '${input_txid}_${input[ "vout" ]}', '${txid}_${index}', '${unique}' );">View bitcoin script</button></p>
|
|
<div class="btc_script hidden">
|
|
<p class="script_arguments_label">Arguments</p>
|
|
<div class="script_arguments"></div>
|
|
<p class="script_program_label">Program</p>
|
|
<div class="script_program"></div>
|
|
</div>
|
|
</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;
|
|
try {
|
|
addy = tapscript.Address.fromScriptPubKey( addy, testnet_generator.addykind );
|
|
} catch( e ) {}
|
|
var amnt = testnet_generator.utxo_set[ output_id ].amnt;
|
|
var vout_bg_color = "white";
|
|
if ( index % 2 ) vout_bg_color = "grey";
|
|
outputs_html = outputs_html + `
|
|
<div class="output ${vout_bg_color}">
|
|
<p>Vout: <span class="which_vout_is_this">${index}</span></p>
|
|
<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;
|
|
},
|
|
showSoloTx: ( txid, txhex, idx, block_mined_if_coinbase ) => {
|
|
$( '.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";
|
|
if ( block_mined_if_coinbase ) mined_in_block = block_mined_if_coinbase;
|
|
$( '.mined_in_block' ).innerHTML = `${mined_in_block}`;
|
|
}
|
|
var tx_size = tapscript.Tx.util.getTxSize( txhex ).vsize;
|
|
$( '.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' );
|
|
testnet_generator.showSoloTx( txid, txhex, idx, block_mined_if_coinbase );
|
|
}
|
|
});
|
|
$$( '.addy_link' ).forEach( item => {
|
|
item.onclick = e => {
|
|
var addy = e.target.innerText;
|
|
showPage( 'addy_explorer' );
|
|
testnet_generator.showAddyInfo( addy );
|
|
}
|
|
});
|
|
},
|
|
showAddyInfo: addy => {
|
|
$( '.explorer_addy' ).innerText = `${addy.substring( 0, 4 )}...${addy.substring( addy.length - 4 )}`;
|
|
$( '.full_address' ).innerText = addy;
|
|
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;
|
|
var sent = [];
|
|
var total_sent = 0;
|
|
var total_sent_to_self = 0;
|
|
testnet_generator.addresses[ spky ].sent.forEach( input_id => {
|
|
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 ) => {
|
|
var recipient = vout[ "scriptPubKey" ];
|
|
if ( recipient !== spky ) return;
|
|
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;
|
|
testnet_generator.addresses[ spky ].received.forEach( output_id => {
|
|
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' );
|
|
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 );
|
|
}
|
|
});
|
|
},
|
|
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 );
|
|
}
|
|
});
|
|
},
|
|
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 );
|
|
else return alert( 'error: the block headers could not be synced, so the blocks cannot be synced either. refresh the page to try again' );
|
|
$( '.no_blocks_msg' ).innerText = `synced 0 out of ${num_of_blocks_expected} blocks`;
|
|
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 );
|
|
return;
|
|
}
|
|
var since = events[ 0 ].created_at - 1;
|
|
since_to_use_in_outer_loop = since;
|
|
await testnet_generator.waitSomeTime( 1 );
|
|
await innerLoop( since );
|
|
}
|
|
await innerLoop( since );
|
|
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();
|
|
}
|
|
}
|
|
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 {
|
|
var sum_of_inputs = 0;
|
|
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" ] ) {
|
|
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 );
|
|
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" ] ) );
|
|
var tx_fee_in_sats = sum_of_inputs - sum_of_outputs;
|
|
if ( tx_fee_in_sats < 0 ) return `that tx had more money in its outputs than its inputs, which is not allowed -- no printing money! (specifically, that tx had ${sum_of_inputs} sats in its inputs and ${sum_of_outputs} sats in its outputs)`;
|
|
var tx_size = tapscript.Tx.util.getTxSize( txhex ).vsize;
|
|
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! But it didn't work, your tx is invalid`;
|
|
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 ) {}
|
|
}
|
|
}
|
|
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 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;
|
|
return 'that tx was invalid because it had an improperly formatted sig';
|
|
}
|
|
if ( !sig.length ) {
|
|
input_with_improperly_formatted_sig = index;
|
|
return 'that tx was invalid because it had an improperly formatted sig';
|
|
} else {
|
|
var sigflag = 1; //sighash_all
|
|
if ( sig.length === 130 && sig.substring( 128 ) === "02" ) sigflag = 2; //sighash_none
|
|
if ( sig.length === 130 && sig.substring( 128 ) === "03" ) sigflag = 3; //sighash_single
|
|
if ( sig.length === 130 && sig.substring( 128 ) === "81" ) sigflag = 128 | 1; //sighash_all | anyone_can_pay
|
|
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
|
|
if ( sig.length === 130 ) sig = sig.substring( 0, 128 );
|
|
if ( sigflag != 1 ) var sighash = tapscript.Signer.taproot.hash( tx, index, { sigflag }).hex;
|
|
else var sighash = tapscript.Signer.taproot.hash( tx, index ).hex;
|
|
tx.vin.forEach( ( vin, index ) => tx.vin[ index ].prevout.value = Number( vin.prevout.value ) );
|
|
tx.vout.forEach( ( vout, index ) => tx.vout[ index ].value = Number( vout.value ) );
|
|
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;
|
|
return `that tx was invalid because input ${i} had a sig that failed an implicit verify opcode (the stack only had a single sig on it, and in that case the sig must validate, which is what op_verify ensures)`;
|
|
}
|
|
}
|
|
} 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;
|
|
return;
|
|
}
|
|
if ( !sig.length ) {
|
|
params.push( 0 );
|
|
} else {
|
|
var sigflag = 1; //sighash_all
|
|
if ( sig.length === 130 && sig.substring( 128 ) === "02" ) sigflag = 2; //sighash_none
|
|
if ( sig.length === 130 && sig.substring( 128 ) === "03" ) sigflag = 3; //sighash_single
|
|
if ( sig.length === 130 && sig.substring( 128 ) === "81" ) sigflag = 128 | 1; //sighash_all | anyone_can_pay
|
|
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
|
|
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;
|
|
return;
|
|
}
|
|
if ( !sig.length ) {
|
|
input_whose_script_failed_a_verify_opcode = index;
|
|
return;
|
|
} else {
|
|
var sigflag = 1; //sighash_all
|
|
if ( sig.length === 130 && sig.substring( 128 ) === "02" ) sigflag = 2; //sighash_none
|
|
if ( sig.length === 130 && sig.substring( 128 ) === "03" ) sigflag = 3; //sighash_single
|
|
if ( sig.length === 130 && sig.substring( 128 ) === "81" ) sigflag = 128 | 1; //sighash_all | anyone_can_pay
|
|
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
|
|
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;
|
|
return;
|
|
}
|
|
// 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
|
|
if ( sig.length === 130 && sig.substring( 128 ) === "02" ) sigflag = 2; //sighash_none
|
|
if ( sig.length === 130 && sig.substring( 128 ) === "03" ) sigflag = 3; //sighash_single
|
|
if ( sig.length === 130 && sig.substring( 128 ) === "81" ) sigflag = 128 | 1; //sighash_all | anyone_can_pay
|
|
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
|
|
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( "," );
|
|
if ( tx_array.length > 2 ) return `too many txs in this package -- only 2 are allowed`;
|
|
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 );
|
|
if ( first_tx_is_invalid ) return first_tx_is_invalid;
|
|
var allow_zero_fee = false;
|
|
var second_tx_is_invalid = await testnet_generator.validateTx( tx_array[ 1 ], allow_zero_fee, tx_array[ 0 ] );
|
|
if ( second_tx_is_invalid ) return second_tx_is_invalid;
|
|
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;
|
|
});
|
|
if ( !all_parents_confirmed ) return 'you cannot broadcast this package because it tries to spend an unconfirmed output, which is not allowed';
|
|
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;
|
|
var parent_tx_size = tapscript.Tx.util.getTxSize( tx_array[ 0 ] ).vsize;
|
|
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;
|
|
var child_tx_size = tapscript.Tx.util.getTxSize( tx_array[ 1 ] ).vsize;
|
|
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;
|
|
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';
|
|
var skip_validation = true;
|
|
var txid = await testnet_generator.broadcast( tapscript.Tx.encode( parent_tx ).hex, skip_validation );
|
|
testnet_generator.broadcast( tapscript.Tx.encode( child_tx ).hex, skip_validation );
|
|
return txid;
|
|
}
|
|
var reason_why_tx_is_invalid = await testnet_generator.validateTx( txhex );
|
|
if ( reason_why_tx_is_invalid ) return reason_why_tx_is_invalid;
|
|
}
|
|
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 );
|
|
return txid;
|
|
},
|
|
showBlock: ( num, count ) => {
|
|
var div = document.createElement( "div" );
|
|
div.innerHTML = `
|
|
<div class="block">
|
|
<div class="block_inner">
|
|
<div class="block_back"></div>
|
|
<div class="block_side1"></div>
|
|
<div class="block_side2"></div>
|
|
<div class="block_side3"></div>
|
|
<div class="block_side4"></div>
|
|
<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",
|
|
});
|
|
}
|
|
$( '.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" ),
|
|
}
|
|
}
|
|
});
|
|
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,
|
|
}
|
|
});
|
|
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;
|
|
console.log( txid, tx.vout.length );
|
|
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;
|
|
}
|
|
var privkey = $( '.your_addy' ).getAttribute( "data-privkey" );
|
|
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 ] ] );
|
|
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 ] );
|
|
testnet_generator.blocks_relayed.push( ...blocks_to_relay );
|
|
await testnet_generator.waitSomeTime( 2_000 );
|
|
testnet_generator.relayBlocks();
|
|
},
|
|
viewScript: async ( txid_and_vout_of_utxo_being_spent, txid_and_vout_in_which_it_is_spent, unique ) => {
|
|
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 ] );
|
|
}
|
|
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 ];
|
|
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";
|
|
}
|
|
},
|
|
}
|
|
if ( params.hasOwnProperty( "relays" ) ) testnet_generator.relays = JSON.parse( testnet_generator.hexToText( params.relays ) );
|
|
var sendMoney = async ( destino, amnt, feerate ) => {
|
|
try {
|
|
if ( !destino ) destino = prompt( `enter the address you want to send money to` );
|
|
if ( !destino ) return;
|
|
if ( !amnt ) amnt = Number( prompt( `enter an amount of sats you want to send there` ) );
|
|
if ( !amnt ) return;
|
|
if ( !feerate ) 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 `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;
|
|
var error = await testnet_generator.broadcast( txhex );
|
|
if ( error.length !== 64 || !testnet_generator.isValidHex( error ) ) return error;
|
|
//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 );
|
|
testnet_generator.calcMempool();
|
|
return txhex;
|
|
} catch ( e ) {
|
|
return 'unknown error';
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
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 = async () => {
|
|
var txhex_or_error = await sendMoney();
|
|
if ( !testnet_generator.isValidHex( txhex_or_error ) ) alert( txhex_or_error );
|
|
}
|
|
var privkey = params.hasOwnProperty( "privkey" ) ? params.privkey : undefined;
|
|
showAddress( privkey );
|
|
$( '.generate_block' ).onclick = () => {testnet_generator.makeBlock();}
|
|
if ( !privkey ) $( '.generate_block' ).disabled = false;
|
|
if ( params.hasOwnProperty( "pubkey" ) ) {
|
|
testnet_generator.processExistingBlocks();
|
|
$( '.generate_block' ).remove();
|
|
$( '.options' ).remove();
|
|
$( '.wallet' ).style.display = "none";
|
|
$( '.txs_in_mempool_label' ).style.display = "none";
|
|
}
|
|
$( '.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 );
|
|
}
|
|
if ( $( '.auto_generate' ) ) $( '.auto_generate' ).onchange = () => {
|
|
autogen_value = $( '.auto_generate' ).value;
|
|
if ( autogen_value.includes( "---" ) ) $( '.auto_go' ).disabled = true;
|
|
else $( '.auto_go' ).removeAttribute( "disabled" );
|
|
}
|
|
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";
|
|
}
|
|
}
|
|
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 );
|
|
}
|
|
}
|
|
}
|
|
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();
|
|
var listenFunction = async socket => {
|
|
var subId = super_nostr.bytesToHex( crypto.getRandomValues( new Uint8Array( 8 ) ) );
|
|
var filter = {}
|
|
filter.kinds = [ 4 ];
|
|
filter[ "#p" ] = [ pubkey ];
|
|
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;
|
|
var relay = testnet_generator.relays[ 0 ];
|
|
var recipient = event.pubkey;
|
|
var content = await super_nostr.alt_decrypt( privkey, recipient, event.content );
|
|
console.log( content );
|
|
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,
|
|
});
|
|
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,
|
|
});
|
|
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 === "rawtx" ) {
|
|
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: "rawtx_reply",
|
|
msg_value: txhex,
|
|
});
|
|
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 === "genblock" ) {
|
|
var txid_or_error = await testnet_generator.makeBlock();
|
|
var blockheight = Object.keys( testnet_generator.blocks ).length - 1;
|
|
var blockhash = testnet_generator.intToHex( blockheight ).padStart( 64, "0" );
|
|
var message = JSON.stringify({
|
|
msg_type: "genblock_reply",
|
|
msg_value: blockhash,
|
|
});
|
|
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 === "send" ) {
|
|
var { destino, amnt, feerate } = json.msg_value;
|
|
var txhex_or_error = await sendMoney( destino, amnt, feerate );
|
|
var message = JSON.stringify({
|
|
msg_type: "send_reply",
|
|
msg_value: txhex_or_error,
|
|
});
|
|
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 );
|
|
var event = await super_nostr.prepEvent( privkey, "", 54091, [ [ "e", params.say_when_connected ] ] );
|
|
super_nostr.sendEvent( event, testnet_generator.relays[ 0 ] );
|
|
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 ] );
|
|
}
|
|
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>
|
|
var runScript = async () => {
|
|
var keypairs = [testnet_generator.getAddy(), testnet_generator.getAddy(), testnet_generator.getAddy()];
|
|
var script = [
|
|
"OP_3",
|
|
"OP_CHECKSEQUENCEVERIFY",
|
|
"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 );
|
|
var txhex = await sendMoney();
|
|
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 )],
|
|
});
|
|
txdata.vin[ 0 ].sequence = 3;
|
|
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 );
|
|
}
|
|
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( "," ) );
|
|
}
|
|
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();
|
|
}
|
|
</script>
|
|
<div class="black-bg hidden" onclick="modalVanish();"></div>
|
|
<div class="modal hidden"></div>
|
|
</body>
|
|
</html>
|