kaspa-txscript 2.1.0

Kaspa txscript engine library
Documentation
use kaspa_consensus_core::{
    hashing::sighash::SigHashReusedValuesUnsync,
    tx::{PopulatedTransaction, Transaction, TransactionId, TransactionInput, TransactionOutpoint, UtxoEntry},
};
use kaspa_txscript_errors::TxScriptError;

use crate::{
    EngineCtx, EngineFlags, SigCacheKey, TxScriptEngine,
    caches::Cache,
    hex,
    opcodes::codes::OpZkPrecompile,
    pay_to_script_hash_script, pay_to_script_hash_signature_script_with_flags,
    script_builder::{ScriptBuilder, ScriptBuilderResult},
    zk_precompiles::tags::ZkTag,
};

pub fn zk_test_flags() -> EngineFlags {
    Default::default()
}

pub fn build_zk_script(elements: &[&[u8]]) -> ScriptBuilderResult<Vec<u8>> {
    let mut builder = ScriptBuilder::with_flags(Default::default());
    for element in elements {
        builder.add_data(element)?;
    }
    builder.add_op(OpZkPrecompile)?;
    Ok(builder.drain())
}

pub fn execute_zk_script(
    script: &[u8],
    sig_cache: &Cache<SigCacheKey, bool>,
    reused_values: &SigHashReusedValuesUnsync,
) -> Result<(), TxScriptError> {
    execute_zk_script_with_flags(script, sig_cache, reused_values, Default::default())
}

pub fn execute_zk_script_with_flags(
    script: &[u8],
    sig_cache: &Cache<SigCacheKey, bool>,
    reused_values: &SigHashReusedValuesUnsync,
    flags: EngineFlags,
) -> Result<(), TxScriptError> {
    let mut vm =
        TxScriptEngine::<PopulatedTransaction, SigHashReusedValuesUnsync>::from_script(script, reused_values, sig_cache, flags);
    vm.execute()
}

pub fn execute_p2sh_script(
    signature_script: Vec<u8>,
    redeem_script: &[u8],
    sig_cache: &Cache<SigCacheKey, bool>,
    reused_values: &SigHashReusedValuesUnsync,
) -> Result<(), TxScriptError> {
    let outpoint = TransactionOutpoint::new(TransactionId::default(), 0);
    let input = TransactionInput::new_with_compute_budget(outpoint, signature_script, 0, 0);
    let utxo = UtxoEntry::new(20_000, pay_to_script_hash_script(redeem_script), 0, false, None);
    let tx = Transaction::new(1, vec![input], vec![], 0, Default::default(), 0, vec![]);
    let populated_tx = PopulatedTransaction::new(&tx, vec![utxo.clone()]);
    let mut vm = TxScriptEngine::from_transaction_input(
        &populated_tx,
        &populated_tx.tx.inputs[0],
        0,
        &utxo,
        EngineCtx::new(sig_cache).with_reused(reused_values),
        zk_test_flags(),
    );

    vm.execute()
}

#[allow(clippy::type_complexity)]
pub fn load_stark_fields() -> (Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>) {
    let stark_seal_hex = include_str!("data/succinct.seal.hex");
    let stark_control_id_hex = include_str!("data/succinct.control_id.hex");
    let stark_claim_hex = include_str!("data/succinct.claim.hex");
    let stark_hashfn_hex = include_str!("data/succinct.hashfn.hex");
    let stark_control_index_hex = include_str!("data/succinct.control_index.hex");
    let stark_control_digests_hex = include_str!("data/succinct.control_digests.hex");
    let stark_image_id_hex = include_str!("data/succinct.image.hex");
    let stark_journal_hex = include_str!("data/succinct.journal.hex");

    let stark_control_id_bytes = hex::decode(stark_control_id_hex).expect("Failed to decode hex STARK control id");
    let stark_seal_bytes = hex::decode(stark_seal_hex).expect("Failed to decode hex STARK seal");
    let stark_claim_bytes = hex::decode(stark_claim_hex).expect("Failed to decode hex STARK claim");
    let stark_hashfn_bytes = hex::decode(stark_hashfn_hex).expect("Failed to decode hex STARK hashfn");
    let stark_control_index_bytes = hex::decode(stark_control_index_hex).expect("Failed to decode hex STARK control index");
    let stark_control_digests_bytes = hex::decode(stark_control_digests_hex).expect("Failed to decode hex STARK control digests");
    let stark_image_id_bytes = hex::decode(stark_image_id_hex).expect("Failed to decode hex image id");
    let stark_journal_bytes = hex::decode(stark_journal_hex).expect("Failed to decode hex journal");

    (
        stark_control_id_bytes,
        stark_seal_bytes,
        stark_claim_bytes,
        stark_hashfn_bytes,
        stark_control_index_bytes,
        stark_control_digests_bytes,
        stark_journal_bytes,
        stark_image_id_bytes,
    )
}

