Skip to content

Instantly share code, notes, and snippets.

@supertestnet
Last active December 10, 2024 12:47
Show Gist options
  • Save supertestnet/ad0ce4f92d1eecfc607b0b46afed7228 to your computer and use it in GitHub Desktop.
Save supertestnet/ad0ce4f92d1eecfc607b0b46afed7228 to your computer and use it in GitHub Desktop.
CTV Hash Generator in Javascript
//dependencies:
//https://unpkg.com/@cmdcode/[email protected]
var 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;
});
}
var hexToBytes = hex => Uint8Array.from( hex.match( /.{1,2}/g ).map( byte => parseInt( byte, 16 ) ) );
var reverseHexString = s => s.match(/[a-fA-F0-9]{2}/g).reverse().join('');
var getStandardTemplateHash = async ( rawtx, inputnum ) => {
var tx = tapscript.Tx.decode( rawtx );
var ctv_hash_preimage = "";
//add the tx version number to the ctv preimage
var version = tx.version.toString( 16 );
version = version.padStart( 8, "0" );
version = reverseHexString( version );
ctv_hash_preimage = ctv_hash_preimage + version;
//add the tx locktime to the ctv preimage
var locktime = tx.locktime.toString( 16 );
locktime = locktime.padStart( 8, "0" );
var locktime = reverseHexString( locktime );
ctv_hash_preimage = ctv_hash_preimage + locktime;
//go through each input and concatenate the scriptSigs, if any; then hash them and add the hash to the ctv preimage
var vin = tx.vin || [];
var scriptsigs = [];
if ( vin.length ) {
vin.forEach( ( input, index ) => {
var length = ( input.scriptSig.length / 2 ).toString( 16 );
if ( length.length % 2 ) length = "0" + length;
length = reverseHexString( length );
//I'm not sure why I have to prefix fd to a 2-byte scriptsig length, but that made it match the test cases I was testing against,
//which are available here: https://github.com/jamesob/simple-ctv-vault/blob/master/ctvhash-test-vectors.json
if ( length.length === 4 ) length = "fd" + length;
//I'm also not sure why ff turns into fdff00, but that's what it does in the test vectors
if ( length === "ff" ) length = "fdff00";
scriptsigs.push( length + input.scriptSig );
});
}
var scriptsigs_has_nonempty_elements = false;
scriptsigs.every( scriptsig => {
if ( scriptsig !== "00" ) {
scriptsigs_has_nonempty_elements = true;
return;
}
return true;
});
if ( !scriptsigs_has_nonempty_elements ) scriptsigs = [];
var scriptsigs_bytes = [];
scriptsigs.forEach( scriptsig => scriptsigs_bytes.push( hexToBytes( scriptsig ) ) );
var all_scriptsigs_together = [];
scriptsigs_bytes.forEach( scriptsig => all_scriptsigs_together = [ ...all_scriptsigs_together, ...scriptsig ] );
scriptsigs = Uint8Array.from( all_scriptsigs_together );
if ( scriptsigs.length ) var hash = await sha256( scriptsigs );
else var hash = "";
ctv_hash_preimage = ctv_hash_preimage + hash;
//add the number of inputs to the ctv preimage
var num_of_inputs = reverseHexString( tx.vin.length.toString( 16 ).padStart( 8, "0" ) );
ctv_hash_preimage = ctv_hash_preimage + num_of_inputs;
//concatenate the sequence numbers of every input, hash them, and add the hash to the ctv preimage
var sequences = "";
tx.vin.forEach( input => sequences = sequences + reverseHexString( input.sequence ) );
sequences_hash = await sha256( hexToBytes( sequences ) );
ctv_hash_preimage = ctv_hash_preimage + sequences_hash;
//add the number of outputs to the ctv preimage
var num_of_outputs = reverseHexString( tx.vout.length.toString( 16 ).padStart( 8, "0" ) );
ctv_hash_preimage = ctv_hash_preimage + num_of_outputs;
//concatenate the outputs, hash them, and add the hash to the ctv preimage
var outputs_preimage = "";
tx.vout.forEach( output => {
var value = output.value.toString( 16 );
value = value.padStart( 16, "0" );
value = reverseHexString( value );
outputs_preimage = outputs_preimage + value;
var length = ( output.scriptPubKey.length / 2 ).toString( 16 );
if ( length % 2 ) length = "0" + length;
outputs_preimage = outputs_preimage + length;
outputs_preimage = outputs_preimage + output.scriptPubKey;
});
var outputs_hash = await sha256( hexToBytes( outputs_preimage ) );
ctv_hash_preimage = ctv_hash_preimage + outputs_hash;
//add the index number of the input being spent to the ctv preimage
var inputnum = reverseHexString( inputnum.toString( 16 ).padStart( 8, "0" ) );
ctv_hash_preimage = ctv_hash_preimage + inputnum;
//hash the ctv preimage
var ctv_hash = await sha256( hexToBytes( ctv_hash_preimage ) );
//return the ctv hash
return ctv_hash;
}
@BitcoinAcademyClub
Copy link

🙌🏻 good work

@Gudnessuche
Copy link

Javascript Maxi🙌🙌🙌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment