1use crate::tx::extract_and_verify_spell;
2use bitcoin::hashes::Hash;
3use charms_data::{App, Charms, Data, Transaction, TxId, UtxoId};
4use serde::{Deserialize, Serialize};
5use std::collections::{BTreeMap, BTreeSet};
6
7pub mod tx;
8
9pub const V0: u32 = 0u32;
11pub const V0_SPELL_VK: &str = "0x00e9398ac819e6dd281f81db3ada3fe5159c3cc40222b5ddb0e7584ed2327c5d";
13pub const V1_SPELL_VK: &str = "0x009f38f590ebca4c08c1e97b4064f39e4cd336eea4069669c5f5170a38a1ff97";
15pub const V1: u32 = 1u32;
17pub const V2: u32 = 2u32;
19pub const CURRENT_VERSION: u32 = V2;
21
22pub type NormalizedCharms = BTreeMap<usize, Data>;
25
26#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
28pub struct NormalizedTransaction {
29 #[serde(skip_serializing_if = "Option::is_none")]
32 pub ins: Option<Vec<UtxoId>>,
33 pub refs: BTreeSet<UtxoId>,
35 pub outs: Vec<NormalizedCharms>,
41}
42
43impl NormalizedTransaction {
44 pub fn prev_txids(&self) -> Option<BTreeSet<&TxId>> {
46 self.ins
47 .as_ref()
48 .map(|ins| ins.iter().map(|utxo_id| &utxo_id.0).collect())
49 }
50}
51
52pub type Proof = Box<[u8]>;
54
55#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
58pub struct NormalizedSpell {
59 pub version: u32,
61 pub tx: NormalizedTransaction,
63 pub app_public_inputs: BTreeMap<App, Data>,
65}
66
67#[tracing::instrument(level = "debug", skip(prev_txs, spell_vk))]
69pub fn prev_spells(
70 prev_txs: &Vec<bitcoin::Transaction>,
71 spell_vk: &str,
72) -> BTreeMap<TxId, (Option<NormalizedSpell>, usize)> {
73 prev_txs
74 .iter()
75 .map(|tx| {
76 let tx_id = TxId(tx.compute_txid().to_byte_array());
77 (
78 tx_id,
79 (
80 extract_and_verify_spell(tx, spell_vk)
81 .map_err(|e| {
82 tracing::info!("no correct spell in tx {}: {}", tx_id, e);
83 })
84 .ok(),
85 tx.output.len(),
86 ),
87 )
88 })
89 .collect()
90}
91
92#[tracing::instrument(level = "debug", skip(spell, prev_spells))]
94pub fn well_formed(
95 spell: &NormalizedSpell,
96 prev_spells: &BTreeMap<TxId, (Option<NormalizedSpell>, usize)>,
97) -> bool {
98 if spell.version != CURRENT_VERSION {
99 eprintln!(
100 "spell version {} is not the current version {}",
101 spell.version, CURRENT_VERSION
102 );
103 return false;
104 }
105 let created_by_prev_spells = |utxo_id: &UtxoId| -> bool {
106 prev_spells
107 .get(&utxo_id.0)
108 .and_then(|(_, num_tx_outs)| Some(utxo_id.1 as usize <= *num_tx_outs))
109 == Some(true)
110 };
111 if !spell
112 .tx
113 .outs
114 .iter()
115 .all(|n_charm| n_charm.keys().all(|i| i < &spell.app_public_inputs.len()))
116 {
117 eprintln!("charm app index higher than app_public_inputs.len()");
118 return false;
119 }
120 let Some(tx_ins) = &spell.tx.ins else {
123 eprintln!("no tx.ins");
124 return false;
125 };
126 if !tx_ins.iter().all(created_by_prev_spells)
127 || !spell.tx.refs.iter().all(created_by_prev_spells)
128 {
129 eprintln!("input or reference UTXOs are not created by prev transactions");
130 return false;
131 }
132 true
133}
134
135pub fn apps(spell: &NormalizedSpell) -> Vec<App> {
137 spell.app_public_inputs.keys().cloned().collect()
138}
139
140pub fn to_tx(
142 spell: &NormalizedSpell,
143 prev_spells: &BTreeMap<TxId, (Option<NormalizedSpell>, usize)>,
144) -> Transaction {
145 let from_utxo_id = |utxo_id: &UtxoId| -> (UtxoId, Charms) {
146 let (prev_spell_opt, _) = &prev_spells[&utxo_id.0];
147 let charms = prev_spell_opt
148 .as_ref()
149 .and_then(|prev_spell| {
150 prev_spell
151 .tx
152 .outs
153 .get(utxo_id.1 as usize)
154 .map(|n_charms| charms(prev_spell, n_charms))
155 })
156 .unwrap_or_default();
157 (utxo_id.clone(), charms)
158 };
159
160 let from_normalized_charms =
161 |n_charms: &NormalizedCharms| -> Charms { charms(spell, n_charms) };
162
163 let Some(tx_ins) = &spell.tx.ins else {
164 unreachable!("self.tx.ins MUST be Some at this point");
165 };
166 Transaction {
167 ins: tx_ins.iter().map(from_utxo_id).collect(),
168 refs: spell.tx.refs.iter().map(from_utxo_id).collect(),
169 outs: spell.tx.outs.iter().map(from_normalized_charms).collect(),
170 }
171}
172
173pub fn charms(spell: &NormalizedSpell, n_charms: &NormalizedCharms) -> Charms {
175 let apps = apps(spell);
176 n_charms
177 .iter()
178 .map(|(&i, data)| (apps[i].clone(), data.clone()))
179 .collect()
180}
181
182#[derive(Clone, Debug, Serialize, Deserialize)]
183pub struct SpellProverInput {
184 pub self_spell_vk: String,
185 pub prev_txs: Vec<bitcoin::Transaction>,
186 pub spell: NormalizedSpell,
187 pub app_contract_proofs: BTreeSet<usize>, }
190
191#[cfg(test)]
192mod test {
193 #[test]
194 fn dummy() {}
195}