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