Merge pull request #515 from logos-blockchain/fix/pp-proof-deserialize-panic

This commit is contained in:
Moudy 2026-06-09 15:45:08 +02:00 committed by GitHub
commit 4577f2cbcd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 56 additions and 1 deletions

View File

@ -17,6 +17,7 @@ ignore = [
{ id = "RUSTSEC-2026-0119", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" },
{ id = "RUSTSEC-2024-0370", reason = "transitive dependency of `logos-blockchain-http-api-common`, can't do anything than wait for upstream fix" },
{ id = "RUSTSEC-2026-0173", reason = "`proc-macro-error2` is unmaintained; pulled in transitively via `leptos_macro` and `overwatch-derive`, waiting on upstream fix" },
]
yanked = "deny"
unused-ignored-advisory = "deny"

View File

@ -31,7 +31,9 @@ impl Proof {
}
pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool {
let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap();
let Ok(inner) = borsh::from_slice::<InnerReceipt>(&self.0) else {
return false;
};
let receipt = Receipt::new(inner, circuit_output.to_bytes());
receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok()
}

View File

@ -930,4 +930,56 @@ mod tests {
"recipient should receive nothing"
);
}
/// Regression test: a `PrivacyPreservingTransaction` carrying a structurally invalid
/// proof must be rejected with a clean `Err`.
#[test]
fn privacy_garbage_proof_is_rejected() {
use lee_core::{
Commitment,
account::Account,
program::{BlockValidityWindow, TimestampValidityWindow},
};
use crate::{
PrivacyPreservingTransaction,
privacy_preserving_transaction::{
circuit::Proof, message::Message, witness_set::WitnessSet,
},
};
let state = V03State::new_with_genesis_accounts(&[], vec![], 0);
// Minimal message that passes every check up to proof verification: a single
// commitment satisfies the non-empty requirement, no signers makes the
// nonce/signature checks vacuously true, and unbounded validity windows are valid
// for any block/timestamp.
let account_id = AccountId::from(&PublicKey::new_from_private_key(
&PrivateKey::try_new([1_u8; 32]).unwrap(),
));
let commitment = Commitment::new(&account_id, &Account::default());
let message = Message {
public_account_ids: vec![],
nonces: vec![],
public_post_states: vec![],
encrypted_private_post_states: vec![],
new_commitments: vec![commitment],
new_nullifiers: vec![],
block_validity_window: BlockValidityWindow::new_unbounded(),
timestamp_validity_window: TimestampValidityWindow::new_unbounded(),
};
// Garbage proof bytes: not a valid borsh-encoded `InnerReceipt`.
let garbage_proof = Proof::from_inner(vec![0xff_u8; 64]);
let witness_set = WitnessSet::for_message(&message, garbage_proof, &[]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
let result = ValidatedStateDiff::from_privacy_preserving_transaction(&tx, &state, 1, 0);
match result {
Err(LeeError::InvalidPrivacyPreservingProof) => {}
Err(other) => panic!("expected InvalidPrivacyPreservingProof, got {other:?}"),
Ok(_) => panic!("garbage proof was accepted instead of rejected"),
}
}
}