Skip to content

Commit 1a4eb14

Browse files
committed
Replace checks in validate_prove_request with charms_client::is_correct
(cherry picked from commit 0367ba1) # Conflicts: # src/spell.rs
1 parent 3f90a00 commit 1a4eb14

File tree

1 file changed

+128
-13
lines changed

1 file changed

+128
-13
lines changed

src/spell.rs

Lines changed: 128 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ pub use charms_client::{
2828
CURRENT_VERSION, NormalizedCharms, NormalizedSpell, NormalizedTransaction, Proof,
2929
SpellProverInput, to_tx,
3030
};
31-
use charms_client::{MOCK_SPELL_VK, bitcoin_tx::BitcoinTx, tx::Tx, well_formed};
31+
use charms_client::{MOCK_SPELL_VK, bitcoin_tx::BitcoinTx, tx::Tx};
3232
use charms_data::{
33-
App, AppInput, B32, Charms, Data, TOKEN, Transaction, TxId, UtxoId, is_simple_transfer, util,
33+
App, AppInput, B32, Charms, Data, TOKEN, Transaction, TxId, UtxoId, check, is_simple_transfer,
34+
util,
3435
};
3536
use charms_lib::SPELL_VK;
3637
use const_format::formatcp;
@@ -971,6 +972,39 @@ impl ProveSpellTx for ProveSpellTxImpl {
971972
}
972973
}
973974

975+
pub fn ensure_exact_app_binaries(
976+
norm_spell: &NormalizedSpell,
977+
app_private_inputs: &BTreeMap<App, Data>,
978+
tx: &Transaction,
979+
binaries: &BTreeMap<B32, Vec<u8>>,
980+
) -> anyhow::Result<()> {
981+
let required_vks: BTreeSet<_> = norm_spell
982+
.app_public_inputs
983+
.iter()
984+
.filter(|(app, data)| {
985+
!data.is_empty()
986+
|| !app_private_inputs
987+
.get(app)
988+
.is_none_or(|data| data.is_empty())
989+
|| !is_simple_transfer(app, tx)
990+
})
991+
.map(|(app, _)| &app.vk)
992+
.collect();
993+
994+
let provided_vks: BTreeSet<_> = binaries.keys().collect();
995+
996+
ensure!(
997+
required_vks == provided_vks,
998+
"binaries must contain exactly the required app binaries.\n\
999+
Required VKs: {:?}\n\
1000+
Provided VKs: {:?}",
1001+
required_vks,
1002+
provided_vks
1003+
);
1004+
1005+
Ok(())
1006+
}
1007+
9741008
pub fn ensure_no_zero_amounts(norm_spell: &NormalizedSpell) -> anyhow::Result<()> {
9751009
let apps: Vec<_> = norm_spell
9761010
.app_public_inputs
@@ -1018,6 +1052,60 @@ fn ensure_all_prev_txs_are_present(
10181052
Ok(())
10191053
}
10201054

1055+
// NOTE: copied over from charms-spell-checker to make cherry-picked code work.
1056+
// This is in charms-client in later versions.
1057+
/// Check if the spell is correct.
1058+
fn spell_is_correct(
1059+
spell: &NormalizedSpell,
1060+
prev_txs: &Vec<Tx>,
1061+
app_input: Option<AppInput>,
1062+
spell_vk: &str,
1063+
tx_ins_beamed_source_utxos: &BTreeMap<UtxoId, UtxoId>,
1064+
) -> bool {
1065+
let prev_spells = charms_client::prev_spells(prev_txs, spell_vk, false);
1066+
1067+
check!(charms_client::well_formed(
1068+
spell,
1069+
&prev_spells,
1070+
tx_ins_beamed_source_utxos
1071+
));
1072+
1073+
let Some(prev_txids) = spell.tx.prev_txids() else {
1074+
unreachable!("the spell is well formed: tx.ins MUST be Some");
1075+
};
1076+
let all_prev_txids: BTreeSet<_> = tx_ins_beamed_source_utxos
1077+
.values()
1078+
.map(|u| &u.0)
1079+
.chain(prev_txids)
1080+
.collect();
1081+
check!(all_prev_txids == prev_spells.keys().collect());
1082+
1083+
let apps = charms_client::apps(spell);
1084+
1085+
let charms_tx = charms_client::to_tx(spell, &prev_spells, tx_ins_beamed_source_utxos);
1086+
let tx_is_simple_transfer_or_app_contracts_satisfied =
1087+
apps.iter().all(|app| is_simple_transfer(app, &charms_tx)) && app_input.is_none()
1088+
|| app_input.is_some_and(|app_input| apps_satisfied(&app_input, &charms_tx));
1089+
check!(tx_is_simple_transfer_or_app_contracts_satisfied);
1090+
1091+
true
1092+
}
1093+
1094+
// NOTE: copied over from charms-spell-checker to make cherry-picked code work.
1095+
// This is in charms-client in later versions.
1096+
fn apps_satisfied(app_input: &AppInput, tx: &Transaction) -> bool {
1097+
let app_runner = charms_app_runner::AppRunner::new(false);
1098+
app_runner
1099+
.run_all(
1100+
&app_input.app_binaries,
1101+
&tx,
1102+
&app_input.app_public_inputs,
1103+
&app_input.app_private_inputs,
1104+
)
1105+
.expect("all apps should run successfully");
1106+
true
1107+
}
1108+
10211109
impl ProveSpellTxImpl {
10221110
pub fn validate_prove_request(
10231111
&self,
@@ -1035,19 +1123,46 @@ impl ProveSpellTxImpl {
10351123
let prev_spells = charms_client::prev_spells(&prev_txs, SPELL_VK, self.mock);
10361124

10371125
let tx = to_tx(&norm_spell, &prev_spells, &tx_ins_beamed_source_utxos);
1038-
// prove charms-app-checker run
1039-
let cycles = AppRunner::new(true).run_all(
1040-
&prove_request.binaries,
1041-
&tx,
1042-
&norm_spell.app_public_inputs,
1126+
1127+
ensure_exact_app_binaries(
1128+
&norm_spell,
10431129
&app_private_inputs,
1130+
&tx,
1131+
&prove_request.binaries,
10441132
)?;
1045-
let total_cycles = cycles.iter().sum();
1046-
ensure!(well_formed(
1047-
&norm_spell,
1048-
&prev_spells,
1049-
&tx_ins_beamed_source_utxos
1050-
));
1133+
1134+
let app_input = match prove_request.binaries.is_empty() {
1135+
true => None,
1136+
false => Some(AppInput {
1137+
app_binaries: prove_request.binaries.clone(),
1138+
app_public_inputs: norm_spell.app_public_inputs.clone(),
1139+
app_private_inputs: app_private_inputs.clone(),
1140+
}),
1141+
};
1142+
1143+
ensure!(
1144+
spell_is_correct(
1145+
&norm_spell,
1146+
&prev_txs,
1147+
app_input.clone(),
1148+
SPELL_VK,
1149+
&tx_ins_beamed_source_utxos,
1150+
),
1151+
"spell verification failed"
1152+
);
1153+
1154+
// Calculate cycles for fee estimation
1155+
let total_cycles = if let Some(app_input) = &app_input {
1156+
let cycles = AppRunner::new(true).run_all(
1157+
&app_input.app_binaries,
1158+
&tx,
1159+
&norm_spell.app_public_inputs,
1160+
&app_input.app_private_inputs,
1161+
)?;
1162+
cycles.iter().sum()
1163+
} else {
1164+
0
1165+
};
10511166

10521167
match prove_request.chain.as_str() {
10531168
BITCOIN => {

0 commit comments

Comments
 (0)