1use crate::{
2 CURRENT_VERSION, MOCK_SPELL_VK, NormalizedSpell, V0, V0_SPELL_VK, V1, V1_SPELL_VK, V2,
3 V2_SPELL_VK, V3, V3_SPELL_VK, V4, V4_SPELL_VK, V5, V5_SPELL_VK, V6, V6_SPELL_VK, V7,
4 V7_SPELL_VK, ark, bitcoin_tx::BitcoinTx, cardano_tx::CardanoTx,
5};
6use anyhow::{anyhow, bail};
7use charms_data::{NativeOutput, TxId, UtxoId, util};
8use enum_dispatch::enum_dispatch;
9use serde::{Deserialize, Serialize};
10use sp1_primitives::io::SP1PublicValues;
11use sp1_verifier::Groth16Verifier;
12use std::collections::BTreeMap;
13use strum::{AsRefStr, EnumDiscriminants, EnumString};
14
15#[enum_dispatch]
16pub trait EnchantedTx {
17 fn extract_and_verify_spell(
18 &self,
19 spell_vk: &str,
20 mock: bool,
21 ) -> anyhow::Result<NormalizedSpell>;
22 fn tx_outs_len(&self) -> usize;
23 fn tx_id(&self) -> TxId;
24 fn hex(&self) -> String;
25 fn spell_ins(&self) -> Vec<UtxoId>;
26 fn all_coin_outs(&self) -> Vec<NativeOutput>;
27 fn proven_final(&self) -> bool;
28}
29
30#[enum_dispatch(EnchantedTx)]
31#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, EnumDiscriminants)]
32#[serde(rename_all = "snake_case")]
33#[strum_discriminants(
34 name(Chain),
35 derive(AsRefStr, EnumString, Ord, PartialOrd, Serialize, Deserialize),
36 serde(rename_all = "snake_case"),
37 strum(serialize_all = "snake_case")
38)]
39pub enum Tx {
40 Bitcoin(BitcoinTx),
41 Cardano(CardanoTx),
42}
43
44impl TryFrom<&str> for Tx {
45 type Error = anyhow::Error;
46
47 fn try_from(hex: &str) -> Result<Self, Self::Error> {
48 if let Ok(b_tx) = BitcoinTx::from_hex(hex) {
49 Ok(Self::Bitcoin(b_tx))
50 } else if let Ok(c_tx) = CardanoTx::from_hex(hex) {
51 Ok(Self::Cardano(c_tx))
52 } else {
53 bail!("invalid hex")
54 }
55 }
56}
57
58impl Tx {
59 pub fn new(tx: impl Into<Tx>) -> Self {
60 tx.into()
61 }
62
63 pub fn hex(&self) -> String {
64 match self {
65 Tx::Bitcoin(tx) => tx.hex(),
66 Tx::Cardano(tx) => tx.hex(),
67 }
68 }
69}
70
71#[tracing::instrument(level = "debug", skip_all)]
74pub fn committed_normalized_spell(
75 spell_vk: &str,
76 tx: &Tx,
77 mock: bool,
78) -> anyhow::Result<NormalizedSpell> {
79 tx.extract_and_verify_spell(spell_vk, mock)
80}
81
82pub fn extended_normalized_spell(spell_vk: &str, tx: &Tx, mock: bool) -> NormalizedSpell {
85 match tx.extract_and_verify_spell(spell_vk, mock) {
86 Ok(mut spell) => {
87 spell.tx.coins = Some(tx.all_coin_outs());
88 spell
89 }
90 Err(_) => {
91 let mut spell = NormalizedSpell::default();
92 spell.tx.ins = Some(tx.spell_ins());
93 spell.tx.outs = vec![];
94 spell.tx.coins = Some(tx.all_coin_outs());
95 spell
96 }
97 }
98}
99
100pub fn spell_vk(spell_version: u32, spell_vk: &str, mock: bool) -> anyhow::Result<&str> {
101 if mock {
102 return Ok(MOCK_SPELL_VK);
103 }
104 match spell_version {
105 CURRENT_VERSION => Ok(spell_vk),
106 V7 => Ok(V7_SPELL_VK),
107 V6 => Ok(V6_SPELL_VK),
108 V5 => Ok(V5_SPELL_VK),
109 V4 => Ok(V4_SPELL_VK),
110 V3 => Ok(V3_SPELL_VK),
111 V2 => Ok(V2_SPELL_VK),
112 V1 => Ok(V1_SPELL_VK),
113 V0 => Ok(V0_SPELL_VK),
114 _ => bail!("unsupported spell version: {}", spell_version),
115 }
116}
117
118pub fn groth16_vk(spell_version: u32, mock: bool) -> anyhow::Result<&'static [u8]> {
119 if mock {
120 return Ok(MOCK_GROTH16_VK_BYTES);
121 }
122 match spell_version {
123 CURRENT_VERSION => Ok(CURRENT_GROTH16_VK_BYTES),
124 V7 => Ok(V7_GROTH16_VK_BYTES),
125 V6 => Ok(V6_GROTH16_VK_BYTES),
126 V5 => Ok(V5_GROTH16_VK_BYTES),
127 V4 => Ok(V4_GROTH16_VK_BYTES),
128 V3 => Ok(V3_GROTH16_VK_BYTES),
129 V2 => Ok(V2_GROTH16_VK_BYTES),
130 V1 => Ok(V1_GROTH16_VK_BYTES),
131 V0 => Ok(V0_GROTH16_VK_BYTES),
132 _ => bail!("unsupported spell version: {}", spell_version),
133 }
134}
135
136pub const MOCK_GROTH16_VK_BYTES: &'static [u8] = include_bytes!("../vk/mock/mock-groth16-vk.bin");
137
138pub const V0_GROTH16_VK_BYTES: &'static [u8] = include_bytes!("../vk/v0/groth16_vk.bin");
139pub const V1_GROTH16_VK_BYTES: &'static [u8] = include_bytes!("../vk/v1/groth16_vk.bin");
140pub const V2_GROTH16_VK_BYTES: &'static [u8] = V1_GROTH16_VK_BYTES;
141pub const V3_GROTH16_VK_BYTES: &'static [u8] = V1_GROTH16_VK_BYTES;
142pub const V4_GROTH16_VK_BYTES: &'static [u8] = include_bytes!("../vk/v4/groth16_vk.bin");
143pub const V5_GROTH16_VK_BYTES: &'static [u8] = V4_GROTH16_VK_BYTES;
144pub const V6_GROTH16_VK_BYTES: &'static [u8] = V4_GROTH16_VK_BYTES;
145pub const V7_GROTH16_VK_BYTES: &'static [u8] = V4_GROTH16_VK_BYTES;
146pub const V8_GROTH16_VK_BYTES: &'static [u8] = V4_GROTH16_VK_BYTES;
147pub const CURRENT_GROTH16_VK_BYTES: &'static [u8] = V8_GROTH16_VK_BYTES;
148
149pub fn to_serialized_pv<T: Serialize>(spell_version: u32, t: &T) -> Vec<u8> {
150 match spell_version {
151 CURRENT_VERSION | V7 | V6 | V5 | V4 | V3 | V2 | V1 => {
152 util::write(t).unwrap()
154 }
155 V0 => {
156 let mut pv = SP1PublicValues::new();
159 pv.write(t);
160 pv.to_vec()
161 }
162 _ => unreachable!(),
163 }
164}
165
166pub fn verify_snark_proof(
167 proof: &[u8],
168 public_inputs: &[u8],
169 vk_hash: &str,
170 spell_version: u32,
171 mock: bool,
172) -> anyhow::Result<()> {
173 let groth16_vk = groth16_vk(spell_version, mock)?;
174 match mock {
175 false => Groth16Verifier::verify(proof, public_inputs, vk_hash, groth16_vk)
176 .map_err(|e| anyhow!("could not verify spell proof: {}", e)),
177 true => ark::verify_groth16_proof(proof, public_inputs, groth16_vk),
178 }
179}
180
181pub fn by_txid(prev_txs: &[Tx]) -> BTreeMap<TxId, Tx> {
182 prev_txs
183 .iter()
184 .map(|prev_tx| (prev_tx.tx_id(), prev_tx.clone()))
185 .collect::<BTreeMap<_, _>>()
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use std::str::FromStr;
192
193 #[test]
194 fn chain_names() {
195 assert_eq!(Chain::Bitcoin.as_ref(), "bitcoin");
196 assert_eq!(Chain::Cardano.as_ref(), "cardano");
197 }
198
199 #[test]
200 fn chain_name_from_str() {
201 assert_eq!(Chain::from_str("bitcoin").unwrap(), Chain::Bitcoin);
202 assert_eq!(Chain::from_str("cardano").unwrap(), Chain::Cardano);
203 }
204
205 #[test]
206 fn chain_name_deserialize() {
207 assert_eq!(
208 serde_json::from_str::<Chain>(r#""bitcoin""#).unwrap(),
209 Chain::Bitcoin
210 );
211 assert_eq!(
212 serde_json::from_str::<Chain>(r#""cardano""#).unwrap(),
213 Chain::Cardano
214 );
215 }
216
217 #[test]
218 fn chain_name_serialize() {
219 assert_eq!(
220 serde_json::to_string(&Chain::Bitcoin).unwrap(),
221 r#""bitcoin""#
222 );
223 assert_eq!(
224 serde_json::to_string(&Chain::Cardano).unwrap(),
225 r#""cardano""#
226 );
227 }
228
229 #[test]
230 fn ser_to_json() {
231 let c_tx_hex = "84a400d901028182582011a2338987035057f6c36286cf5aadc02573059b2cde9790017eb4e148f0c67a0001828258390174f84e13070bb755eaa01cb717da8c7450daf379948e979f6de99d26ba89ff199fde572546b9a044eb129ad2edb184bd79cde63ab4b47aec1a01312d008258390184f1c3b1fff5241088acc4ce0aec81f45a71a70e35c94e30a70b7cdfeb0785cdec744029db6b4f344b1123497c9cabfeeb94af20fcfddfe01a33e578fd021a000299e90758201e8eb8575d879922d701c12daa7366cb71b6518a9500e083a966a8e66b56ed23a10081825820ea444825bbd5cc97b6c795437849fe55694b52e2f51485ac76ca2d9f991e83305840d59db4fa0b4bb233504f5e6826261a2e18b2e22cb3df4f631ab77d94d62e8df3200536271f3f3a625bc86919714972964f070f909f145b342f2889f58ccc210ff5a11902a2a1636d736765546f6b656f";
232
233 let b_tx_hex = "0200000000010115ccf0534b7969e5ac0f4699e51bf7805168244057059caa333397fcf8a9acdd0000000000fdffffff027a6faf85150000001600147b458433d0c04323426ef88365bd4cfef141ac7520a107000000000022512087a397fc19d816b6f938dad182a54c778d2d5db8b31f4528a758b989d42f0b78024730440220072d64b2e3bbcd27bd79cb8859c83ca524dad60dc6310569c2a04c997d116381022071d4df703d037a9fe16ccb1a2b8061f10cda86ccbb330a49c5dcc95197436c960121030db9616d96a7b7a8656191b340f77e905ee2885a09a7a1e80b9c8b64ec746fb300000000";
234
235 let c_tx: Tx = Tx::try_from(c_tx_hex).unwrap();
236 let Tx::Cardano(_) = c_tx.clone() else {
237 unreachable!("not a cardano tx: {c_tx:?}")
238 };
239 let b_tx: Tx = Tx::try_from(b_tx_hex).unwrap();
240 let Tx::Bitcoin(_) = b_tx.clone() else {
241 unreachable!("not a bitcoin tx: {b_tx:?}")
242 };
243
244 let v = vec![b_tx, c_tx];
245 let json_str = serde_json::to_string_pretty(&v).unwrap();
246 eprintln!("{json_str}");
247 }
248
249 #[test]
250 fn ser_to_cbor() {
251 let c_tx_hex = "84a400d901028182582011a2338987035057f6c36286cf5aadc02573059b2cde9790017eb4e148f0c67a0001828258390174f84e13070bb755eaa01cb717da8c7450daf379948e979f6de99d26ba89ff199fde572546b9a044eb129ad2edb184bd79cde63ab4b47aec1a01312d008258390184f1c3b1fff5241088acc4ce0aec81f45a71a70e35c94e30a70b7cdfeb0785cdec744029db6b4f344b1123497c9cabfeeb94af20fcfddfe01a33e578fd021a000299e90758201e8eb8575d879922d701c12daa7366cb71b6518a9500e083a966a8e66b56ed23a10081825820ea444825bbd5cc97b6c795437849fe55694b52e2f51485ac76ca2d9f991e83305840d59db4fa0b4bb233504f5e6826261a2e18b2e22cb3df4f631ab77d94d62e8df3200536271f3f3a625bc86919714972964f070f909f145b342f2889f58ccc210ff5a11902a2a1636d736765546f6b656f";
252
253 let b_tx_hex = "0200000000010115ccf0534b7969e5ac0f4699e51bf7805168244057059caa333397fcf8a9acdd0000000000fdffffff027a6faf85150000001600147b458433d0c04323426ef88365bd4cfef141ac7520a107000000000022512087a397fc19d816b6f938dad182a54c778d2d5db8b31f4528a758b989d42f0b78024730440220072d64b2e3bbcd27bd79cb8859c83ca524dad60dc6310569c2a04c997d116381022071d4df703d037a9fe16ccb1a2b8061f10cda86ccbb330a49c5dcc95197436c960121030db9616d96a7b7a8656191b340f77e905ee2885a09a7a1e80b9c8b64ec746fb300000000";
254
255 let c_tx: Tx = Tx::try_from(c_tx_hex).unwrap();
256 let b_tx: Tx = Tx::try_from(b_tx_hex).unwrap();
257
258 let v0 = vec![b_tx, c_tx];
259 let v0_cbor = ciborium::Value::serialized(&v0).unwrap();
260
261 let v1: Vec<Tx> = ciborium::Value::deserialized(&v0_cbor).unwrap();
262 let v1_cbor = ciborium::Value::serialized(&v1).unwrap();
263 assert_eq!(v0, v1);
264 assert_eq!(v0_cbor, v1_cbor);
265 }
266}