#[derive(Clone, Debug)]
pub struct R0Fields {
    pub claim: Vec<u8>,
    pub control_index: Vec<u8>,
    pub control_digests: Vec<u8>,
    pub seal: Vec<u8>,
    pub journal: Vec<u8>,
    pub image_id: Vec<u8>,
    pub control_id: Vec<u8>,
    pub hashfn: Vec<u8>,
}

impl R0Fields {
    pub fn from_fixture() -> Self {
        let (control_id, seal, claim, hashfn, control_index, control_digests, journal, image_id) = load_stark_fields();
        Self { claim, control_index, control_digests, seal, journal, image_id, control_id, hashfn }
    }

    pub fn script(&self) -> Vec<u8> {
        build_zk_script(&[
            &self.claim,
            &self.control_index,
            &self.control_digests,
            &self.seal,
            &self.journal,
            &self.image_id,
            &self.control_id,
            &self.hashfn,
            &[ZkTag::R0Succinct as u8],
        ])
        .unwrap()
    }

    pub fn p2sh_redeem_script(&self) -> Vec<u8> {
        let mut builder = ScriptBuilder::with_flags(zk_test_flags());
        builder
            .add_data(&self.image_id)
            .unwrap()
            .add_data(&self.control_id)
            .unwrap()
            .add_data(&self.hashfn)
            .unwrap()
            .add_data(&[ZkTag::R0Succinct as u8])
            .unwrap()
            .add_op(OpZkPrecompile)
            .unwrap()
            .drain()
    }

    pub fn p2sh_signature_script(&self, redeem_script: Vec<u8>) -> Vec<u8> {
        let mut signature = ScriptBuilder::with_flags(zk_test_flags());
        signature
            .add_data(&self.claim)
            .unwrap()
            .add_data(&self.control_index)
            .unwrap()
            .add_data(&self.control_digests)
            .unwrap()
            .add_data(&self.seal)
            .unwrap()
            .add_data(&self.journal)
            .unwrap();

        pay_to_script_hash_signature_script_with_flags(redeem_script, signature.drain(), zk_test_flags()).unwrap()
    }

    pub fn p2sh_scripts(&self) -> (Vec<u8>, Vec<u8>) {
        let redeem_script = self.p2sh_redeem_script();
        let signature_script = self.p2sh_signature_script(redeem_script.clone());
        (signature_script, redeem_script)
    }
}

pub fn build_stark_script(break_control_id: bool) -> Vec<u8> {
    let mut fields = R0Fields::from_fixture();
    if break_control_id {
        fields.control_id[0] ^= 0xFF;
    }
    fields.script()
}

pub fn build_groth_script() -> Vec<u8> {
    let (unprepared_compressed_vk, groth16_proof_bytes, inputs) = load_groth_fields();
    build_groth_script_from_fields(&unprepared_compressed_vk, &groth16_proof_bytes, &inputs)
}

#[derive(Clone, Debug)]
pub struct Groth16Fields {
    pub vk: Vec<u8>,
    pub proof: Vec<u8>,
    pub inputs: Vec<Vec<u8>>,
}

impl Groth16Fields {
    pub fn from_fixture() -> Self {
        let (vk, proof, inputs) = load_groth_fields();
        Self { vk, proof, inputs }
    }

    pub fn script(&self) -> Vec<u8> {
        build_groth_script_from_fields(&self.vk, &self.proof, &self.inputs)
    }

    pub fn p2sh_redeem_script(&self) -> Vec<u8> {
        ScriptBuilder::with_flags(zk_test_flags())
            .add_data(&self.vk)
            .unwrap()
            .add_data(&[ZkTag::Groth16 as u8])
            .unwrap()
            .add_op(OpZkPrecompile)
            .unwrap()
            .drain()
    }

