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:
parent
d1c9e7a732
commit
326a559f76
12 changed files with 201 additions and 339 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -10,11 +10,9 @@
|
|||
//! - a collateral input (≥ 5 ADA) from the wallet's normal UTXOs;
|
||||
//! consumed if the script fails on-chain.
|
||||
//!
|
||||
//! ## Phase 4.1–4.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();
|
||||
|
|
|
|||
|
|
@ -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}")));
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue