Skip to content

fix!: require a specific signer for input spend + key manager backend - #1602

Merged
sdbondi merged 4 commits into
tari-project:developmentfrom
sdbondi:stealth-malleability-fixes
Oct 16, 2025
Merged

fix!: require a specific signer for input spend + key manager backend#1602
sdbondi merged 4 commits into
tari-project:developmentfrom
sdbondi:stealth-malleability-fixes

Conversation

@sdbondi

@sdbondi sdbondi commented Oct 15, 2025

Copy link
Copy Markdown
Member

Description

  1. fix: malleability bug in transfer statement
  2. fix!: require a specific signer for input spend
  3. fix(wallet/sdk): use key store in key manager api

Motivation and Context

  1. Outputs could be changed in transfer statements and still verify.
  2. Previously, a stealth transfer statement (without revealed input funds) could be taken from an existing transaction and put into another, and still validate successfully and perform the transfer. The correctly stealth outputs state transitions would still occur, however revealed outputs funds (if any) could be stolen! This PR prevents this by committing to a particular transaction signer at spend-time in the input statement. This signer must be present in the transaction or the transaction fails. This still preserves privacy, as a nonce key can be used to sign the transaction.
  3. Use the wallet key store backend in the key manager

How Has This Been Tested?

New unit tests

What process can a PR reviewer use to test or verify this change?

Breaking Changes

  • None
  • Requires data directory to be deleted
  • Other - Please specify

Summary by CodeRabbit

  • New Features

    • Stealth transfers now require and propagate an explicit signer for stronger access control; Hash64 exposed in bindings.
  • Bug Fixes

    • Clearer access-denied errors and improved fee estimation/error surfacing in the UI; dry-run and submission flows release locks reliably.
  • Refactor

    • Key management moved to a local keystore and signing context is surfaced in transfer flows.
  • Tests

    • Expanded stealth transfer validation and access-control tests.

@coderabbitai

coderabbitai Bot commented Oct 15, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Introduces signer-scoped stealth transfer handling and metadata hashing: adds Executable::signers_iter, threads a required_signer through stealth statements/validations, replaces spend_stealth_utxos with validate_and_spend_stealth_utxos returning ValidatedStealthTransfer, adds Hash64, refactors key management to LocalKeyStore and updates transaction builder/signing flows and tests.

Changes

