#[path = "../src/artifact.rs"]
mod artifact;
use neptune_consensus::proof_abstractions::SecretWitness;
use neptune_consensus::transaction::primitive_witness::PrimitiveWitness;
use neptune_consensus::transaction::transaction_kernel::{
TransactionKernelField, TransactionKernelProxy,
};
use neptune_consensus::transaction::validity::{
collect_lock_scripts::CollectLockScriptsWitness,
collect_type_scripts::CollectTypeScriptsWitness, kernel_to_outputs::KernelToOutputsWitness,
proof_collection::ProofCollection, removal_records_integrity::RemovalRecordsIntegrityWitness,
single_proof::SingleProofWitness,
};
use neptune_consensus::type_scripts::native_currency::NativeCurrencyWitness;
use neptune_consensus::type_scripts::native_currency_amount::NativeCurrencyAmount;
use neptune_mutator_set::mutator_set_accumulator::MutatorSetAccumulator;
use neptune_primitives::{mast_hash::MastHash, network::Network, timestamp::Timestamp};
use tasm_lib::triton_vm::prelude::*;
fn primitive(lock: Option<Digest>) -> PrimitiveWitness {
let accumulator = if lock.is_some() {
neptune_consensus::block::Block::genesis(Network::Testnet(1))
.mutator_set_accumulator_after()
.expect("canonical genesis accumulator")
} else {
MutatorSetAccumulator::default()
};
let timestamp = if lock.is_some() {
Timestamp::now()
} else {
Timestamp::millis(1_000_000)
};
let outputs: Vec<_> = lock
.map(|hash| neptune_consensus::transaction::utxo::Utxo::new(hash, vec![]))
.into_iter()
.collect();
let senders = vec![Digest::new([bfe!(101); 5]); outputs.len()];
let receivers = vec![Digest::new([bfe!(201); 5]); outputs.len()];
let additions = outputs
.iter()
.zip(&senders)
.zip(&receivers)
.map(|((utxo, sender), receiver)| {
neptune_mutator_set::commit(Tip5::hash(utxo), *sender, *receiver)
})
.collect();
let kernel = TransactionKernelProxy {
inputs: vec![],
outputs: additions,
announcements: vec![],
fee: NativeCurrencyAmount::coins(0),
coinbase: None,
timestamp,
mutator_set_hash: accumulator.hash(),
merge_bit: false,
}
.into_kernel();
PrimitiveWitness::generate_primitive_witness(
vec![],
outputs,
senders,
receivers,
kernel,
accumulator,
)
}
fn execute_or_prove(
label: &str,
witness: &dyn SecretWitness,
directory: Option<&std::path::Path>,
) -> Option<Proof> {
let claim = witness.claim();
let (trace, output) = VM::trace_execution(
witness.program(),
witness.standard_input(),
witness.nondeterminism(),
)
.expect("canonical transaction relation must execute");
assert_eq!(output, claim.output);
eprintln!(
"{label}: cycles={}, padded_height={}",
trace.processor_trace.nrows(),
trace.padded_height()
);
let cycles = trace.processor_trace.nrows() as u64;
let directory = directory?;
let started = std::time::Instant::now();
let proof = Stark::default()
.prove(&claim, &trace)
.expect("default-security proof");
drop(trace);
Stark::default()
.verify(&claim, &proof)
.expect("fresh native verification");
eprintln!(
"{label}: proved and verified in {:.3}s",
started.elapsed().as_secs_f64()
);
std::fs::write(
directory.join(format!("{label}.json")),
serde_json::to_vec(&serde_json::json!({"claim":claim,"proof":proof})).unwrap(),
)
.unwrap();
artifact::write(
&directory.join(label),
&claim,
&proof,
cycles,
started.elapsed().as_millis() as u64,
);
Some(proof)
}
fn main() {
let mut args: Vec<_> = std::env::args().skip(1).collect();
let deploy_lock = if let Some(position) = args.iter().position(|arg| arg == "--deploy-lock") {
assert!(
position + 1 < args.len(),
"--deploy-lock requires an assembly file"
);
let path = args.remove(position + 1);
args.remove(position);
Some(
Program::from_code(&std::fs::read_to_string(path).unwrap())
.unwrap()
.hash(),
)
} else {
None
};
let directory = match args.as_slice() {
[] => None,
[flag, directory] if flag == "--prove" => {
let directory = std::path::PathBuf::from(directory);
std::fs::create_dir(&directory).expect("new output directory required");
Some(directory)
}
_ => panic!("usage: single_proof [--prove NEW_DIRECTORY]"),
};
let primitive = primitive(deploy_lock);
assert!(primitive.lock_scripts_and_witnesses.is_empty());
assert_eq!(
primitive.type_scripts_and_witnesses.len(),
1,
"native currency must be checked even for an empty transaction"
);
let native = NativeCurrencyWitness {
salted_input_utxos: primitive.input_utxos.clone(),
salted_output_utxos: primitive.output_utxos.clone(),
kernel: primitive.kernel.clone(),
};
let witnesses: Vec<(&str, Box<dyn SecretWitness>)> = vec![
(
"removal-records",
Box::new(RemovalRecordsIntegrityWitness::from(&primitive)),
),
(
"collect-lock-scripts",
Box::new(CollectLockScriptsWitness::from(&primitive)),
),
(
"kernel-to-outputs",
Box::new(KernelToOutputsWitness::from(&primitive)),
),
(
"collect-type-scripts",
Box::new(CollectTypeScriptsWitness::from(&primitive)),
),
("native-currency", Box::new(native)),
];
let mut proofs = Vec::new();
for (label, witness) in witnesses {
if let Some(proof) = execute_or_prove(label, witness.as_ref(), directory.as_deref()) {
proofs.push(proof);
}
}
let Some(directory) = directory else {
return;
};
let mut proofs = proofs.into_iter();
let collection = ProofCollection {
removal_records_integrity: proofs.next().unwrap().into(),
collect_lock_scripts: proofs.next().unwrap().into(),
lock_scripts_halt: vec![],
kernel_to_outputs: proofs.next().unwrap().into(),
collect_type_scripts: proofs.next().unwrap().into(),
type_scripts_halt: vec![proofs.next().unwrap().into()],
lock_script_hashes: vec![],
type_script_hashes: primitive
.type_scripts_and_witnesses
.iter()
.map(|witness| witness.program.hash())
.collect(),
kernel_mast_hash: primitive.kernel.mast_hash(),
salted_inputs_hash: Tip5::hash(&primitive.input_utxos),
salted_outputs_hash: Tip5::hash(&primitive.output_utxos),
merge_bit_mast_path: primitive.kernel.mast_path(TransactionKernelField::MergeBit),
};
std::fs::write(
directory.join("proof-collection.json"),
serde_json::to_vec(&collection).unwrap(),
)
.unwrap();
let single = SingleProofWitness::from_collection(collection);
assert!(single.claim().output.is_empty());
assert_eq!(
single.claim().input,
primitive.kernel.mast_hash().reversed().values()
);
assert_eq!(single.claim().version, 5);
let proof = execute_or_prove("single-proof", &single, Some(&directory)).unwrap();
if deploy_lock.is_some() {
let transaction = neptune_consensus::transaction::Transaction {
kernel: primitive.kernel.clone(),
proof: neptune_consensus::transaction::transaction_proof::TransactionProof::SingleProof(
proof.into(),
),
};
let rpc =
neptune_rpc_api::model::wallet::transaction::RpcTransaction::try_from(transaction)
.unwrap();
let utxo = neptune_rpc_api::model::wallet::transaction::RpcUtxo::from(
primitive.output_utxos.utxos[0].clone(),
);
let intent = serde_json::json!({"schema_version":1,"network":"local-testnet1","expected_kernel":primitive.kernel.mast_hash().values().map(|v|v.value()),"transaction":rpc,"outputs":[{"utxo":utxo,"sender_randomness":[101,101,101,101,101],"receiver_digest":[201,201,201,201,201],"compiled_lock":true}]});
std::fs::write(
directory.join("deployment-intent.json"),
serde_json::to_vec_pretty(&intent).unwrap(),
)
.unwrap();
}
println!(
"{}",
serde_json::json!({"protocol":"Neptune0.15.1/HardforkGamma", "kernel_digest":primitive.kernel.mast_hash().values().map(|word|word.value()), "program_digest":single.claim().program_digest.values().map(|word|word.value())})
);
}