chore: strip audit-ticket prefixes from code comments

Drops the ~60 ticket-prefix comments (CRIT-N, HIGH-N, MED-N, LOW-N,
L-N, M-N, AUDIT-N, PLUTUS-N, "audit fix (date):", "Phase N" labels,
"Adversarial-review fix:") that had accumulated in inline + doc
comments over several audit cycles. Where the surrounding prose
still carried useful WHY context it gets kept and tightened; where
the ticket WAS the comment it gets dropped entirely.

No logic, no renames, no behavior change. Audit history lives in
commit messages and the audits/ tree where it belongs — eternal
comments don't need to mirror it.

Net 138 LOC shorter. 253 tests pass, no new clippy or fmt warnings.
This commit is contained in:
Kayos 2026-05-12 14:42:13 -07:00
parent d1c9e7a732
commit 326a559f76
12 changed files with 201 additions and 339 deletions

View file

@ -1,10 +1,10 @@
//! Decode + summarize a Conway-era tx CBOR for human review.
//!
//! Used by `wallet.tx_summary` (Phase 4 / audit HIGH-2 fix). Before
//! a caller hands a pre-built CBOR to `wallet.sign_partial` or
//! `wallet.submit_signed_tx`, they should pull a summary through here
//! and review what the tx actually does — what's being spent, where
//! funds are going, what's being minted, what certs are present.
//! Backs `wallet.tx_summary`. Before handing a pre-built CBOR to
//! `wallet.sign_partial` or `wallet.submit_signed_tx`, callers should
//! summarize through here and read what the tx actually does —
//! what's being spent, where funds are going, what's being minted,
//! what certs are present.
//!
//! No I/O. Pure decode → typed summary. Caller serializes to JSON.

View file

@ -180,7 +180,6 @@ impl Mnemonic {
// `xprv_bytes` was moved into normalize_bytes_force3rd, but
// the stack slot can still hold a copy depending on calling
// conventions / inlining. Defensive zeroize.
// (M-1 audit fix.)
xprv_bytes.zeroize();
Ok(RootKey { xprv })
}

View file

@ -9,7 +9,7 @@
//! asset under that policy and sends them to a destination
//! address.
//!
//! ## Phase 3 scope today
//! Current scope:
//!
//! - **Single-sig** + **single-sig with `invalid_after`** policy
//! shapes — the bread-and-butter wallet-controlled mint.

View file

