1use crate::tx::{EnchantedTx, Tx, extract_and_verify_spell};
2use charms_data::{App, B32, Charms, Data, Transaction, TxId, UtxoId, check};
3use serde::{Deserialize, Serialize};
4use sha2::{Digest, Sha256};
5use std::collections::{BTreeMap, BTreeSet};
6
7pub use charms_app_runner::{AppProverInput, AppProverOutput};
8
9pub mod ark;
10pub mod bitcoin_tx;
11pub mod cardano_tx;
12pub mod tx;
13
14pub const APP_VK: [u32; 8] = [
15 379943684, 1320425212, 2011087664, 382625374, 62801581, 1553560260, 1934929111, 166204531,
16];
17
18pub const MOCK_SPELL_VK: &str = "7c38e8639a2eac0074cee920982b92376513e8940f4a7ca6859f17a728af5b0e";
19
20pub const V0_SPELL_VK: &str = "0x00e9398ac819e6dd281f81db3ada3fe5159c3cc40222b5ddb0e7584ed2327c5d";
22pub const V1_SPELL_VK: &str = "0x009f38f590ebca4c08c1e97b4064f39e4cd336eea4069669c5f5170a38a1ff97";
24pub const V2_SPELL_VK: &str = "0x00bd312b6026dbe4a2c16da1e8118d4fea31587a4b572b63155252d2daf69280";
26pub const V3_SPELL_VK: &str = "0x0034872b5af38c95fe82fada696b09a448f7ab0928273b7ac8c58ba29db774b9";
28pub const V4_SPELL_VK: &str = "0x00c707a155bf8dc18dc41db2994c214e93e906a3e97b4581db4345b3edd837c5";
30pub const V5_SPELL_VK: &str = "0x00e98665c417bd2e6e81c449af63b26ed5ad5c400ef55811b592450bf62c67cd";
32
33pub const V0: u32 = 0u32;
35pub const V1: u32 = 1u32;
37pub const V2: u32 = 2u32;
39pub const V3: u32 = 3u32;
41pub const V4: u32 = 4u32;
43pub const V5: u32 = 5u32;
45pub const V6: u32 = 6u32;
47
48pub const CURRENT_VERSION: u32 = V6;
50
51pub type NormalizedCharms = BTreeMap<u32, Data>;
54
55#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
57pub struct NormalizedTransaction {
58 #[serde(skip_serializing_if = "Option::is_none")]
61 pub ins: Option<Vec<UtxoId>>,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub refs: Option<Vec<UtxoId>>,
66
67 pub outs: Vec<NormalizedCharms>,
73
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub beamed_outs: Option<BTreeMap<u32, B32>>,
77}
78
79impl NormalizedTransaction {
80 pub fn prev_txids(&self) -> Option<BTreeSet<&TxId>> {
83 self.ins
84 .as_ref()
85 .map(|ins| ins.iter().map(|utxo_id| &utxo_id.0).collect())
86 }
87}
88
89pub type Proof = Vec<u8>;
91
92#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
95pub struct NormalizedSpell {
96 pub version: u32,
98 pub tx: NormalizedTransaction,
100 pub app_public_inputs: BTreeMap<App, Data>,
102 #[serde(skip_serializing_if = "std::ops::Not::not", default)]
104 pub mock: bool,
105}
106
107pub fn utxo_id_hash(utxo_id: &UtxoId) -> B32 {
108 let hash = Sha256::digest(utxo_id.to_bytes());
109 B32(hash.into())
110}
111
112#[tracing::instrument(level = "debug", skip(prev_txs, spell_vk))]
114pub fn prev_spells(
115 prev_txs: &Vec<Tx>,
116 spell_vk: &str,
117 mock: bool,
118) -> BTreeMap<TxId, (Option<NormalizedSpell>, usize)> {
119 prev_txs
120 .iter()
121 .map(|tx| {
122 let tx_id = tx.tx_id();
123 (
124 tx_id,
125 (
126 extract_and_verify_spell(spell_vk, tx, mock)
127 .map_err(|e| {
128 tracing::info!("no correct spell in tx {}: {}", tx_id, e);
129 })
130 .ok(),
131 tx.tx_outs_len(),
132 ),
133 )
134 })
135 .collect()
136}
137
138#[tracing::instrument(level = "debug", skip(spell, prev_spells))]
140pub fn well_formed(
141 spell: &NormalizedSpell,
142 prev_spells: &BTreeMap<TxId, (Option<NormalizedSpell>, usize)>,
143 tx_ins_beamed_source_utxos: &BTreeMap<UtxoId, UtxoId>,
144) -> bool {
145 check!(spell.version == CURRENT_VERSION);
146 let directly_created_by_prev_txns = |utxo_id: &UtxoId| -> bool {
147 let tx_id = utxo_id.0;
148 prev_spells
149 .get(&tx_id)
150 .is_some_and(|(n_spell_opt, num_tx_outs)| {
151 let utxo_index = utxo_id.1;
152
153 let is_beamed_out = n_spell_opt
154 .as_ref()
155 .and_then(|n_spell| n_spell.tx.beamed_outs.as_ref())
156 .and_then(|beamed_outs| beamed_outs.get(&utxo_index))
157 .is_some();
158
159 utxo_index <= *num_tx_outs as u32 && !is_beamed_out
160 })
161 };
162 check!({
163 spell.tx.outs.iter().all(|n_charm| {
164 n_charm
165 .keys()
166 .all(|&i| i < spell.app_public_inputs.len() as u32)
167 })
168 });
169 let Some(tx_ins) = &spell.tx.ins else {
172 eprintln!("no tx.ins");
173 return false;
174 };
175 check!(
176 tx_ins.iter().all(directly_created_by_prev_txns)
177 && spell
178 .tx
179 .refs
180 .iter()
181 .flatten()
182 .all(directly_created_by_prev_txns)
183 );
184 let beamed_source_utxos_point_to_placeholder_dest_utxos = tx_ins_beamed_source_utxos
185 .iter()
186 .all(|(tx_in_utxo_id, beaming_source_utxo_id)| {
187 let prev_txid = tx_in_utxo_id.0;
188 let prev_tx = prev_spells.get(&prev_txid);
189 let Some((prev_spell_opt, _tx_outs)) = prev_tx else {
190 return false;
192 };
193 check!(prev_spell_opt.is_none());
195
196 let beaming_txid = beaming_source_utxo_id.0;
197 let beaming_utxo_index = beaming_source_utxo_id.1;
198
199 prev_spells
200 .get(&beaming_txid)
201 .and_then(|(n_spell_opt, _tx_outs)| {
202 n_spell_opt.as_ref().and_then(|n_spell| {
203 n_spell
204 .tx
205 .beamed_outs
206 .as_ref()
207 .and_then(|beamed_outs| beamed_outs.get(&beaming_utxo_index))
208 })
209 })
210 .is_some_and(|dest_utxo_hash| dest_utxo_hash == &utxo_id_hash(tx_in_utxo_id))
211 });
212 check!(beamed_source_utxos_point_to_placeholder_dest_utxos);
213 true
214}
215
216pub fn apps(spell: &NormalizedSpell) -> Vec<App> {
218 spell.app_public_inputs.keys().cloned().collect()
219}
220
221pub fn to_tx(
223 spell: &NormalizedSpell,
224 prev_spells: &BTreeMap<TxId, (Option<NormalizedSpell>, usize)>,
225 tx_ins_beamed_source_utxos: &BTreeMap<UtxoId, UtxoId>,
226) -> Transaction {
227 let from_utxo_id = |utxo_id: &UtxoId| -> (UtxoId, Charms) {
228 let (prev_spell_opt, _) = &prev_spells[&utxo_id.0];
229 let charms = prev_spell_opt
230 .as_ref()
231 .and_then(|prev_spell| charms_in_utxo(prev_spell, utxo_id))
232 .or_else(|| {
233 tx_ins_beamed_source_utxos
234 .get(utxo_id)
235 .and_then(|beam_source_utxo_id| {
236 prev_spells[&beam_source_utxo_id.0]
237 .0
238 .as_ref()
239 .and_then(|prev_spell| charms_in_utxo(prev_spell, beam_source_utxo_id))
240 })
241 })
242 .unwrap_or_default();
243 (utxo_id.clone(), charms)
244 };
245
246 let from_normalized_charms =
247 |n_charms: &NormalizedCharms| -> Charms { charms(spell, n_charms) };
248
249 let Some(tx_ins) = &spell.tx.ins else {
250 unreachable!("self.tx.ins MUST be Some at this point");
251 };
252 Transaction {
253 ins: tx_ins.iter().map(from_utxo_id).collect(),
254 refs: spell.tx.refs.iter().flatten().map(from_utxo_id).collect(),
255 outs: spell.tx.outs.iter().map(from_normalized_charms).collect(),
256 }
257}
258
259fn charms_in_utxo(prev_spell: &NormalizedSpell, utxo_id: &UtxoId) -> Option<Charms> {
260 prev_spell
261 .tx
262 .outs
263 .get(utxo_id.1 as usize)
264 .map(|n_charms| charms(prev_spell, n_charms))
265}
266
267pub fn charms(spell: &NormalizedSpell, n_charms: &NormalizedCharms) -> Charms {
269 let apps = apps(spell);
270 n_charms
271 .iter()
272 .map(|(&i, data)| (apps[i as usize].clone(), data.clone()))
273 .collect()
274}
275
276#[derive(Clone, Debug, Serialize, Deserialize)]
277pub struct SpellProverInput {
278 pub self_spell_vk: String,
279 pub prev_txs: Vec<Tx>,
280 pub spell: NormalizedSpell,
281 pub tx_ins_beamed_source_utxos: BTreeMap<UtxoId, UtxoId>,
282 pub app_prover_output: Option<AppProverOutput>, }
285
286#[cfg(test)]
287mod test {
288 #[test]
289 fn dummy() {}
290}