    pub fn p2sh_signature_script(&self, redeem_script: Vec<u8>) -> Vec<u8> {
        let mut signature = ScriptBuilder::with_flags(zk_test_flags());
        for input in self.inputs.iter().rev() {
            signature.add_data(input).unwrap();
        }
        signature.add_i64(self.inputs.len() as i64).unwrap().add_data(&self.proof).unwrap();

        pay_to_script_hash_signature_script_with_flags(redeem_script, signature.drain(), zk_test_flags()).unwrap()
    }

    pub fn p2sh_scripts(&self) -> (Vec<u8>, Vec<u8>) {
        let redeem_script = self.p2sh_redeem_script();
        let signature_script = self.p2sh_signature_script(redeem_script.clone());
        (signature_script, redeem_script)
    }
}

pub fn build_groth_script_from_fields(unprepared_compressed_vk: &[u8], groth16_proof_bytes: &[u8], inputs: &[Vec<u8>]) -> Vec<u8> {
    let groth16_tag = ZkTag::Groth16 as u8;
    let mut builder = ScriptBuilder::with_flags(Default::default());
    for input in inputs.iter().rev() {
        builder.add_data(input).unwrap();
    }
    builder
        .add_i64(inputs.len() as i64)
        .unwrap()
        .add_data(groth16_proof_bytes)
        .unwrap()
        .add_data(unprepared_compressed_vk)
        .unwrap()
        .add_data(&[groth16_tag])
        .unwrap()
        .add_op(OpZkPrecompile)
        .unwrap()
        .drain()
}

pub fn load_groth_fields() -> (Vec<u8>, Vec<u8>, Vec<Vec<u8>>) {
    let unprepared_compressed_vk = hex::decode("e2f26dbea299f5223b646cb1fb33eadb059d9407559d7441dfd902e3a79a4d2dabb73dc17fbc13021e2471e0c08bd67d8401f52b73d6d07483794cad4778180e0c06f33bbc4c79a9cadef253a68084d382f17788f885c9afd176f7cb2f036789edf692d95cbdde46ddda5ef7d422436779445c5e66006a42761e1f12efde0018c212f3aeb785e49712e7a9353349aaf1255dfb31b7bf60723a480d9293938e1933033e7fea1f40604eaacf699d4be9aacc577054a0db22d9129a1728ff85a01a1c3af829b62bf4914c0bcf2c81a4bd577190eff5f194ee9bac95faefd53cb0030600000000000000e43bdc655d0f9d730535554d9caa611ddd152c081a06a932a8e1d5dc259aac123f42a188f683d869873ccc4c119442e57b056e03e2fa92f2028c97bc20b9078747c30f85444697fdf436e348711c011115963f855197243e4b39e6cbe236ca8ba7f2042e11f9255afbb6c6e2c3accb88e401f2aac21c097c92b3fbdb99f98a9b0dcd6c075ada6ed0ddfece1d4a2d005f61a7d5df0b75c18a5b2374d64e495fab93d4c4b1200394d5253cce2f25a59b862ee8e4cd43686603faa09d5d0d3c1c8f").unwrap();
    let groth16_proof_bytes = hex::decode("570253c0c483a1b16460118e63c155f3684e784ae7d97e8fc3f544128b37fe15075eab5ac31150c8a44253d8525971241bbd7227fcefbae2db4ae71675c56a2e0eb9235136b15ab72f16e707832f3d6ae5b0ba7cca53ae17cb52b3201919eb9d908c16297abd90aa7e00267bc21a9a78116e717d4d76edd44e21cca17e3d592d").unwrap();
    let input0 = hex::decode("a54dc85ac99f851c92d7c96d7318af4100000000000000000000000000000000").unwrap();
    let input1 = hex::decode("dbe7c0194edfcc37eb4d422a998c1f5600000000000000000000000000000000").unwrap();
    let input2 = hex::decode("a95ac0b37bfedcd8136e6c1143086bf500000000000000000000000000000000").unwrap();
    let input3 = hex::decode("d223ffcb21c6ffcb7c8f60392ca49dde00000000000000000000000000000000").unwrap();
    let input4 = hex::decode("c07a65145c3cb48b6101962ea607a4dd93c753bb26975cb47feb00d3666e4404").unwrap();

    (unprepared_compressed_vk, groth16_proof_bytes, vec![input0, input1, input2, input3, input4])
}