@ -10,11 +10,9 @@
//! - a collateral input (≥ 5 ADA) from the wallet's normal UTXOs;
//! consumed if the script fails on-chain.
//!
//! ## Phase 4.14.3 scope
//!
//! Single Plutus input, single output, single collateral. Reference
//! inputs (4.2 expansion) and live ExUnits estimation (4.4) are
//! follow-ups. ExUnits today come from the caller.
//! Current scope: single Plutus input, single output, single collateral.
//! Reference inputs and live ExUnits estimation are follow-ups; ExUnits
//! today come from the caller.
use bech32::FromBase32;
use pallas_crypto::hash::Hash;
@ -137,8 +135,8 @@ pub fn build_signed_plutus_spend(
let payout_addr = parse_address(payout_address_bech32)?;
let network_id = network_id_for(network);
// PLUTUS-2 audit fix: collateral MUST be ADA-only (chain rejects
// collateral inputs that carry native assets).
// Collateral must be ADA-only — the chain rejects collateral
// inputs that carry native assets.
let mut ada_only: Vec<InputUtxo> = available_utxos
.iter()
.filter(|u| u.assets.is_empty())
@ -146,13 +144,12 @@ pub fn build_signed_plutus_spend(
.collect();
ada_only.sort_by_key(|u| std::cmp::Reverse(u.lovelace));
// AUDIT4-2 fix: pick the SMALLEST ADA-only UTXO that still
// qualifies for collateral (≥ 5 ADA), so the LARGEST stays
// available for funding the spend. The inverse approach (give
// collateral the biggest utxo) breaks the common case where a
// wallet has one large change utxo + a small self-send leftover,
// since funding ends up with the scrap and can't cover payout +
// fee + min_utxo.
// Pick the SMALLEST ADA-only UTXO that still qualifies for
// collateral (≥ 5 ADA), so the LARGEST stays available for
// funding the spend. Giving collateral the biggest utxo breaks
// the common case where a wallet has one large change utxo + a
// small self-send leftover — funding ends up with the scrap and
// can't cover payout + fee + min_utxo.
//
// Collateral is NEVER consumed on the happy path — it's only
// seized if the script fails — so its size beyond the 5-ADA
@ -170,9 +167,9 @@ pub fn build_signed_plutus_spend(
})?
.clone();
// PLUTUS-1 audit fix: collateral is NOT consumed on the happy
// path — it's only seized if the script fails. So we need a
// SEPARATE regular input to fund payout + fee + change.
// Collateral is NOT consumed on the happy path — it's only
// seized if the script fails — so we need a SEPARATE regular
// input to fund payout + fee + change.
//
// Pick the LARGEST ADA-only UTXO that's not the collateral —
// funding has to cover payout + script-execution fee + change
@ -195,8 +192,8 @@ pub fn build_signed_plutus_spend(
// Collateral is held off-line and only consumed on script failure.
let total_in = locked.lovelace.saturating_add(funding.lovelace);
// PLUTUS-3 audit fix: Plutus tx fee = size_fee + ExUnits_fee.
// ExUnits dominates for typical scripts (~1.5 ADA at default budget).
// Plutus tx fee = size_fee + ExUnits_fee. ExUnits dominates for
// typical scripts (~1.5 ADA at default budget).
let ex_fee = params.ex_units_fee(ex_units.mem, ex_units.steps);
let fee_pass1 = 1_000_000u64.saturating_add(ex_fee);
let need = payout_lovelace
@ -225,8 +222,8 @@ pub fn build_signed_plutus_spend(
let build_with_fee =
|fee: u64, change_lovelace: u64| -> Result<StagingTransaction, WalletError> {
let mut staging = StagingTransaction::new();
// PLUTUS-1: locked + funding as regular inputs (both consumed
// on happy path); collateral as collateral_input only.
// Locked + funding go in as regular inputs (both consumed
// on the happy path); collateral as collateral_input only.
staging = staging.input(locked_input.clone());
staging = staging.input(funding_input.clone());
staging = staging.collateral_input(collateral_input.clone());
@ -246,11 +243,11 @@ pub fn build_signed_plutus_spend(
if let Some(d) = witness_datum_cbor {
staging = staging.datum(d.to_vec());
}
// PLUTUS-4 audit fix: pallas-txbuilder only computes
// script_data_hash if language_view is set. Without it, the
// body's hash is None and the chain rejects with
// PPViewHashesDontMatch. PlutusV3 path requires a V3 cost
// model — caller-supplied via ProtocolParams.
// pallas-txbuilder only computes script_data_hash when
// language_view is set; without it the body hash is None
// and the chain rejects with PPViewHashesDontMatch. The
// PlutusV3 path needs a V3 cost model — caller-supplied
// via ProtocolParams.
if let Some(cost_model) = params.plutus_v3_cost_model.as_deref() {
if matches!(script_version, PlutusVersion::V3) {
staging =
@ -379,7 +376,7 @@ mod tests {
#[test]
fn ex_units_fee_matches_known_values() {
// PLUTUS-3 audit fix: fee for default budget should be ~1.5 ADA.
// Fee for the default budget should be ~1.5 ADA.
let p = ProtocolParams::default();
let fee = p.ex_units_fee(DEFAULT_EX_UNITS.mem, DEFAULT_EX_UNITS.steps);
// 14M mem * 0.0577 + 10B steps * 7.21e-5
@ -403,9 +400,8 @@ mod tests {
lovelace: 50_000_000,
};
// Only ONE wallet UTXO — collateral pick consumes it; no
// separate funding UTXO available. PLUTUS-1 fix should
// reject with a clear error rather than building a tx that
// chain rejects.
// separate funding UTXO available. Should reject with a clear
// error rather than build a tx the chain rejects.
let utxos = vec![InputUtxo {
tx_hash_hex: "cafebabe".repeat(8),
output_index: 0,
@ -492,8 +488,8 @@ mod tests {
output_index: 0,
lovelace: 50_000_000,
};
// PLUTUS-1+2 audit fix: spend path needs TWO ADA-only wallet
// UTXOs — one for collateral, one for funding.
// The spend path needs TWO ADA-only wallet UTXOs — one for
// collateral, one for funding.
let utxos = vec![
InputUtxo {
tx_hash_hex: "cafebabe".repeat(8),
@ -541,11 +537,11 @@ mod tests {
assert!(witness.redeemer.is_some(), "redeemer witness present");
}
/// AUDIT4-2 regression. A wallet with one tiny qualifying UTXO
/// alongside one huge UTXO must pick the tiny one for collateral
/// and the huge one for funding (not the inverse). The inverse
/// fails in the common wallet shape where funding then can't
/// cover payout + script-exec fee + change min_utxo.
/// A wallet with one tiny qualifying UTXO alongside one huge UTXO
/// must pick the tiny one for collateral and the huge one for
/// funding (not the inverse). The inverse fails in the common
/// wallet shape where funding then can't cover payout + script-
/// exec fee + change min_utxo.
#[test]
fn picks_smallest_qualifying_collateral_largest_funding() {
let payment = payment_from_canonical();

View file

@ -44,8 +44,7 @@ pub fn add_witness(payment_key: &PaymentKey, cbor_bytes: &[u8]) -> Result<Vec<u8
// encoder).
let body_hash = tx.transaction_body.compute_hash();
// M-1 audit fix: stack copy gets zeroized after from_bytes
// consumes it.
// Zeroize the stack copy after from_bytes consumes it.
let mut extended_bytes: [u8; 64] = payment_key.xprv().extended_secret_key();
let secret = SecretKeyExtended::from_bytes(extended_bytes)
.map_err(|e| WalletError::Derivation(format!("invalid extended secret: {e}")));

View file

@ -127,10 +127,9 @@ impl ProtocolParams {
.saturating_add(self.min_fee_b)
}
/// ExUnits execution cost in lovelace, ceiling-rounded per the
/// Conway protocol rules. Add this to `min_fee_for_size(...)` for
/// the total fee on a Plutus transaction.
/// (PLUTUS-3 audit fix.)
/// ExUnits execution cost in lovelace, ceiling-rounded per Conway
/// protocol rules. Add this to `min_fee_for_size(...)` for the
/// total fee on a Plutus transaction.
pub fn ex_units_fee(&self, mem: u64, steps: u64) -> u64 {
// ceil(mem * num / den)
let mem_cost = mem
@ -341,10 +340,10 @@ fn hex_decode_32(s: &str) -> Result<[u8; 32], WalletError> {
/// `BuiltTransaction::sign` can consume it. The XPrv's first 64
/// bytes are the extended secret; we reuse them directly.
///
/// **M-1 audit fix**: defensive zeroize of the stack-resident
/// extended-secret bytes after `from_bytes` consumes them. The
/// SecretKeyExtended itself zeroizes on drop (pallas-crypto handles
/// that); this just covers the local stack copy that lingers between
/// Defensive zeroize of the stack-resident extended-secret bytes
/// after `from_bytes` consumes them. The SecretKeyExtended itself
/// zeroizes on drop (pallas-crypto handles that); this just covers
/// the local stack copy that lingers between
/// `extended_secret_key()` returning and `from_bytes` taking it by
/// value.
fn payment_key_to_private(payment: &PaymentKey) -> Result<PrivateKey, WalletError> {
@ -502,10 +501,10 @@ fn build_unsigned_bytes(staging: StagingTransaction) -> Result<Vec<u8>, WalletEr
/// `&[]` for `assets_to_send` to keep it ADA-only.
///
/// `to_inline_datum_cbor`, when `Some`, attaches the bytes as an
/// inline datum on the recipient output. Used to lock funds at a
/// script address with a datum the validator can read (AUDIT4-3
/// fix). Change output never gets a datum — it goes back to the
/// wallet which has no validator to satisfy.
/// inline datum on the recipient output — needed to lock funds at a
/// script address with a datum the validator can read. The change
/// output never gets a datum (it goes back to the wallet, no
/// validator to satisfy).
///
/// `to_reference_script`, when `Some`, attaches the script bytes as
/// a reference-script on the recipient output (Babbage/Conway era
@ -622,8 +621,8 @@ fn prepare_payment(
// worth of lovelace into change in that case.
let change_must_exist = !change_assets.is_empty();
// L-1 audit fix: checked_add for the inner sum so the outer
// checked_sub is fully defensive against u64 overflow.
// checked_add on the inner sum so the outer checked_sub is fully
// defensive against u64 overflow.
let outflow = lovelace
.checked_add(real_fee)
.ok_or_else(|| WalletError::Derivation("lovelace + fee overflow".into()))?;
@ -671,9 +670,8 @@ fn prepare_payment(
// Re-shape the asset maps back into Vec<AssetSpec> for the
// summary — easier for callers to display than a BTreeMap.
// L-4 audit fix: replaced .expect() with proper error
// propagation. Logic-bug paths (we built the key) become
// typed errors instead of process-level panics.
// Logic-bug paths (we built the key) propagate as typed errors
// rather than panicking the process.
let send_assets_vec: Vec<AssetSpec> = target_assets
.iter()
.map(|(k, v)| {
@ -767,8 +765,8 @@ pub fn build_signed_payment(
///
/// `to_inline_datum_cbor`, when `Some`, attaches the bytes as an
/// inline datum on the recipient output — needed to lock funds at
/// a script address with a datum the validator can read (AUDIT4-3
/// fix). `None` for normal sends to wallet addresses.
/// a script address with a datum the validator can read. `None`
/// for normal sends to wallet addresses.
#[allow(clippy::too_many_arguments)]
pub fn build_signed_payment_with_assets(
payment_key: &PaymentKey,