Cohort / File(s) Summary
Executables & Transaction Auth
crates/engine/src/executables/mod.rs, crates/engine/src/executables/transaction.rs, applications/tari_app_utilities/src/transaction_executor.rs, crates/template_test_tooling/src/wrapped_transaction.rs
Add Executable::signers_iter and implementations; derive main signer from iterator; use signers_iter to build initial ownership proofs and expose signer iteration for wrapped transactions.
Engine Runtime Validation
crates/engine/src/runtime/working_state.rs, crates/engine/src/runtime/error.rs
Replace spend_stealth_utxos with validate_and_spend_stealth_utxos(view_key, stmt) returning ValidatedStealthTransfer; enforce signer-in-scope checks and return AccessDeniedStealthTransferSigner when required signer missing.
Engine Types: Hash & Crypto Changes
crates/engine_types/src/hash.rs, crates/engine_types/src/lib.rs, crates/engine_types/src/crypto/messages.rs, crates/engine_types/src/crypto/mod.rs, crates/engine_types/src/crypto/utxo_spend.rs, crates/engine_types/src/stealth/transfer.rs
Add Hash64 type and re-export it; change various message hashing APIs to return/accept Hash64; add stealth_statement_metadata64; remove old verify_utxo_spend_permission; add validate_ownership_proof and rename/reshape balance-proof validation to include required_signer and metadata_hash.
Proto & Conversions
crates/p2p/proto/transaction.proto, crates/p2p/src/conversions/transaction.rs
Add bytes required_signer to proto StealthInputsStatement and convert to/from RistrettoPublicKeyBytes with validation.
Template Models & Builtins
crates/template_lib/src/models/stealth.rs, crates/template_lib/src/models/non_fungible.rs, crates/template_builtin/templates/faucet/src/lib.rs
Add required_signer: RistrettoPublicKeyBytes to StealthInputsStatement and constructors; adjust NonFungibleId::as_u256 to return a reference; faucet::take_confidential receives required_signer.
Wallet Crypto
crates/wallet/crypto/src/stealth.rs, crates/wallet/crypto/src/balance_proof.rs
Thread required_signer and outputs metadata hash into transfer creation and owner-proof generation; expose stealth balance-proof generator; handle zero-excess revealed-only early-return.
Wallet SDK: Key Management & APIs
crates/wallet/sdk/src/local_key_store.rs, crates/wallet/sdk/src/key_managers/backend.rs, crates/wallet/sdk/src/key_managers/local.rs, crates/wallet/sdk/src/apis/key_manager.rs, crates/wallet/sdk/src/apis/stealth_transfer.rs, crates/wallet/sdk/src/apis/stealth_outputs.rs, crates/wallet/sdk/src/apis/stealth_crypto.rs, crates/wallet/sdk/src/models/key.rs, crates/wallet/sdk/src/sdk.rs
Move key derivation into LocalKeyStore (add derive_secret); refactor LocalKeyManager to use key_store; update KeyManagerApi to accept LocalKeyStore; wire KeyManager into StealthTransferApi; propagate required_signer through APIs; remove ImportedWalletKey.key_type.
Transaction Builder API
crates/transaction/src/builder/mod.rs, crates/transaction/src/unsigned_transaction.rs
Change then to return Self, add map combinator, rename build to build_with_signatures and add zero-arg build() wrapper; update builders to use build_with_signatures.
Walletd Handlers & Utilities
applications/tari_walletd/src/handlers/accounts.rs, .../nfts.rs, .../transaction.rs, .../validator.rs, utilities/tariswap_test_bench/src/tariswap.rs
Align handlers with new signing flow: derive public signer, pass required_signer, switch builder combinator use thenmap, use build_with_signatures, propagate Result-based errors, and adjust dry-run/locking semantics; small builder combinator updates in tariswap.
Engine & Wallet Crypto Tests
crates/engine/tests/*.rs, crates/wallet/crypto/tests/stealth_transfer_statement.rs, crates/wallet/crypto/tests/output_statement.rs (removed)
Update tests to propagate required_signer and new Hash64/message shapes; enable auto-adding signer proofs in template tests; add negative test for wrong signer; remove legacy output_statement tests.
Template Test Tooling
crates/template_test_tooling/src/template_test.rs, crates/template_test_tooling/src/support/stealth.rs, crates/template_test_tooling/src/wrapped_transaction.rs
Add auto_add_proofs_from_signers (default true) and toggles; rename test helper to_public_key_bytes; propagate required_signer through stealth helpers; delegate WrappedTransaction signers_iter.
State Store & Key Removal
crates/state_store_rocksdb/src/key.rs (deleted), crates/state_store_rocksdb/src/codecs/small_bytes.rs
Remove CompositeKey/key utilities (file deleted); use central hex helper in small_bytes formatting.
Storage API Enhancements
crates/wallet/sdk/src/storage.rs, crates/wallet/storage_sqlite/src/reader.rs
Add stealth_outputs_count_by_status to WalletStoreReader trait and SQLite implementation to return counts filtered by account, resource and status.
Bindings & UI
bindings/src/*, applications/tari_walletd/web_ui/src/routes/...
Expose Hash64 in JS bindings and update generated types (StealthInputsStatement includes required_signer); add formError handling and UI propagation in SendMoney form.
Misc: Formatting & Helpers
crates/template_lib_types/*, crates/state_store_rocksdb/src/codecs/small_bytes.rs, crates/template_lib_types/src/hex.rs
Replace manual byte-hex loops with hex::write_hex_fmt helper; add bytes_to_hex test; add Scalar32Bytes::is_zero, EncryptedData::empty, MaxBytes::empty and related default changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant WalletSDK as Wallet SDK
  participant KeyStore as LocalKeyStore / KeyMgr
  participant Crypto as Wallet Crypto
  participant Builder as TransactionBuilder
  participant Engine as Engine Runtime
  participant State as WorkingState

  WalletSDK->>KeyStore: derive signing key -> public bytes (required_signer)
  KeyStore-->>WalletSDK: required_signer
  WalletSDK->>Crypto: create_transfer_statement(required_signer, outputs)
  Crypto-->>WalletSDK: StealthTransferStatement + owner proofs
  WalletSDK->>Builder: builder.map(...sign...) -> build_with_signatures(signatures)
  Builder->>Engine: submit transaction
  Engine->>State: validate_and_spend_stealth_utxos(stmt, view_key)
  State->>State: check required_signer in scope
  State->>State: validate ownership proofs (required_signer, metadata_hash)
  State->>State: validate transfer balance -> ValidatedStealthTransfer
  State-->>Engine: validation result / AccessDeniedStealthTransferSigner
  Engine-->>WalletSDK: submission result
Loading
sequenceDiagram
  autonumber
  participant TX as Transaction (Executable)
  participant Wrapped as WrappedTransaction / TemplateTest

  TX->>TX: signers_iter() yields [seal_signer?, signatures...]
  TX-->>Builder: main_signer = signers_iter().next()
  Wrapped->>TX: WrappedTransaction.signers_iter() delegates to transaction.signers_iter()
  Note over TX,Wrapped: signers_iter used to auto-add proofs and build auth params
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

I thump at dawn with bytes in tow,
Sixty-four hashes softly glow.
Signers line up, proofs stitched tight,
Stealthy hops through ledger night.
A carrot-coded cheer — secure and light! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "fix!: require a specific signer for input spend + key manager backend" directly addresses the two primary objectives described in the PR: implementing a security fix for a stealth transfer malleability vulnerability by requiring a specific signer to be committed at input-spend time, and updating the key manager API to use the wallet key store backend. The title is clear, concise, and specific enough that a developer scanning commit history would understand the core changes. While the changeset includes many supporting modifications (such as the addition of Hash64, stealth validation refactoring, balance proof updates, and various API adjustments), the title appropriately prioritizes the main breaking changes and security fix that represent the primary objectives of the pull request.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/wallet/sdk/src/apis/key_manager.rs (1)

195-205: Bug: view_only_key_id uses the wrong key id

You derive view_only_key but set view_only_key_id from key (owner). Should use view_only_key.as_key_id().

Apply:

-            view_only_key_id: key.as_key_id(),
-            owner_key_id: key.as_key_id(),
+            view_only_key_id: view_only_key.as_key_id(),
+            owner_key_id: key.as_key_id(),
🧹 Nitpick comments (17)
crates/template_lib_types/src/crypto/scalar.rs (1)

28-30: Consider extracting the zero-check pattern.

The exact same implementation appears in RistrettoPublicKeyBytes::is_zero() (in crates/template_lib_types/src/crypto/ristretto.rs lines 52-54). If this pattern appears in additional fixed-size byte array types, consider extracting it into a helper trait or macro to reduce duplication.

Example trait approach:

pub trait FixedByteArrayZeroCheck {
    fn is_zero(&self) -> bool;
}

impl<const N: usize> FixedByteArrayZeroCheck for [u8; N] {
    fn is_zero(&self) -> bool {
        self.iter().all(|&b| b == 0)
    }
}

Then both Scalar32Bytes and RistrettoPublicKeyBytes could leverage this trait by delegating to self.0.is_zero().

crates/engine/src/executables/transaction.rs (1)

26-31: Signer ordering logic looks correct; watch for duplicates.

Yields seal signer (if authorized) then all signature keys. If consumers assume uniqueness, consider deduping or documenting that duplicates may appear.

crates/transaction/src/builder/mod.rs (1)

418-430: Guard against missing with_authorized_seal_signer() when signatures are present.

Current default only authorizes the seal signer if there are no signatures. With pre-attached signatures, forgetting to call with_authorized_seal_signer() can undermine the “required signer” guarantee. Add a debug assertion to catch this during development.

Suggested patch:

     pub fn build(self) -> UnsealedTransactionV1 {
-        let builder = self.then(|builder| {
+        let builder = self.then(|builder| {
             // This is so that we dont have to add this in a lot of places - TODO: this is an assumption that may not
             // apply to all transactions
             if builder.signatures.is_empty() {
                 builder.with_authorized_seal_signer()
             } else {
                 builder
             }
         });
 
+        // Dev-safety: if there are signatures, require explicit seal signer authorization
+        #[cfg(debug_assertions)]
+        {
+            let has_sigs = !builder.signatures.is_empty();
+            let is_authorized = match &builder.unsigned_transaction {
+                UnsignedTransaction::V1(tx) => tx.is_seal_signer_authorized,
+            };
+            debug_assert!(
+                !has_sigs || is_authorized,
+                "Transactions with pre-attached signatures should call \
+                 `with_authorized_seal_signer()` before signing to commit the required signer"
+            );
+        }
+
         builder.unsigned_transaction.build_with_signatures(builder.signatures)
     }

Also applies to: 429-429

crates/wallet/crypto/src/stealth.rs (1)

94-116: Avoid duplicating inputs_statement and extra Vec clone.

Build inputs_statement once and reuse it for the balance proof and final return. This removes a clone and reduces duplication.

-    let inputs_statement = StealthInputsStatement {
-        inputs: inputs_to_spend.clone(),
-        revealed_amount: revealed_input_amount,
-        required_signer,
-    };
+    let inputs_statement = StealthInputsStatement {
+        inputs: inputs_to_spend,
+        revealed_amount: revealed_input_amount,
+        required_signer,
+    };

     let balance_proof = generate_stealth_balance_proof_signature(
         &agg_input_mask,
         &agg_output_mask,
         &inputs_statement,
         &outputs_statement,
     );

-    Ok(StealthTransferStatement {
-        inputs_statement: StealthInputsStatement {
-            inputs: inputs_to_spend,
-            revealed_amount: revealed_input_amount,
-            required_signer,
-        },
-        outputs_statement,
-        balance_proof,
-    })
+    Ok(StealthTransferStatement {
+        inputs_statement,
+        outputs_statement,
+        balance_proof,
+    })
applications/tari_walletd/src/handlers/accounts.rs (1)

997-1013: Avoid double-releasing the lock on dry-run failure

You release before the dry run and again on error. The second release just logs an error. Simplify by releasing once.

Apply this diff:

         if req.dry_run {
             // Release the lock immediately as dry run does not submit the transaction
-            // TODO: maybe transfer() should not lock the outputs if it's a dry run
             if let Err(err) = sdk.stealth_outputs_api().release_lock(transfer.lock_id) {
                 error!(
                     target: LOG_TARGET,
                     "Failed to release locked outputs for dry run : {}",
                     err
                 );
             }
             let result = transaction_service.submit_dry_run_transaction(transaction).await;
             return match result {
                 Ok(res) => Ok(StealthTransferResponse {
                     transaction_id: res.finalize.transaction_hash.into(),
                 }),
                 Err(e) => {
-                    if let Err(err) = sdk.stealth_outputs_api().release_lock(transfer.lock_id) {
-                        error!(
-                            target: LOG_TARGET,
-                            "Failed to release locked outputs after dry run failure: {}",
-                            err
-                        );
-                    }
-
                     Err(anyhow::anyhow!("Dry run transaction failed: {}", e))
                 },
             };
         }
crates/wallet/crypto/tests/stealth_transfer_statement.rs (1)

4-8: Use rand::rngs::OsRng instead of aead::OsRng

Avoid mixing RNG types; use rand::rngs::OsRng which matches tari_crypto expectations.

-use chacha20poly1305::aead::OsRng;
+use rand::rngs::OsRng;
crates/engine/tests/stealth.rs (1)

350-357: Guard against OOB when corrupting the range proof

If agg_range_proof length changes, rp[100] could panic. Use a checked index.

-    rp[100] ^= 0xFF; // Corrupt the range proof
+    let idx = 100.min(rp.len().saturating_sub(1));
+    rp[idx] ^= 0xFF; // Corrupt the range proof
crates/engine_types/src/stealth/transfer.rs (1)

54-63: Also require zero public nonce for the revealed-only edge case

You enforce zero signature when there are no stealth inputs/outputs. For a canonical encoding, also require the public nonce to be zero.

-        if balance_proof.get_signature().as_bytes() != RistrettoSecretKey::default().as_bytes() {
+        if balance_proof.get_signature().as_bytes() != RistrettoSecretKey::default().as_bytes()
+            || !balance_proof.get_public_nonce().is_zero()
+        {
             return Err(ResourceError::InvalidBalanceProof {
                 details: "Balance proof signature verification failed for revealed amount. This typically indicates \
                           that the transfer statement total input amount != total output amount."
                     .to_string(),
             });
         }
crates/template_test_tooling/src/template_test.rs (1)

524-539: Auto-extend proofs from tx signers — consider dedup

Behavior aligns with the new signer requirement. Minor: extend() may introduce duplicates if callers already provided identity proofs. Not harmful, but you could dedup to keep auth scope tidy.

Example:

-        if self.auto_add_proofs_from_signers {
-            proofs.extend(
-                transaction
-                    .signers_iter()
-                    .map(|pk| NonFungibleAddress::from_public_key(*pk)),
-            );
-        }
+        if self.auto_add_proofs_from_signers {
+            use std::collections::HashSet;
+            let mut seen = HashSet::new();
+            proofs.retain(|p| seen.insert(p.clone()));
+            proofs.extend(
+                transaction
+                    .signers_iter()
+                    .map(|pk| NonFungibleAddress::from_public_key(*pk))
+                    .filter(|p| seen.insert(p.clone())),
+            );
+        }
crates/engine/src/runtime/working_state.rs (1)

270-312: Signer-in-scope enforcement and ownership proof binding: solid; minor readability tweak

Logic correctly:

  • Enforces required signer presence in auth scope
  • Binds ownership proofs to required signer + outputs metadata
  • Locks/downs each UTXO and validates

Small readability improvement for the scope check:

-        let proofs = self.base_call_scope().auth_scope().virtual_proofs();
-        if proofs
-            .iter()
-            .filter(|p| *p.resource_address() == PUBLIC_IDENTITY_RESOURCE_ADDRESS)
-            .all(|p| p.id().as_u256().map(|b| b.as_slice()) != Some(required_signer.as_bytes()))
-        {
+        let proofs = self.base_call_scope().auth_scope().virtual_proofs();
+        if !proofs.iter().any(|p|
+            *p.resource_address() == PUBLIC_IDENTITY_RESOURCE_ADDRESS &&
+            p.id()
+                .as_u256()
+                .map(|b| b.as_slice() == required_signer.as_bytes())
+                .unwrap_or(false)
+        ) {
             return Err(RuntimeError::AccessDeniedStealthTransferSigner {
                 required_signer: *required_signer,
             });
         }

Note: The lock/unlock order (down -> unlock -> validate) is acceptable in this single-threaded transaction context; if you prefer stronger TOCTOU hygiene, keep the lock until after validation. Optional.

Ensure all call sites now use validate_and_spend_stealth_utxos and no one references the removed spend_stealth_utxos.

crates/wallet/sdk/src/apis/stealth_transfer.rs (1)

445-474: Fee/input lock rollback on error is helpful

Releasing fee locks on input-selection error reduces stuck locks. Consider unifying both locks under a single DB transaction in future.

crates/wallet/sdk/src/key_managers/local.rs (1)

54-64: Error variants likely obsolete

PasswordManagerApiError and ReadOnlyMode aren’t used in this backend after refactor to key_store-only. Consider pruning to simplify API.

crates/wallet/sdk/src/local_key_store.rs (1)

8-16: Tighten module dependency to avoid circular coupling

LocalKeyStore depends on apis::key_manager::WalletKeyManager while key_manager.rs depends on LocalKeyStore. Move the WalletKeyManager type alias to a neutral module (e.g., a crypto/types module) to avoid cross-module coupling.

Also applies to: 19-24

crates/engine_types/src/crypto/messages.rs (1)

74-79: Use a dedicated domain label for metadata hashing

stealth_statement_metadata64 uses EngineHashDomainLabel::StealthOwnership. Prefer a distinct label (e.g., StealthStatementMetadata) to prevent ambiguity and accidental cross-domain collisions.

Apply if the label exists (or add it):

-pub fn stealth_statement_metadata64(outputs_statement: &StealthOutputsStatement) -> Hash64 {
-    engine_hasher64(EngineHashDomainLabel::StealthOwnership)
+pub fn stealth_statement_metadata64(outputs_statement: &StealthOutputsStatement) -> Hash64 {
+    engine_hasher64(EngineHashDomainLabel::StealthStatementMetadata)
         .chain(outputs_statement)
         .result()
         .into()
 }
crates/engine_types/src/hash.rs (2)

15-23: Incorrect docs: says 32 bytes; type is 64 bytes

Fix misleading docs to 64 bytes and update panic notes.

Apply:

-/// Representation of a 32-byte hash value
+/// Representation of a 64-byte hash value
@@
-    /// Panics if `N` is greater than Self::LENGTH (32)
+    /// Panics if `N` is greater than Self::LENGTH (64)
@@
-    /// Panics if `N` is greater than Self::LENGTH (32)
+    /// Panics if `N` is greater than Self::LENGTH (64)

Also applies to: 64-75, 77-88


140-144: Avoid DerefMut for hash types

Exposing &mut [u8] lets callers mutate a Hash64 in-place, breaking invariants if used as map keys or cached values. Prefer immutable accessors only.

Apply:

-impl DerefMut for Hash64 {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
-    }
-}
crates/wallet/sdk/src/apis/key_manager.rs (1)

217-233: Align index parameter types for consistency

derive_view_only_keypair/derive_account_key_pair take u64 while most APIs use DerivedKeyIndex. Consider switching to DerivedKeyIndex for consistency and type clarity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53bf250 and 788b18a.

📒 Files selected for processing (45)
  • applications/tari_app_utilities/src/transaction_executor.rs (2 hunks)
  • applications/tari_walletd/src/handlers/accounts.rs (5 hunks)
  • applications/tari_walletd/src/handlers/nfts.rs (1 hunks)
  • applications/tari_walletd/src/handlers/transaction.rs (2 hunks)
  • applications/tari_walletd/src/handlers/validator.rs (1 hunks)
  • crates/engine/src/executables/mod.rs (1 hunks)
  • crates/engine/src/executables/transaction.rs (1 hunks)
  • crates/engine/src/runtime/error.rs (2 hunks)
  • crates/engine/src/runtime/working_state.rs (5 hunks)
  • crates/engine/tests/signature.rs (3 hunks)
  • crates/engine/tests/stealth.rs (27 hunks)
  • crates/engine/tests/test.rs (1 hunks)
  • crates/engine_types/src/crypto/messages.rs (1 hunks)
  • crates/engine_types/src/crypto/mod.rs (0 hunks)
  • crates/engine_types/src/crypto/utxo_spend.rs (0 hunks)
  • crates/engine_types/src/hash.rs (1 hunks)
  • crates/engine_types/src/lib.rs (1 hunks)
  • crates/engine_types/src/stealth/transfer.rs (5 hunks)
  • crates/p2p/proto/transaction.proto (1 hunks)
  • crates/p2p/src/conversions/transaction.rs (2 hunks)
  • crates/template_builtin/templates/faucet/src/lib.rs (1 hunks)
  • crates/template_lib/src/models/non_fungible.rs (1 hunks)
  • crates/template_lib/src/models/stealth.rs (3 hunks)
  • crates/template_lib_types/src/crypto/scalar.rs (1 hunks)
  • crates/template_lib_types/src/encrypted_data.rs (1 hunks)
  • crates/template_lib_types/src/max_bytes.rs (2 hunks)
  • crates/template_test_tooling/src/support/stealth.rs (7 hunks)
  • crates/template_test_tooling/src/template_test.rs (6 hunks)
  • crates/template_test_tooling/src/wrapped_transaction.rs (1 hunks)
  • crates/transaction/src/builder/mod.rs (2 hunks)
  • crates/transaction/src/unsigned_transaction.rs (1 hunks)
  • crates/wallet/crypto/src/balance_proof.rs (3 hunks)
  • crates/wallet/crypto/src/stealth.rs (7 hunks)
  • crates/wallet/crypto/tests/output_statement.rs (0 hunks)
  • crates/wallet/crypto/tests/stealth_transfer_statement.rs (1 hunks)
  • crates/wallet/sdk/src/apis/key_manager.rs (4 hunks)
  • crates/wallet/sdk/src/apis/stealth_crypto.rs (2 hunks)
  • crates/wallet/sdk/src/apis/stealth_outputs.rs (3 hunks)
  • crates/wallet/sdk/src/apis/stealth_transfer.rs (12 hunks)
  • crates/wallet/sdk/src/key_managers/backend.rs (2 hunks)
  • crates/wallet/sdk/src/key_managers/local.rs (3 hunks)
  • crates/wallet/sdk/src/local_key_store.rs (2 hunks)
  • crates/wallet/sdk/src/models/key.rs (0 hunks)
  • crates/wallet/sdk/src/sdk.rs (3 hunks)
  • utilities/tariswap_test_bench/src/tariswap.rs (2 hunks)
💤 Files with no reviewable changes (4)
  • crates/wallet/sdk/src/models/key.rs
  • crates/engine_types/src/crypto/utxo_spend.rs
  • crates/wallet/crypto/tests/output_statement.rs
  • crates/engine_types/src/crypto/mod.rs
🧰 Additional context used
🧬 Code graph analysis (37)
crates/engine/src/executables/mod.rs (3)
crates/engine/src/executables/transaction.rs (1)
  • signers_iter (26-31)
crates/template_test_tooling/src/wrapped_transaction.rs (1)
  • signers_iter (43-45)
bindings/src/types/RistrettoPublicKeyBytes.ts (1)
  • RistrettoPublicKeyBytes (6-6)
crates/template_lib_types/src/max_bytes.rs (1)
crates/template_lib_types/src/encrypted_data.rs (1)
  • empty (26-28)
crates/wallet/sdk/src/apis/stealth_outputs.rs (2)
bindings/src/types/PedersenCommitmentBytes.ts (1)
  • PedersenCommitmentBytes (6-6)
bindings/src/types/RistrettoPublicKeyBytes.ts (1)
  • RistrettoPublicKeyBytes (6-6)
utilities/tariswap_test_bench/src/tariswap.rs (3)
crates/transaction/src/transaction.rs (1)
  • builder (47-49)
utilities/transaction_generator/src/transaction_builders/free_coins.rs (1)
  • builder (12-36)
utilities/transaction_generator/src/transaction_builders/manifest.rs (1)
  • builder (14-31)
crates/template_test_tooling/src/wrapped_transaction.rs (3)
crates/engine/src/executables/mod.rs (1)
  • signers_iter (21-21)
crates/engine/src/executables/transaction.rs (1)
  • signers_iter (26-31)
bindings/src/types/RistrettoPublicKeyBytes.ts (1)
  • RistrettoPublicKeyBytes (6-6)
crates/engine_types/src/lib.rs (1)
crates/storage/src/global/models/validator_node.rs (1)
  • hash (45-47)
applications/tari_walletd/src/handlers/validator.rs (3)
crates/engine_types/src/validator_fee.rs (1)
  • claim_public_key (156-158)
bindings/src/types/wallet-daemon-client/KeyBranch.ts (1)
  • KeyBranch (3-10)
crates/wallet/sdk/src/models/key.rs (1)
  • derived (297-299)
crates/template_lib_types/src/encrypted_data.rs (1)
crates/template_lib_types/src/max_bytes.rs (1)
  • empty (39-41)
applications/tari_walletd/src/handlers/transaction.rs (3)
crates/transaction/src/transaction.rs (2)
  • builder (47-49)
  • signatures (99-103)
crates/transaction/src/builder/mod.rs (1)
  • signatures (410-412)
crates/transaction/src/v1/transaction.rs (1)
  • signatures (64-66)
crates/engine/tests/test.rs (2)
crates/engine_types/src/resource.rs (1)
  • owner_key (115-117)
crates/template_test_tooling/src/template_test.rs (1)
  • to_public_key_bytes (416-418)
crates/transaction/src/unsigned_transaction.rs (1)
crates/transaction/src/builder/mod.rs (3)
  • signatures (410-412)
  • new (53-59)
  • build (418-430)
crates/template_lib/src/models/non_fungible.rs (1)
bindings/src/types/NonFungibleId.ts (1)
  • NonFungibleId (6-6)
crates/wallet/sdk/src/key_managers/backend.rs (3)
bindings/src/types/wallet-daemon-client/KeyId.ts (1)
  • KeyId (3-3)
crates/wallet/sdk/src/local_key_store.rs (1)
  • derive_secret (47-54)
crates/wallet/sdk/src/models/key.rs (1)
  • key_index (221-223)
crates/wallet/crypto/tests/stealth_transfer_statement.rs (9)
bindings/src/types/PrivateOutput.ts (1)
  • PrivateOutput (6-11)
bindings/src/types/UtxoOutput.ts (1)
  • UtxoOutput (6-14)
crates/wallet/crypto/src/balance_proof.rs (1)
  • generate_stealth_balance_proof_signature (38-55)
crates/wallet/crypto/src/stealth.rs (3)
  • create_outputs_statement (118-161)
  • create_transfer_statement (29-116)
  • output_statements (122-152)
crates/wallet/crypto/src/confidential.rs (1)
  • create_withdraw_proof (21-72)
crates/template_lib/src/models/confidential_proof.rs (1)
  • is_revealed_only (107-119)
crates/engine_types/src/stealth/transfer.rs (2)
  • validate_transfer_balance (33-134)
  • validate_ownership_proof (136-171)
crates/engine_types/src/crypto/messages.rs (1)
  • stealth_statement_metadata64 (74-79)
crates/template_lib_types/src/encrypted_data.rs (3)
  • empty (26-28)
  • try_from (76-86)
  • min_size (30-32)
crates/wallet/sdk/src/key_managers/local.rs (3)
crates/wallet/sdk/src/apis/key_manager.rs (1)
  • new (51-63)
crates/wallet/sdk/src/local_key_store.rs (1)
  • new (27-37)
bindings/src/types/wallet-daemon-client/KeyId.ts (1)
  • KeyId (3-3)
crates/template_test_tooling/src/support/stealth.rs (2)
bindings/src/types/RistrettoPublicKeyBytes.ts (1)
  • RistrettoPublicKeyBytes (6-6)
crates/engine_types/src/resource.rs (1)
  • view_key (126-128)
applications/tari_walletd/src/handlers/accounts.rs (1)
crates/wallet/sdk/src/apis/stealth_transfer.rs (1)
  • transfer (315-659)
crates/engine/src/runtime/working_state.rs (5)
crates/engine_types/src/stealth/outputs.rs (1)
  • stmt (43-82)
crates/engine_types/src/resource.rs (2)
  • view_key (126-128)
  • new (54-81)
crates/engine_types/src/crypto/messages.rs (1)
  • stealth_statement_metadata64 (74-79)
crates/template_lib/src/models/stealth.rs (1)
  • new (58-69)
crates/engine_types/src/stealth/transfer.rs (2)
  • validate_ownership_proof (136-171)
  • validate_transfer_balance (33-134)
crates/template_lib/src/models/stealth.rs (2)
bindings/src/types/RistrettoPublicKeyBytes.ts (1)
  • RistrettoPublicKeyBytes (6-6)
bindings/src/types/StealthInputsStatement.ts (1)
  • StealthInputsStatement (8-17)
crates/wallet/sdk/src/apis/stealth_crypto.rs (1)
bindings/src/types/RistrettoPublicKeyBytes.ts (1)
  • RistrettoPublicKeyBytes (6-6)
crates/template_test_tooling/src/template_test.rs (1)
crates/template_lib/src/models/non_fungible.rs (3)
  • new (220-223)
  • new (305-307)
  • from_public_key (233-238)
crates/engine/src/executables/transaction.rs (2)
crates/engine/src/executables/mod.rs (1)
  • signers_iter (21-21)
crates/template_test_tooling/src/wrapped_transaction.rs (1)
  • signers_iter (43-45)
crates/wallet/crypto/src/stealth.rs (2)
bindings/src/types/RistrettoPublicKeyBytes.ts (1)
  • RistrettoPublicKeyBytes (6-6)
crates/engine_types/src/crypto/messages.rs (1)
  • stealth_statement_metadata64 (74-79)
crates/template_builtin/templates/faucet/src/lib.rs (5)
bindings/src/types/RistrettoPublicKeyBytes.ts (1)
  • RistrettoPublicKeyBytes (6-6)
crates/wallet/sdk/src/apis/stealth_transfer.rs (1)
  • transfer (315-659)
bindings/src/types/StealthTransferStatement.ts (1)
  • StealthTransferStatement (5-13)
bindings/src/types/StealthInputsStatement.ts (1)
  • StealthInputsStatement (8-17)
crates/template_lib/src/models/stealth.rs (1)
  • new_revealed_only (72-74)
crates/engine_types/src/hash.rs (2)
crates/template_lib_types/src/crypto/scalar.rs (1)
  • fmt (83-88)
crates/tari_bor/src/lib.rs (2)
  • encode_into_std_writer (61-66)
  • to_value (96-98)
crates/transaction/src/builder/mod.rs (4)
crates/template_lib/src/resource/builder/fungible.rs (1)
  • then (95-97)
crates/template_lib/src/resource/builder/confidential.rs (1)
  • then (61-63)
crates/template_lib/src/resource/builder/non_fungible.rs (1)
  • then (59-61)
crates/template_lib/src/resource/builder/stealth.rs (1)
  • then (62-64)
crates/wallet/sdk/src/sdk.rs (3)
crates/wallet/sdk/src/apis/key_manager.rs (1)
  • new (51-63)
crates/wallet/sdk/src/key_managers/local.rs (1)
  • new (24-26)
crates/wallet/sdk/src/local_key_store.rs (1)
  • new (27-37)
crates/engine_types/src/stealth/transfer.rs (6)
bindings/src/types/StealthInput.ts (1)
  • StealthInput (8-18)
bindings/src/types/StealthTransferStatement.ts (1)
  • StealthTransferStatement (5-13)
crates/engine_types/src/crypto/helpers.rs (1)
  • try_decode_to_signature (91-93)
bindings/src/types/UtxoOutput.ts (1)
  • UtxoOutput (6-14)
crates/engine_types/src/byte_types.rs (5)
  • convert_from_byte_type (30-31)
  • convert_from_byte_type (62-64)
  • convert_from_byte_type (79-81)
  • convert_from_byte_type (101-105)
  • convert_from_byte_type (124-129)
crates/engine_types/src/crypto/messages.rs (1)
  • stealth_ownership64 (59-72)
crates/template_lib_types/src/crypto/scalar.rs (1)
crates/template_lib_types/src/crypto/ristretto.rs (1)
  • is_zero (53-55)
crates/engine_types/src/crypto/messages.rs (1)
crates/engine_types/src/hashing.rs (1)
  • engine_hasher64 (35-37)
crates/engine/tests/stealth.rs (3)
crates/template_lib/src/models/stealth.rs (1)
  • new (58-69)
crates/template_test_tooling/src/template_test.rs (3)
  • new (94-96)
  • None (95-95)
  • owner_proof (404-406)
crates/template_test_tooling/src/support/stealth.rs (2)
  • generate_mint_statement (41-67)
  • generate_transfer_data (116-135)
crates/wallet/sdk/src/local_key_store.rs (3)
crates/wallet/sdk/src/cipher_seed.rs (1)
  • cipher_seed (29-34)
crates/wallet/sdk/src/apis/key_manager.rs (1)
  • new (51-63)
crates/wallet/sdk/src/key_managers/backend.rs (1)
  • derive_secret (22-22)
crates/wallet/crypto/src/balance_proof.rs (4)
crates/engine_types/src/hash.rs (2)
  • default (147-149)
  • zero (28-30)
crates/template_lib_types/src/crypto/scalar.rs (1)
  • zero (24-26)
bindings/src/types/RistrettoPublicKeyBytes.ts (1)
  • RistrettoPublicKeyBytes (6-6)
crates/engine_types/src/crypto/messages.rs (1)
  • stealth_ownership64 (59-72)
crates/engine/tests/signature.rs (2)
crates/template_test_tooling/src/support/stealth.rs (1)
  • generate_transfer_data (116-135)
crates/template_test_tooling/src/template_test.rs (1)
  • owner_proof (404-406)
crates/wallet/sdk/src/apis/key_manager.rs (4)
crates/wallet/crypto/src/encryption.rs (1)
  • encrypt_with_password (100-134)
crates/wallet/sdk/src/key_managers/local.rs (1)
  • new (24-26)
crates/wallet/sdk/src/local_key_store.rs (1)
  • new (27-37)
crates/wallet/sdk/src/models/key.rs (2)
  • derived (297-299)
  • secret (158-160)
crates/wallet/sdk/src/apis/stealth_transfer.rs (5)
bindings/src/types/wallet-daemon-client/KeyBranch.ts (1)
  • KeyBranch (3-10)
bindings/src/types/wallet-daemon-client/KeyId.ts (1)
  • KeyId (3-3)
crates/wallet/sdk/src/apis/stealth_outputs.rs (2)
  • key_manager_api (361-378)
  • params (683-695)
crates/wallet/sdk/src/sdk.rs (1)
  • key_manager_api (159-167)
crates/wallet/sdk/src/models/key.rs (12)
  • key_id (65-67)
  • key_id (137-139)
  • key_id (162-164)
  • derived (297-299)
  • from (118-123)
  • from (143-148)
  • from (172-177)
  • from (181-186)
  • from (190-195)
  • from (199-204)
  • from (276-278)
  • from (282-284)
crates/engine/src/runtime/error.rs (1)
bindings/src/types/RistrettoPublicKeyBytes.ts (1)
  • RistrettoPublicKeyBytes (6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: check stable
  • GitHub Check: test
  • GitHub Check: check nightly
  • GitHub Check: clippy
  • GitHub Check: machete
🔇 Additional comments (54)
crates/template_lib_types/src/crypto/scalar.rs (1)

28-30: LGTM! Correct and useful addition.

The is_zero() implementation is correct and idiomatic. It uses iter().all() which short-circuits on the first non-zero byte, making it efficient. This is a natural companion to the existing zero() constructor and will be useful in validation paths for the stealth transfer flows introduced in this PR.

crates/template_lib/src/models/non_fungible.rs (1)

176-181: Zero-copy accessor looks good; confirm downstream impact.

Returning &[u8; 32] avoids unnecessary copies. It’s a public API change vs the prior owned array; please ensure all call sites (and any FFI/bindings) are updated accordingly.

Also applies to: 178-178

crates/transaction/src/unsigned_transaction.rs (2)

132-137: Good separation of concerns with build_with_signatures.

Centralizing signature injection here clarifies intent and keeps the wrapper simple.


139-141: Convenience build() delegating to build_with_signatures is fine.

This preserves the old ergonomics. Please verify any prior .build(signatures) call sites were migrated.

crates/engine/src/executables/transaction.rs (1)

23-24: Deriving main_signer from signers_iter() is cleaner.

This removes conditional branching and standardizes ordering.

crates/transaction/src/builder/mod.rs (2)

69-72: then(Self -> Self) aligns with other builders.

Consistent chaining API. Ensure external users expecting a generic return updated to use map.


73-75: map(Self -> T) addition is helpful for Result-based flows.

Enables error propagation and non-Self transforms cleanly.

utilities/tariswap_test_bench/src/tariswap.rs (2)

149-157: map(...)? is the right migration here.

This ensures the contextual signature is applied (or errors) before build(). One check: you pass &primary_account_pk while signing with the account key—confirm the context is intended to be the primary account in this flow.


353-354: Adding with_authorized_seal_signer() before build is correct.

Matches the new required-signer model and prevents malleability in these flows.

applications/tari_walletd/src/handlers/transaction.rs (2)

330-341: LGTM! Builder refactoring aligns with new flow.

The change from then to map indicates that the signing operation now returns a non-Result type directly, simplifying the builder chain. Error propagation is still maintained via the ? operator after the map call.


353-353: LGTM! More explicit method name.

The rename from build to build_with_signatures improves API clarity by making it explicit that signatures are being provided to the build operation.

crates/engine/tests/test.rs (1)

75-75: LGTM! Test utility method rename.

The method name change from get_test_public_key_bytes() to to_public_key_bytes() aligns with updated test utilities and maintains consistency with the broader refactoring.

crates/wallet/sdk/src/apis/stealth_crypto.rs (1)

48-75: LGTM! Required signer parameter properly propagated.

The addition of the required_signer parameter is correctly propagated through to stealth::create_transfer_statement, enabling the malleability fix that commits to a specific transaction signer at input-spend time.

crates/wallet/crypto/src/balance_proof.rs (2)

57-67: LGTM! Signer-scoped ownership proof signature.

The addition of required_signer and metadata_hash parameters enables signer-aware stealth ownership proofs, which is essential for the malleability fix. The parameters are correctly incorporated into the stealth_ownership64 message construction.


38-55: Confirm public API exposure

The function’s visibility changed from pub(crate) to pub, exposing it beyond the crate. It’s only used internally (in stealth.rs and tests). Ensure this wider API is intentional; if not, revert to pub(crate).

crates/p2p/proto/transaction.proto (1)

235-235: LGTM! Protobuf field addition for signer-scoped transfers.

The required_signer field addition to StealthInputsStatement enables the malleability fix by carrying signer commitment information through the protocol layer. The field number assignment (3) is correct and follows the existing sequence.

applications/tari_walletd/src/handlers/nfts.rs (1)

306-313: LGTM! Builder refactoring consistent with transaction handler.

The change from then to map aligns with the broader builder flow refactoring, matching the pattern applied in the transaction handler. The signing operation and error handling remain correct.

crates/template_lib_types/src/encrypted_data.rs (1)

26-28: LGTM! Convenient empty constructor.

The empty() constructor provides a clean way to create zero-length EncryptedData instances, properly delegating to MaxBytes::empty(). This is consistent with the pattern established in the MaxBytes type.

crates/engine/src/runtime/error.rs (1)

48-48: LGTM! New error variant for signer enforcement.

The AccessDeniedStealthTransferSigner error variant provides clear, specific feedback when a stealth transfer is attempted without the required signer present. This supports the malleability fix by enforcing signer requirements at runtime.

Also applies to: 177-178

crates/engine/src/executables/mod.rs (1)

21-21: LGTM! Clean iterator-based API addition.

The new signers_iter method provides a more flexible way to access all signers compared to main_signer. The trait method signature follows Rust conventions with impl Iterator, and the implementation in Transaction correctly chains the seal signer (when authorized) with other signatures.

crates/engine_types/src/lib.rs (2)

35-35: LGTM! Standard module organization.

Adding the private hash module follows Rust conventions for organizing related functionality.


43-43: LGTM! Proper re-export pattern.

The public re-export of hash module contents makes Hash64 and related types available to downstream code through the engine_types crate facade.

crates/engine/tests/signature.rs (6)

79-86: LGTM! Test updated for required signer flow.

The addition of test.to_public_key_bytes() as the required_signer parameter correctly aligns with the PR's objective to commit to a specific transaction signer at input-spend time.


91-91: LGTM! Ownership proof expectation updated correctly.

The change from vec![] to vec![test.owner_proof()] properly reflects the new ownership proof requirements in the signer-scoped transfer flow.


113-114: LGTM! Multi-claim test updated consistently.

Both transfer data generation calls now include the required signer parameter, maintaining consistency with the single-claim test pattern.


122-122: LGTM! Ownership proof updated for multi-claim.


132-132: LGTM! Bad signature test updated correctly.

The test maintains its original intent (verifying signature validation failure) while adapting to the new required signer parameter.


138-138: LGTM! Ownership proof updated for bad signature test.

applications/tari_app_utilities/src/transaction_executor.rs (2)

8-8: LGTM! Import added for new API usage.

The Executable trait import enables access to the new signers_iter method used in the refactored code below.


123-126: LGTM! Cleaner signer iteration approach.

The refactored code delegates signer iteration logic to transaction.signers_iter(), removing duplicate seal signer authorization checks and simplifying the auth scope construction. This improves maintainability by centralizing signer logic in the Transaction type.

crates/wallet/sdk/src/apis/stealth_outputs.rs (3)

32-32: LGTM! Import added for new field type.

The RistrettoPublicKeyBytes import supports the new required_signer field added to TransferStatementParams.


766-766: Breaking change: new required field added to TransferStatementParams.

The addition of required_signer: RistrettoPublicKeyBytes is a breaking change for any code constructing TransferStatementParams. This aligns with the PR's stated breaking changes requiring data directory deletion.


715-715: LGTM! Required signer properly propagated.

The params.required_signer is correctly passed to generate_transfer_statement, enabling signer-scoped stealth transfers as intended by the PR.

crates/template_test_tooling/src/wrapped_transaction.rs (1)

43-45: LGTM! Proper trait implementation via delegation.

The signers_iter implementation correctly delegates to the underlying transaction's method, maintaining consistency with other delegated methods in this wrapper.

crates/template_lib_types/src/max_bytes.rs (2)

39-41: LGTM! New empty constructor added.

The empty() constructor provides a clear way to create an empty MaxBytes instance, improving API clarity.


59-59: Verify no reliance on old Default behavior
No direct MaxBytes::default() usages found; manually audit any implicit Default::default() initializations of MaxBytes to ensure no code assumes a non-empty or zero‐filled buffer.

crates/wallet/sdk/src/key_managers/backend.rs (2)

6-6: LGTM! Import updated for new trait method.

The DerivedKeyIndex import is needed for the new derive_secret method signature.


22-22: Breaking change: new required method on WalletKeyStore trait.

The addition of derive_secret requires all trait implementors to provide an implementation. This aligns with the PR's refactoring of the key manager to use the wallet key store backend.

applications/tari_walletd/src/handlers/validator.rs (1)

182-199: Signing flow/error propagation LGTM; confirm context semantics.

Result-based chaining and dual-sign path are correct. Please confirm sign_with_context(..., builder.with_authorized_seal_signer()) both:

  • binds the claim key as an authorized seal signer, and
  • leaves the builder ready for the final account-key sign at Line 201.

Optional: add a debug log when the dual-sign path is taken for observability.

crates/wallet/crypto/src/stealth.rs (1)

59-61: Good: bind ownership proofs to outputs metadata.

Hashing outputs once via stealth_statement_metadata64 and passing it into owner-proof signing is correct and reduces malleability.

crates/p2p/src/conversions/transaction.rs (1)

813-818: Serialization and proto schema verified
required_signer is defined in StealthInputsStatement in crates/p2p/proto/transaction.proto:235. Ensure network/proto versioning gates handle older nodes missing this field.

applications/tari_walletd/src/handlers/accounts.rs (3)

537-545: Signer binding for claim burn is correct

You commit the required_signer (nonce key) into the transfer statement and sign the tx with the same key. This enforces signer presence at execution time without leaking the account key. LGTM.

Also applies to: 564-565


962-967: Good: explicit guard for owner_key_id presence

Clear error path for view-only accounts before building the transfer. LGTM.


990-995: Signer carried through from builder to signature

Using authorized_sealed_signer().build() and then signing with transfer.signing_key_branch/id keeps the tx signer aligned with the statement’s required_signer. LGTM.

crates/engine/tests/stealth.rs (1)

630-671: Great negative test for signer enforcement

Asserting AccessDeniedStealthTransferSigner when the tx is signed by the wrong key exercises the new policy well. LGTM.

crates/wallet/sdk/src/sdk.rs (1)

48-48: Key store integration looks correct

LocalKeyStore is consistently wired into KeyManagerApi and LocalSignerApi, and passed into StealthTransferApi. This aligns with the backend switch. LGTM.

Also applies to: 158-167, 171-175, 225-233

crates/template_test_tooling/src/template_test.rs (3)

181-182: Sensible default: auto-add proofs from signers

Defaulting auto_add_proofs_from_signers to true in tests matches the new signer requirement and reduces boilerplate. LGTM.


228-236: Clear toggles for signer-proof auto-injection

Enable/disable methods are straightforward and useful for test control. LGTM.


416-418: Rename to to_public_key_bytes looks good

Compact helper to get bytes; consistent with other usages. LGTM.

crates/engine/src/runtime/working_state.rs (1)

1561-1562: New flow adoption

Switch to validate_and_spend_stealth_utxos integrates signer and metadata validation cleanly. LGTM.

crates/wallet/sdk/src/apis/stealth_transfer.rs (1)

508-519: Threading required_signer into both fee and transfer statements

Good: both statements commit to the same required_signer, aligning with the new engine requirement. After applying the fix above, this remains consistent.

Also applies to: 592-609

crates/wallet/sdk/src/key_managers/local.rs (1)

36-51: Key derivation/signing flow looks correct

Deriving secrets via key_store for both Derived and Imported keys and signing with RistrettoSchnorr is sound. LGTM post-OsRng fix.

crates/template_test_tooling/src/support/stealth.rs (1)

45-66: Required signer propagation across helpers

Adding required_signer to mint/transfer data and threading it to create_transfer_statement aligns tests with the new engine checks. Looks consistent.

Also applies to: 116-135, 137-153, 166-173, 217-224

crates/engine_types/src/hash.rs (1)

11-14: serde_with::hex emits raw bytes for non-human-readable serializers
The local serialize uses Serializer::serialize_bytes when is_human_readable() is false, so any non-human-readable serde format (including your BOR-via-serde implementation) will emit raw bytes as expected.

Comment thread crates/engine_types/src/hash.rs
Comment thread crates/template_builtin/templates/faucet/src/lib.rs
Comment thread crates/template_lib/src/models/stealth.rs
Comment thread crates/template_test_tooling/src/template_test.rs
Comment thread crates/wallet/sdk/src/apis/stealth_transfer.rs
Comment thread crates/wallet/sdk/src/key_managers/local.rs
Comment thread crates/wallet/sdk/src/local_key_store.rs
@github-actions

Copy link
Copy Markdown

Test Results (CI)

475 tests  +3   464 ✅ +4   1h 35m 28s ⏱️ -43s
 78 suites ±0     0 💤 ±0 
  2 files   ±0    11 ❌  - 1 

For more details on these failures, see this check.

Results for commit 788b18a. ± Comparison against base commit 53bf250.

This pull request removes 5 and adds 8 tests. Note that renamed tests count towards both.
tari_ootle_wallet_crypto::output_statement ‑ it_create_a_valid_revealed_only_proof
tari_ootle_wallet_crypto::output_statement ‑ stealth_tests::it_creates_a_valid_statement
tari_ootle_wallet_crypto::output_statement ‑ stealth_tests::it_creates_a_valid_statement_with_revealed
tari_ootle_wallet_crypto::output_statement ‑ stealth_tests::it_creates_a_valid_statement_with_revealed_only
tari_ootle_wallet_crypto::output_statement ‑ stealth_tests::it_errors_for_noop_transfer
tari_engine::stealth ‑ transfer_fails_if_tx_signed_by_wrong_signer
tari_engine_types ‑ hash::tests::serialize_deserialize
tari_ootle_wallet_crypto::stealth_transfer_statement ‑ it_create_a_valid_revealed_only_proof
tari_ootle_wallet_crypto::stealth_transfer_statement ‑ stealth_tests::it_creates_a_valid_statement
tari_ootle_wallet_crypto::stealth_transfer_statement ‑ stealth_tests::it_creates_a_valid_statement_with_revealed
tari_ootle_wallet_crypto::stealth_transfer_statement ‑ stealth_tests::it_creates_a_valid_statement_with_revealed_only
tari_ootle_wallet_crypto::stealth_transfer_statement ‑ stealth_tests::it_errors_for_noop_transfer
tari_ootle_wallet_crypto::stealth_transfer_statement ‑ stealth_tests::it_fails_to_validate_if_outputs_are_replaced

@sdbondi
sdbondi force-pushed the stealth-malleability-fixes branch from 5338d47 to ddca5a5 Compare October 16, 2025 05:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx (1)

237-241: Consider removing redundant console.error.

The error is already being captured in the formError state and displayed to the user. The console.error on line 241 is redundant since the error information is surfaced via the UI.

Apply this diff to remove the redundant logging:

         setFormError({
           type: "general",
           message: `Failed to estimate fee: ${error}`,
         });
-        console.error("Fee estimation failed:", error);
         return;
crates/engine_types/src/hash.rs (1)

16-86: Docstrings still describe a 32-byte hash

All of these docs call out 32-byte lengths, but Hash64::LENGTH is 64. This mismatch will trip up anyone relying on the docs. Please update the wording to reflect the actual size (or drop the hard-coded number and just reference Self::LENGTH) so the documentation stays truthful.

-/// Representation of a 32-byte hash value
+/// Representation of a 64-byte hash value
@@
-    /// Panics if `N` is greater than Self::LENGTH (32)
+    /// Panics if `N` is greater than `Self::LENGTH`.
@@
-    /// Panics if `N` is greater than Self::LENGTH (32)
+    /// Panics if `N` is greater than `Self::LENGTH`.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 788b18a and ddca5a5.

📒 Files selected for processing (16)
  • applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx (6 hunks)
  • applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx (8 hunks)
  • bindings/package.json (1 hunks)
  • bindings/src/index.ts (1 hunks)
  • bindings/src/types/Hash64.ts (1 hunks)
  • bindings/src/types/StealthInputsStatement.ts (2 hunks)
  • crates/engine_types/src/hash.rs (1 hunks)
  • crates/state_store_rocksdb/src/codecs/small_bytes.rs (2 hunks)
  • crates/state_store_rocksdb/src/key.rs (0 hunks)
  • crates/template_lib/src/models/non_fungible.rs (3 hunks)
  • crates/template_lib/src/models/stealth.rs (2 hunks)
  • crates/template_lib_types/src/crypto/scalar.rs (3 hunks)
  • crates/template_lib_types/src/entity_id.rs (4 hunks)
  • crates/template_lib_types/src/hash.rs (3 hunks)
  • crates/template_lib_types/src/hex.rs (1 hunks)
  • crates/wallet/sdk/src/apis/stealth_transfer.rs (11 hunks)
💤 Files with no reviewable changes (1)
  • crates/state_store_rocksdb/src/key.rs
✅ Files skipped from review due to trivial changes (1)
  • crates/template_lib_types/src/entity_id.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/template_lib/src/models/stealth.rs
🧰 Additional context used
🧬 Code graph analysis (8)
crates/template_lib_types/src/hash.rs (1)
crates/template_lib_types/src/hex.rs (2)
  • fixed_bytes_from_hex (8-18)
  • write_hex_fmt (39-44)
crates/state_store_rocksdb/src/codecs/small_bytes.rs (1)
crates/template_lib_types/src/hex.rs (1)
  • write_hex_fmt (39-44)
crates/template_lib_types/src/crypto/scalar.rs (2)
crates/template_lib_types/src/hex.rs (1)
  • write_hex_fmt (39-44)
crates/template_lib_types/src/crypto/ristretto.rs (1)
  • is_zero (53-55)
bindings/src/types/StealthInputsStatement.ts (1)
bindings/src/types/RistrettoPublicKeyBytes.ts (1)
  • RistrettoPublicKeyBytes (6-6)
applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx (1)
applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx (1)
  • FormError (67-70)
crates/template_lib/src/models/non_fungible.rs (1)
crates/template_lib_types/src/hex.rs (1)
  • write_hex_fmt (39-44)
crates/engine_types/src/hash.rs (4)
crates/template_lib_types/src/hash.rs (17)
  • fmt (158-160)
  • fmt (171-173)
  • write_hex_fmt (67-69)
  • from_array (50-52)
  • into_array (54-56)
  • as_slice (58-60)
  • from_hex (62-65)
  • try_from_slice (71-73)
  • try_from (125-132)
  • try_from (138-140)
  • leading_bytes (80-86)
  • trailing_bytes (93-99)
  • as_ref (103-105)
  • from (109-111)
  • deref (146-148)
  • deref_mut (152-154)
  • serialize_deserialize (181-187)
crates/template_lib_types/src/hex.rs (1)
  • write_hex_fmt (39-44)
bindings/src/types/Hash64.ts (1)
  • Hash64 (6-6)
crates/tari_bor/src/lib.rs (2)
  • encode_into_std_writer (61-66)
  • to_value (96-98)
crates/wallet/sdk/src/apis/stealth_transfer.rs (6)
bindings/src/types/wallet-daemon-client/KeyBranch.ts (1)
  • KeyBranch (3-10)
bindings/src/types/wallet-daemon-client/KeyId.ts (1)
  • KeyId (3-3)
crates/wallet/sdk/src/apis/stealth_outputs.rs (1)
  • key_manager_api (361-378)
crates/wallet/sdk/src/sdk.rs (1)
  • key_manager_api (159-167)
crates/wallet/sdk/src/models/account.rs (2)
  • owner_key_id (33-35)
  • owner_key_id (91-93)
crates/wallet/sdk/src/models/key.rs (9)
  • derived (297-299)
  • from (118-123)
  • from (143-148)
  • from (172-177)
  • from (181-186)
  • from (190-195)
  • from (199-204)
  • from (276-278)
  • from (282-284)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test
  • GitHub Check: check nightly
  • GitHub Check: clippy
🔇 Additional comments (25)
applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx (3)

69-69: LGTM!

The formError state initialization is correct and follows React best practices.


124-174: Good UX pattern for error clearing.

Clearing formError on user interactions (form field changes and badge toggle) provides a smooth user experience by dismissing error messages when the user attempts to correct the issue.


325-325: LGTM!

The formError prop is correctly passed to FormStep, enabling error display in the form UI.

applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx (3)

67-70: LGTM!

The FormError type is well-structured with a discriminated union on the type field, enabling type-safe error handling for different form fields.


162-262: Good error display positioning.

The DisplayFormError component is strategically placed before the address input, amount input, and general action buttons, ensuring errors appear near the relevant UI elements for better UX.


276-284: LGTM!

The DisplayFormError helper component is simple and effective. It conditionally renders error messages based on type matching, providing clean separation of concerns.

bindings/src/types/StealthInputsStatement.ts (2)

3-3: LGTM: Import correctly added.

The import for RistrettoPublicKeyBytes is properly added to support the new required_signer field.


18-21: All construction sites correctly include the required_signer field.

Verification of the Rust codebase confirms that all places constructing StealthInputsStatement properly include the required_signer field:

  • crates/wallet/crypto/src/stealth.rs (lines 94–98, 108–112): Direct struct literal construction with required_signer
  • crates/template_builtin/templates/faucet/src/lib.rs (line 32): Factory method new_revealed_only() which requires required_signer as a parameter
  • Factory methods new() and new_revealed_only() enforce required_signer through their function signatures
  • TypeScript bindings correctly reflect the required field
  • Protobuf schema and conversions properly handle the field

The security fix is complete and consistently applied.

bindings/package.json (1)

3-3: LGTM!

The version bump from 1.18.0 to 1.18.1 appropriately reflects the addition of the new Hash64 type to the public API surface.

crates/template_lib/src/models/non_fungible.rs (2)

176-181: LGTM!

Returning a borrowed reference instead of copying the 32-byte array is a good optimization. This avoids an unnecessary allocation and aligns with Rust's borrowing best practices.


289-289: LGTM!

The refactoring to use the centralized write_hex_fmt helper ensures consistent hex formatting across the codebase.

crates/state_store_rocksdb/src/codecs/small_bytes.rs (1)

119-119: LGTM!

Using the centralized write_hex_fmt helper eliminates code duplication and ensures consistent hex formatting behavior.

bindings/src/index.ts (1)

54-54: LGTM!

The Hash64 export is correctly positioned alphabetically and makes the new type available to consumers of the bindings package.

crates/template_lib_types/src/hex.rs (2)

41-41: LGTM!

Removing the debug flag ? from the format string is correct. The debug flag was unnecessary and could have produced unexpected output. The format {:02x} correctly produces lowercase hex with zero-padding.


46-56: LGTM!

The new test verifies that byte sequences are correctly formatted as hex strings, ensuring the bytes_to_hex function works as expected.

crates/template_lib_types/src/hash.rs (3)

31-34: LGTM!

The updated imports correctly bring in the centralized write_hex_fmt utility for hex formatting.


68-68: LGTM!

Delegating to the centralized write_hex_fmt helper eliminates code duplication while maintaining the same formatting behavior.


159-159: LGTM!

The Display implementation now uses the centralized hex formatting utility, ensuring consistent behavior across the codebase.

crates/template_lib_types/src/crypto/scalar.rs (2)

28-30: LGTM!

The new is_zero() method is a useful utility that efficiently checks whether the scalar is the zero value. The implementation is consistent with similar methods in the codebase (e.g., RistrettoPublicKeyBytes::is_zero()).


84-84: LGTM!

The Display implementation now uses the centralized write_hex_fmt helper, ensuring consistent hex formatting across the codebase.

crates/wallet/sdk/src/apis/stealth_transfer.rs (5)

491-508: Past critical issue has been resolved.

The double-derivation issue flagged in the previous review (calling next_derived_key_index twice) no longer exists in the current code. The nonce key is now derived exactly once at line 501, and the resulting signing_key_id is consistently used for both:

  1. Computing required_signer (lines 504-507)
  2. Returning in TransferOutput (line 638)

The design is correct: required_signer is the transaction-level signer (committed in statements), while spend_key (always account key) proves UTXO ownership. When no revealed funds are involved, using a nonce key for required_signer preserves privacy while still spending UTXOs with the account key.


510-521: LGTM: Fee statement correctly uses account key for spending and required_signer for transaction authorization.

The parameters are correctly set:

  • spend_key_branch: KeyBranch::Account and spend_key_id: owner_key_id prove UTXO ownership
  • required_signer: required_signer_pk enforces transaction-level authorization (can be nonce key for privacy)

581-598: LGTM: Transfer statement follows the same correct pattern.

Consistent with the fee statement: account key for UTXO spending, required_signer for transaction authorization.


637-639: LGTM: Signing key fields properly returned for subsequent transaction signing.

The signing_key_branch and signing_key_id are correctly propagated to the caller, enabling them to sign the transaction with the same key committed as required_signer in the statements. This completes the signer-scoped stealth transfer flow.

Also applies to: 757-759


883-884: LGTM: Error propagation correctly added.

The KeyManagerApi error variant enables proper error propagation from key manager operations.

Comment thread applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx Outdated
Comment thread bindings/src/types/Hash64.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx (1)

275-283: LGTM! Consider optional accessibility enhancement.

The DisplayFormError helper component has correct conditional rendering logic and appropriate styling. The early returns prevent unnecessary renders.

Optional enhancement: Consider adding ARIA attributes for better accessibility:

 function DisplayFormError({ forType, formError }: { forType: FormError["type"]; formError?: FormError | null }) {
   if (!formError) return null;
   if (formError.type !== forType) return null;
   return (
-    <Typography color="error" sx={{ mb: 2 }}>
+    <Typography color="error" sx={{ mb: 2 }} role="alert" aria-live="polite">
       {formError.message}
     </Typography>
   );
 }
crates/engine_types/src/hash.rs (1)

95-99: Replace Self::LENGTH with literal 64 in trait impl for clarity.

Using Self::LENGTH in the trait implementation header is non-standard. While it may compile in recent Rust versions, the idiomatic approach is to use the literal value for better clarity and compatibility.

Apply this diff:

-impl From<[u8; Self::LENGTH]> for Hash64 {
-    fn from(hash: [u8; Self::LENGTH]) -> Self {
+impl From<[u8; 64]> for Hash64 {
+    fn from(hash: [u8; 64]) -> Self {
         Self::from_array(hash)
     }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ddca5a5 and eb10847.

📒 Files selected for processing (7)
  • applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx (7 hunks)
  • crates/engine_types/src/hash.rs (1 hunks)
  • crates/wallet/sdk/src/apis/stealth_outputs.rs (4 hunks)
  • crates/wallet/sdk/src/storage.rs (1 hunks)
  • crates/wallet/sdk_services/src/account_monitor/monitor.rs (4 hunks)
  • crates/wallet/sdk_services/src/utxo_scanner/scanner_round.rs (2 hunks)
  • crates/wallet/storage_sqlite/src/reader.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
crates/wallet/storage_sqlite/src/reader.rs (2)
crates/wallet/sdk/src/storage.rs (2)
  • stealth_outputs_count_by_status (235-240)
  • general (131-136)
crates/wallet/storage_sqlite/src/writer.rs (14)
  • stealth_outputs (1065-1081)
  • stealth_outputs (1152-1152)
  • stealth_outputs (1153-1153)
  • stealth_outputs (1182-1182)
  • stealth_outputs (1183-1183)
  • stealth_outputs (1195-1195)
  • stealth_outputs (1196-1196)
  • stealth_outputs (1215-1215)
  • stealth_outputs (1216-1216)
  • accounts (590-594)
  • accounts (632-635)
  • accounts (897-900)
  • accounts (960-963)
  • accounts (1059-1062)
crates/engine_types/src/hash.rs (3)
crates/template_lib_types/src/hash.rs (18)
  • fmt (158-160)
  • fmt (171-173)
  • write_hex_fmt (67-69)
  • from_array (50-52)
  • into_array (54-56)
  • as_slice (58-60)
  • from_hex (62-65)
  • try_from_slice (71-73)
  • try_from (125-132)
  • try_from (138-140)
  • leading_bytes (80-86)
  • trailing_bytes (93-99)
  • as_ref (103-105)
  • from (109-111)
  • from_str (117-119)
  • deref (146-148)
  • deref_mut (152-154)
  • serialize_deserialize (181-187)
crates/template_lib_types/src/hex.rs (1)
  • write_hex_fmt (39-44)
crates/tari_bor/src/lib.rs (2)
  • encode_into_std_writer (61-66)
  • to_value (96-98)
crates/wallet/sdk/src/storage.rs (4)
crates/wallet/storage_sqlite/src/reader.rs (1)
  • stealth_outputs_count_by_status (861-886)
bindings/src/types/ComponentAddress.ts (1)
  • ComponentAddress (6-6)
bindings/src/types/ResourceAddress.ts (1)
  • ResourceAddress (6-6)
bindings/src/types/OutputStatus.ts (1)
  • OutputStatus (3-3)
crates/wallet/sdk/src/apis/stealth_outputs.rs (6)
bindings/src/types/PedersenCommitmentBytes.ts (1)
  • PedersenCommitmentBytes (6-6)
bindings/src/types/RistrettoPublicKeyBytes.ts (1)
  • RistrettoPublicKeyBytes (6-6)
bindings/src/types/ComponentAddress.ts (1)
  • ComponentAddress (6-6)
bindings/src/types/ResourceAddress.ts (1)
  • ResourceAddress (6-6)
crates/wallet/sdk/src/apis/accounts.rs (1)
  • count (167-171)
bindings/src/types/OutputStatus.ts (1)
  • OutputStatus (3-3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: test
  • GitHub Check: check nightly
  • GitHub Check: file licenses
  • GitHub Check: check stable
  • GitHub Check: clippy
  • GitHub Check: machete
🔇 Additional comments (17)
crates/wallet/sdk_services/src/account_monitor/monitor.rs (1)

52-52: Approve rename with breaking change notice
Grammar improved with a consistent rename; no references to the old names remain. However, this is a breaking change to the public API—ensure all external callers are updated and bump the crate’s major version.

applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx (4)

67-70: LGTM! Clean error type definition.

The FormError type is well-structured with a discriminated union for error categories and a message field. This provides good type safety for error handling across the form.


58-58: LGTM! Proper prop integration.

The formError prop is correctly added as optional to FormStepProps and properly destructured in the component. This maintains backward compatibility while enabling error display functionality.

Also applies to: 83-83


161-161: Verify handling of "fee" error type.

The DisplayFormError component is used for "address", "amount", and "general" error types, but the FormError type also includes "fee". Since the fee field is disabled (line 251), fee errors may not be expected in this component.

Please confirm whether:

  1. Fee errors should be displayed somewhere (e.g., near the fee input at lines 239-257)
  2. The "fee" error type is intended for use in a different context or component

If fee errors should be displayed here, add:

// Around line 238, before the fee TextField
<DisplayFormError forType="fee" formError={formError} />

Also applies to: 213-213, 261-261


120-120: Past review comment has been addressed.

The debugging console.log(formError) statement mentioned in the previous review has been successfully removed. This line now contains only the return statement.

crates/wallet/sdk_services/src/utxo_scanner/scanner_round.rs (1)

112-128: Good call on guarding the stream with local state

Deriving unspent_count first and only asking the network for spent entries when we actually have local unspent records keeps the sync lean without sacrificing correctness. ✅

crates/wallet/storage_sqlite/src/reader.rs (1)

861-886: Count accessor matches existing query patterns

The new count implementation reuses the established subselect style and keeps error handling consistent, so the API can rely on it without surprises.

crates/wallet/sdk/src/storage.rs (1)

235-241: Trait surface cleanly extended

Nice to see the reader trait stay in sync with the SQLite implementation; downstream callers get the count helper without extra boilerplate.

crates/wallet/sdk/src/apis/stealth_outputs.rs (2)

303-312: Lightweight count helper fits the read path

Leveraging the store reader directly keeps the API thin while giving callers the unspent count they need for optimisations like the scanner tweak.


721-778: Required signer threading looks solid

Propagating required_signer through TransferStatementParams and into generate_transfer_statement lines up with the crypto changes and keeps the balancing checks untouched.

crates/engine_types/src/hash.rs (7)

4-14: LGTM!

The imports are appropriate and all are utilized in the implementation.


17-17: Consider adding borsh::BorshDeserialize if bidirectional borsh serialization is needed.

The struct derives borsh::BorshSerialize but not borsh::BorshDeserialize. If Hash64 needs to be deserialized from borsh-encoded data, add the missing derive.

Apply this diff if bidirectional borsh support is required:

-#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, borsh::BorshSerialize)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, borsh::BorshSerialize, borsh::BorshDeserialize)]

26-43: LGTM!

The constructors and accessors are correctly implemented as const methods where appropriate.


45-52: LGTM!

The hex parsing correctly validates the input length before decoding.


54-60: LGTM!

The hex formatting correctly delegates to the shared write_hex_fmt helper, and try_from_slice appropriately delegates to the TryFrom implementation.


101-154: LGTM!

The trait implementations are correct and follow Rust best practices. The Display implementation correctly delegates to the shared hex formatting helper.


156-178: LGTM!

The error type is well-defined with thiserror, and the test comprehensively validates both serialization correctness and the expected BOR value representation.

Comment thread crates/engine_types/src/hash.rs
@sdbondi
sdbondi force-pushed the stealth-malleability-fixes branch from eb10847 to 13d2a28 Compare October 16, 2025 06:43
@sdbondi
sdbondi merged commit fbce88a into tari-project:development Oct 16, 2025
2 checks passed
@sdbondi
sdbondi deleted the stealth-malleability-fixes branch October 16, 2025 06:44
sdbondi added a commit to tari-project/ootle-wallet-cli that referenced this pull request Oct 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants