Skip to content

feat: make key manager stateless#7550

Merged
SWvheerden merged 53 commits intotari-project:developmentfrom
SWvheerden:sw_change_key_manager
Nov 19, 2025
Merged

feat: make key manager stateless#7550
SWvheerden merged 53 commits intotari-project:developmentfrom
SWvheerden:sw_change_key_manager

Conversation

@SWvheerden
Copy link
Copy Markdown
Collaborator

@SWvheerden SWvheerden commented Oct 20, 2025

Description

Changes the key manager to make it stateless.
This allows the key manager to only work from view and spend keys.
Removes all async from the key manager.

Motivation and Context

Simplify the usage of the key manager

Summary by CodeRabbit

  • New Features

    • Hardware ledger wallet support and new wallet types.
  • Improvements

    • Key-management and transaction APIs now operate synchronously for more predictable behavior.
    • Memo construction, transaction signing, and error reporting refined for more reliable submissions and clearer diagnostics.
  • Bug Fixes

    • Improved memo creation and wallet address error handling to reduce failed transactions.
  • Refactor

    • Large internal modernization of key-management, wallet, and test infrastructure (performance and maintainability).

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 20, 2025

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (1)
  • base_layer/wallet/src/transaction_service/storage/sqlite_db.rs

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Large refactor converting async, memory-backed key-manager flows to a synchronous KeyManager; consolidates key/id/branch/error types, moves many public APIs from async→sync, replaces TransactionKeyManagerBranch with LedgerKeyBranch, updates memo/encryption helpers, and adjusts many application crates and tests to the new interfaces. (50 words)

Changes

Cohort / File(s) Summary
Key manager core & API
base_layer/transaction_components/src/key_manager/manager.rs, base_layer/transaction_components/src/key_manager/mod.rs, base_layer/transaction_components/src/key_manager/interface.rs, base_layer/transaction_components/src/key_manager/error.rs, base_layer/transaction_components/src/key_manager/inner.rs
Adds new synchronous KeyManager, removes old TransactionKeyManagerInner and memory-key-manager factory, reshapes trait surface from async→sync, consolidates errors into KeyManagerError, reorgs public exports.
Key id & wallet types
base_layer/transaction_components/src/key_manager/key_id.rs, base_layer/transaction_components/src/key_manager/wallet_types.rs
Adds TariKeyId, TariKeyAndId, SerializedKeyString and WalletType/wallet structs with parsing/display helpers and ledger-key support.
Transaction components (async→sync)
base_layer/transaction_components/src/transaction_builder/*, base_layer/transaction_components/src/coinbase_builder.rs, base_layer/transaction_components/src/multisig/*, base_layer/transaction_components/src/offline_signing/*, base_layer/transaction_components/src/one_sided.rs, base_layer/transaction_components/src/transaction_input.rs, base_layer/transaction_components/src/transaction_kernel.rs
Converts many public APIs and helpers from async→sync (builders, coinbase, multisig, offline-signing, one-sided flows, kernel/message builders); updates call sites to sync KeyManager and adjusts ownership/signature shapes.
Memo & one-sided encryption
base_layer/transaction_components/src/transaction_components/memo_field.rs, base_layer/transaction_components/src/transaction_components/one_sided.rs
Removes deprecated MemoField open helpers; replaces shared-secret encryption helpers with public-key-based flows and adds DH-stealth hasher variants.
Ledger branches & common types
base_layer/common_types/src/key_branches.rs (deleted), applications/minotari_ledger_wallet/common/src/common_types.rs, applications/minotari_ledger_wallet/comms/src/accessor_methods.rs, applications/minotari_ledger_wallet/comms/examples/ledger_demo/main.rs
Deletes old TransactionKeyManagerBranch, introduces LedgerKeyBranch (serde, reduced variants), and updates ledger accessor signatures, examples, and tests to use LedgerKeyBranch.
Applications: console wallet & gRPC
applications/minotari_console_wallet/*, applications/minotari_app_grpc/Cargo.toml, applications/minotari_app_grpc/src/conversions/mod.rs
Switches many WalletType uses to LegacyWalletType, adapts memo construction to MemoField::new_open_from_string, updates key-manager trait bounds to legacy interfaces, makes tari_comms optional and gates peer module on base_node feature.
Miner / merge-mining proxy / node
applications/minotari_miner/*, applications/minotari_merge_mining_proxy/*, applications/minotari_node/src/grpc/base_node_grpc_server.rs
Replaces memory DB key manager with KeyManager::new_random(), converts block-template/coinbase flows and miner APIs from async→sync, updates signatures to accept &KeyManager, removes awaits.
Tests & helpers (wide)
many files under base_layer/core/src/test_helpers/*, base_layer/core/tests/**/*, base_layer/transaction_components/src/test_helpers/*, base_layer/transaction_components/src/transaction_components/covenants/*, etc.
Converts 40+ test helpers and many tests from async→sync, replaces MemoryDbKeyManager with KeyManager::new_random(), changes key-manager params from &mut&, and removes many .await callsites.
Cargo / deps
applications/minotari_ledger_wallet/common/Cargo.toml, applications/minotari_console_wallet/Cargo.toml, applications/minotari_app_grpc/Cargo.toml, base_layer/transaction_components/Cargo.toml, base_layer/common_types/Cargo.toml, applications/minotari_miner/Cargo.toml
Adds/removes workspace deps, makes tari_comms optional, adds serde derive for ledger common, removes some crates (async-trait/strum) and adjusts cargo metadata.
Error consolidation & mappings
applications/minotari_console_wallet/src/automation/error.rs, applications/minotari_merge_mining_proxy/src/error.rs, base_layer/transaction_components/src/transaction_builder/error.rs, base_layer/transaction_components/src/key_manager/error.rs
Replaces older KeyManagerServiceError/CoreKeyManagerError variants with unified KeyManagerError and updates From impls across modules.
Misc UI / small API changes
applications/minotari_app_grpc/src/conversions/mod.rs, applications/minotari_console_wallet/src/grpc/*, applications/minotari_console_wallet/src/ui/*
Adds feature gating for exports, updates memo/address callsites, adjusts trait bounds to legacy key-manager interfaces, and swaps WalletType→LegacyWalletType in UI components and state APIs.

Sequence Diagram(s)

sequenceDiagram
    participant Old as Old async flow
    participant Factory as create_memory_db_key_manager()
    participant New as KeyManager::new_random()
    participant Builder as TransactionBuilder

    rect rgb(220,238,255)
      Note over Old,Factory: Previous async pattern
      Old->>Factory: create_memory_db_key_manager().await
      Factory-->>Old: MemoryDbKeyManager
      Old->>Builder: TransactionBuilder::new(...).await
      Builder->>Factory: key ops (.await)
    end

    rect rgb(221,255,221)
      Note over Old,New: New synchronous pattern
      Old->>New: KeyManager::new_random()
      New-->>Old: KeyManager
      Old->>Builder: TransactionBuilder::new(...)
      Builder->>New: key ops (sync)
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Key areas needing extra attention:

  • base_layer/transaction_components/src/key_manager/manager.rs — cryptographic derivations, signing, ledger wrappers, and trait implementations.
  • Widespread async→sync API changes: TransactionBuilder, offline-signing, multisig, coinbase generation — verify all callers and error propagation.
  • New TariKeyId parsing/display and LedgerKeyBranch mapping; remaining references to removed TransactionKeyManagerBranch.
  • Tests/helpers: verify &mut → & changes preserve semantics and lifetimes.
  • Memo/encryption API shifts: ensure consumers use public-key based encryption helpers and removed shared-secret helpers are not referenced.

Possibly related PRs

Suggested reviewers

  • hansieodendaal

Poem

🐰 I hopped through code both old and new,
Keys rearranged, branches trimmed for view.
Sync beats async, ledger seeds now sprout,
I nibbled bugs and pushed the changes out.
🥕 Hooray — a carrot for each passing route!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: make key manager stateless' directly reflects the main objective of this PR: removing state from the key manager to make it stateless and simplify its usage.
Docstring Coverage ✅ Passed Docstring coverage is 94.34% which is sufficient. The required threshold is 80.00%.

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.

@SWvheerden SWvheerden changed the title wip: change key manager feat: change key manager Nov 10, 2025
@SWvheerden SWvheerden changed the title feat: change key manager feat: make key manager stateless Nov 10, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

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 (4)
base_layer/common_types/src/seeds/cipher_seed.rs (1)

135-147: Critical: Incomplete breaking change - remaining CipherSeed::new() calls in dependent test files.

The breaking change from new() to random() is incomplete. The following test files still call the deprecated CipherSeed::new() and will fail to compile on non-wasm32 targets:

  • base_layer/wallet/tests/other/mod.rs:380let alice_seed = CipherSeed::new();
  • base_layer/wallet/tests/other/mod.rs:417let recovery_seed = CipherSeed::new();
  • base_layer/wallet/tests/other/mod.rs:434let recovery_seed = CipherSeed::new();
  • base_layer/wallet/tests/other/mod.rs:700CipherSeed::new(),

All must be updated to use CipherSeed::random().

base_layer/transaction_components/src/transaction_components/one_sided.rs (1)

56-67: Address documentation inconsistency and consider ownership pattern consistency.

Two issues identified:

  1. Outdated documentation (line 62): The comment references "Diffie-Hellman shared secret" but the function accepts &CompressedPublicKey. This should be corrected.

  2. Inconsistent ownership: diffie_hellman_stealth_domain_hasher_dhke consumes CommsDHKE by value while diffie_hellman_stealth_domain_hasher takes &CompressedPublicKey by reference. Since both only call .as_bytes(), consider taking CommsDHKE by reference for consistency and to avoid unnecessary moves.

Apply this diff to fix the documentation:

-/// Stealth address domain separated hasher using Diffie-Hellman shared secret
+/// Stealth address domain separated hasher using a compressed public key
 pub fn diffie_hellman_stealth_domain_hasher(diffie_hellman: &CompressedPublicKey) -> DomainSeparatedHash<Blake2b<U64>> {

Consider this diff to make ownership consistent (if CommsDHKE implements AsRef<[u8]> or has .as_bytes() on a reference):

-pub fn diffie_hellman_stealth_domain_hasher_dhke(diffie_hellman: CommsDHKE) -> DomainSeparatedHash<Blake2b<U64>> {
+pub fn diffie_hellman_stealth_domain_hasher_dhke(diffie_hellman: &CommsDHKE) -> DomainSeparatedHash<Blake2b<U64>> {
     WalletHasher::new_with_label("stealth_address")
         .chain(diffie_hellman.as_bytes())
         .finalize()
 }
applications/minotari_miner/src/run_miner.rs (1)

371-379: Breaking change: Function signature modified.

The function signature has changed:

  • Parameter type: &mut MemoryDbKeyManager&KeyManager
  • Mutability: mutable reference → immutable reference

This is a breaking change that affects any code calling this function. The PR description states "Breaking Changes: None", but this should be updated to reflect the actual breaking changes.

applications/minotari_ledger_wallet/comms/src/accessor_methods.rs (1)

229-293: Potential code duplication: ledger_get_public_key_legacy vs ledger_get_public_key.

Both functions appear to have identical implementations (lines 229-260 and 262-293). If ledger_get_public_key_legacy is intended for backward compatibility during a transition period, consider:

  1. Adding a doc comment explaining the distinction and deprecation timeline
  2. Having one delegate to the other to avoid duplication
  3. If they're truly identical, removing the _legacy variant

Example refactoring:

+/// Legacy function maintained for backward compatibility. Use `ledger_get_public_key` instead.
+#[deprecated(since = "0.x.0", note = "Use ledger_get_public_key instead")]
 pub fn ledger_get_public_key_legacy(
     account: u64,
     index: u64,
     branch: LedgerKeyBranch,
 ) -> Result<RistrettoPublicKey, LedgerDeviceError> {
-    // ... full implementation
+    ledger_get_public_key(account, index, branch)
 }
🧹 Nitpick comments (10)
base_layer/core/src/mempool/priority/prioritized_transaction.rs (1)

152-164: Convert to synchronous tests.

Since the tests no longer perform any async operations, the #[tokio::test] attribute and async fn declarations are unnecessary overhead. Consider converting these to regular synchronous tests.

Apply this diff to convert the tests to synchronous:

-    #[tokio::test]
-    async fn fee_increases_priority() {
+    #[test]
+    fn fee_increases_priority() {
         let key_manager = KeyManager::new_random().unwrap();
         let weighting = TransactionWeight::latest();
         let epoch = u64::MAX / 2;
         let tx = create_tx_with_fee(2 * uT, &key_manager);
         let p1 = FeePriority::new(&tx, epoch, tx.calculate_weight(&weighting).expect("Failed to get tx")).unwrap();

         let tx = create_tx_with_fee(3 * uT, &key_manager);
         let p2 = FeePriority::new(&tx, epoch, tx.calculate_weight(&weighting).expect("Failed to get tx")).unwrap();

         assert!(p2 > p1);
     }

-    #[tokio::test]
-    async fn age_increases_priority() {
+    #[test]
+    fn age_increases_priority() {
         let key_manager = KeyManager::new_random().unwrap();
         let weighting = TransactionWeight::latest();
         let epoch = u64::MAX / 2;
         let tx = create_tx_with_fee(2 * uT, &key_manager);
         let p1 = FeePriority::new(&tx, epoch, tx.calculate_weight(&weighting).expect("Failed to get tx")).unwrap();

         let tx = create_tx_with_fee(2 * uT, &key_manager);
         let p2 = FeePriority::new(
             &tx,
             epoch - 1,
             tx.calculate_weight(&weighting).expect("Failed to get tx"),
         )
         .unwrap();

         assert!(p2 > p1);
     }

Also applies to: 166-183

base_layer/transaction_components/src/transaction_components/covenants/fields.rs (1)

401-441: Consider removing async test markers.

All test functions in this file are marked with #[tokio::test] and async fn, but none of them contain any .await calls or async operations after the migration to synchronous KeyManager::new_random(). The async markers add unnecessary runtime overhead.

You can simplify these tests by removing the async keyword and changing #[tokio::test] to #[test]:

-#[tokio::test]
-async fn it_returns_true_if_eq() {
+#[test]
+fn it_returns_true_if_eq() {

Apply this pattern to all test functions:

  • it_returns_true_if_eq (line 401)
  • it_returns_false_if_not_eq (line 443)
  • it_returns_true_if_eq_input (line 495)
  • it_constructs_challenge_using_consensus_encoding (line 570)
  • it_retrieves_the_value_as_ref (line 617)

Also applies to: 443-488, 495-538, 570-610, 617-641

base_layer/common_types/src/seeds/cipher_seed.rs (1)

149-157: Platform-specific API inconsistency.

The wasm32 target retains the new() method name while non-wasm32 uses random(). This creates a platform-specific API inconsistency where cross-platform code would need conditional compilation to call the correct method name.

Consider whether this inconsistency is necessary, or if both platforms should use the same method name for consistency.

base_layer/transaction_components/src/consensus/consensus_constants.rs (1)

221-235: Add documentation and tests for the new method.

The implementation is logically sound and the assumption is valid—all networks include a consensus version starting at height 0, so the expect() is safe. However, the method lacks documentation and unit tests, which are important for maintainability.

Consider these improvements:

  1. Add a doc comment explaining that it returns the most recent consensus version active at the given height.

  2. Add unit tests covering edge cases (height = 0, height between versions, height after all versions).

Example doc comment:

/// Returns the consensus constants for the given network at the specified block height.
/// 
/// This selects the most recent version of consensus constants that is active at the given
/// height by filtering versions where `effective_from_height <= height` and returning
/// the one with the maximum `effective_from_height`.
pub fn for_network_at_height(network: Network, height: u64) -> Self {
base_layer/core/src/chain_storage/blockchain_database.rs (1)

3188-3820: LGTM! Test changes are consistent with synchronous KeyManager.

The removal of .await calls from test helper functions (create_main_chain, create_orphan_chain, create_chained_blocks) is correct and aligns with the migration to synchronous KeyManager. The changes are systematic and consistent across all test functions.

Optional refactor: Tests are still marked as async fn with #[tokio::test], which is acceptable but could be changed to synchronous tests with #[test] if there are no remaining async operations. However, keeping them async is harmless and may provide flexibility for future changes.

base_layer/transaction_components/src/test_helpers/test_helpers_functions.rs (1)

592-592: KeyManager cloning pattern in test helpers.

The key_manager.clone() calls are necessary because TransactionBuilder::new takes ownership of the key manager, while these test helper functions receive a reference to avoid moving. This is acceptable for test code, though it's worth noting that if KeyManager is expensive to clone, it could impact test performance.

Also applies to: 644-644

base_layer/transaction_components/src/transaction_components/test.rs (1)

143-149: Consider keeping structured error assertions.

The error handling has changed from pattern matching on TransactionError variants to string comparison. While acceptable for tests, string comparisons are more brittle and can break with minor error message changes.

Consider maintaining structured error assertions when possible:

-            assert_eq!(
-                e.to_string(),
-                "KeyManager encountered an error: Transaction error: `Error building the transaction: Value provided \
-                 is outside the range allowed by the range proof`"
-            );
+            assert!(e.to_string().contains("Value provided is outside the range allowed"));

This approach is more resilient to minor message formatting changes while still validating the core error condition.

base_layer/core/src/blocks/pre_mine/mod.rs (1)

813-866: Prefer reusing a single key manager for all pre-mine outputs

Calling KeyManager::new_random() inside the loop means every pre-mine output is derived from a freshly seeded wallet type. Besides the extra cost of reinitializing the factories each time, this reseeding makes repeated runs of create_pre_mine_genesis_block_info non-deterministic compared to the previous implementation that reused one memory key manager and produced stable output/offset derivations. Consider constructing a single KeyManager up front (or threading one in from the caller) and reusing it across iterations so the derivations remain reproducible while avoiding the repeated setup cost.

applications/minotari_ledger_wallet/comms/src/accessor_methods.rs (1)

466-533: Clarify the distinction between DH shared secret functions.

Two DH shared secret functions are present:

  • ledger_get_dh_shared_secret_dhke (lines 466-499) returns DiffieHellmanSharedSecret<RistrettoPublicKey>
  • ledger_get_dh_shared_secret (lines 501-533) returns CompressedPublicKey

While the return types differ, consider adding doc comments explaining when to use each variant, as the naming alone may not clearly communicate the semantic difference to consumers.

base_layer/transaction_components/src/key_manager/manager.rs (1)

1-1341: Consider modularizing this large file.

The manager.rs file is 1,341 lines, which may impact maintainability. While the current organization is functional, consider splitting into smaller modules:

  • Core key management operations
  • Ledger-specific operations
  • Signature operations
  • Encryption/decryption utilities

This would improve code navigation and make the module boundaries more explicit.

Copy link
Copy Markdown
Contributor

@hansieodendaal hansieodendaal left a comment

Choose a reason for hiding this comment

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

These should make wasm and ledger wallet compile - they fail CI

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
applications/minotari_ledger_wallet/common/Cargo.toml (1)

5-5: Fix typo in comment.

The comment contains "msut" which should be "must".

Apply this diff to fix the typo:

-#Note: `rust-version` is not specified here as this msut be kept in sync with the ledger-app-builder
+#Note: `rust-version` is not specified here as this must be kept in sync with the ledger-app-builder
♻️ Duplicate comments (1)
base_layer/transaction_components/src/key_manager/key_id.rs (1)

84-94: Fix derived key parsing to match Display output (duplicate issue).

This is the same critical round-trip bug flagged in the previous review. The Derived branch requires parts.len() >= 3, but Display::fmt (line 171) outputs "derived.{key}" where key may be a simple string without dots (e.g., "foo"). This means TariKeyId::Derived { key: SerializedKeyString::from("foo") } will display as "derived.foo" (2 parts), which cannot be parsed back.

Note that the test at line 359-362 explicitly expects "derived.onlytwo" to fail, creating a design inconsistency.

Apply this diff to allow single-segment keys:

                 DERIVED_KEY_BRANCH => {
-                    if parts.len() < 3 {
+                    if parts.len() < 2 {
                         return Err("Wrong derived format".to_string());
                     };

And update the test to reflect the corrected behavior:

-        // Derived must have at least 3 parts
-        assert_eq!(
-            TariKeyId::from_str("derived.onlytwo").unwrap_err(),
-            "Wrong derived format"
-        );
+        // Derived must have at least 2 parts (branch + key)
+        let parsed = TariKeyId::from_str("derived.onlytwo").unwrap();
+        assert!(matches!(parsed, TariKeyId::Derived { .. }));
+        assert_eq!(parsed.to_string(), "derived.onlytwo");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11b12bd and bf548f3.

📒 Files selected for processing (5)
  • applications/minotari_ledger_wallet/common/Cargo.toml (1 hunks)
  • applications/minotari_ledger_wallet/common/src/common_types.rs (2 hunks)
  • applications/minotari_ledger_wallet/comms/src/accessor_methods.rs (9 hunks)
  • base_layer/common_types/src/seeds/cipher_seed.rs (6 hunks)
  • base_layer/transaction_components/src/key_manager/key_id.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7432
File: integration_tests/src/ffi/ffi_import.rs:351-356
Timestamp: 2025-08-22T10:22:27.310Z
Learning: The PR "feat!: remove comms from wallet" is a breaking change PR where backward compatibility is intentionally not maintained. The FFI functions `comms_config_create` and `comms_config_destroy` were renamed to `wallet_db_config_create` and `wallet_db_config_destroy` as part of the intentional breaking changes.
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7432
File: applications/minotari_app_grpc/proto/wallet.proto:1933-1948
Timestamp: 2025-08-26T06:28:13.374Z
Learning: In PR #7432 "feat!: remove comms from wallet", the HttpPeer message in the wallet.proto should only include url, last_latency, and is_online fields. The base node public_key and node_id fields mentioned in the original PR summary were removed from the scope and should not be included.
📚 Learning: 2025-07-15T12:23:14.650Z
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7284
File: applications/minotari_console_wallet/src/automation/commands.rs:0-0
Timestamp: 2025-07-15T12:23:14.650Z
Learning: In applications/minotari_console_wallet/src/automation/commands.rs, the consistent error handling pattern for command execution is to use match statements that: 1) On success: log with debug!, print user feedback, and push tx_id to tx_ids vector for monitoring, 2) On error: print error message with eprintln! using the format "{CommandName} error! {}", rather than using .unwrap() which would panic.

Applied to files:

  • applications/minotari_ledger_wallet/comms/src/accessor_methods.rs
📚 Learning: 2025-06-16T14:49:31.831Z
Learnt from: martinserts
Repo: tari-project/tari PR: 7122
File: base_layer/wallet/src/transaction_service/offline_signing/one_sided_signer.rs:0-0
Timestamp: 2025-06-16T14:49:31.831Z
Learning: In Tari's one-sided transaction offline signing implementation, the script_keys vector in the script offset calculation should only include input.output.script_key_id, not output script keys, change output script keys, or recipient script keys. This is by design for the one-sided transaction protocol.

Applied to files:

  • applications/minotari_ledger_wallet/comms/src/accessor_methods.rs
🧬 Code graph analysis (2)
base_layer/transaction_components/src/key_manager/key_id.rs (2)
applications/minotari_ledger_wallet/common/src/common_types.rs (3)
  • fmt (157-159)
  • from_str (144-153)
  • as_str (130-138)
base_layer/transaction_key_manager/src/legacy_key_manager/interface.rs (6)
  • fmt (220-243)
  • fmt (276-278)
  • from_str (132-215)
  • from (264-266)
  • from (270-272)
  • as_str (258-260)
applications/minotari_ledger_wallet/common/src/common_types.rs (1)
base_layer/transaction_components/src/key_manager/key_id.rs (3)
  • fmt (169-195)
  • fmt (222-224)
  • from_str (78-164)
🔇 Additional comments (17)
base_layer/transaction_components/src/key_manager/key_id.rs (4)

37-73: LGTM!

The constants and TariKeyId enum are well-defined with appropriate variants and derives. The integration with LedgerKeyBranch aligns with the broader key manager refactoring.


167-196: Display implementation is correct given the parsing fix.

The formatting logic is clean and consistent. Once the Derived parsing bug (lines 84-94) is fixed, all variants will round-trip correctly through Display and FromStr.


198-243: LGTM!

SerializedKeyString is a clean newtype wrapper with appropriate helper methods and conversions. TariKeyAndId is straightforward. The lack of Serde traits on TariKeyAndId appears intentional for runtime-only usage.


245-406: Comprehensive test coverage with one update needed.

The test suite thoroughly covers display, parsing, round-trips, and error cases for all variants. Once the Derived parsing bug is fixed, the test at lines 359-362 should be updated as suggested in the earlier comment.

applications/minotari_ledger_wallet/common/Cargo.toml (1)

12-12: LGTM!

The serde dependency is correctly configured with default-features = false and the derive feature, which is appropriate for a no_std environment commonly used in ledger applications.

applications/minotari_ledger_wallet/common/src/common_types.rs (3)

104-112: LGTM!

The LedgerKeyBranch enum is well-defined with appropriate derives for serialization (Serialize, Deserialize) and comparison operations. The explicit discriminants ensure stable wire format representation.


156-159: LGTM!

The Display implementation correctly delegates to as_str(), following the suggestion from the past review comment. This approach is clean and maintainable.


130-154: Roundtrip consistency verified—no issues found.

All string and byte conversions are consistent and bidirectional:

  • String representations in as_str() and FromStr match exactly for all 5 variants
  • Byte values in the enum definition match as_byte() and from_byte() patterns
  • Display impl delegates to as_str(), maintaining consistency with FromStr
  • PartialEq and Eq traits are derived, enabling proper assertions

The code exhibits correct roundtrip behavior without defects.

applications/minotari_ledger_wallet/comms/src/accessor_methods.rs (7)

26-26: LGTM!

The import of LedgerKeyBranch from minotari_ledger_wallet_common is correct and aligns with the PR's objective to standardize on this branch type.


46-49: LGTM!

The ScriptSignatureKey enum correctly uses LedgerKeyBranch for the Managed variant, maintaining type consistency across the ledger wallet codebase.


228-259: LGTM!

The ledger_get_public_key function signature correctly updated to accept LedgerKeyBranch instead of the previous TransactionKeyManagerBranch. The implementation properly serializes the branch byte value for the ledger command.


330-408: LGTM!

The ledger_get_script_offset function correctly migrated to use LedgerKeyBranch for both script_key_indexes and sender_offset_indexes parameters. The serialization logic properly encodes branch bytes for the ledger device.


466-507: LGTM!

The ledger_get_raw_schnorr_signature function correctly updated to use LedgerKeyBranch for both private_key_branch and nonce_branch parameters, maintaining consistency with the PR's refactoring objectives.


510-548: LGTM!

The ledger_get_script_schnorr_signature function properly migrated to use LedgerKeyBranch for the private_key_branch parameter, aligning with the stateless key manager design.


431-463: Breaking change verified—all callers properly updated.

The return type change from DiffieHellmanSharedSecret<RistrettoPublicKey> to CompressedPublicKey has been correctly handled across all callers:

  • key_manager.rs:379 – wrapper function maps the new return type
  • key_manager.rs:661 – consumes wrapper result as CompressedPublicKey
  • ledger_demo/main.rs:224 – example code compatible with new type

Optional cleanup: Remove unused import of DiffieHellmanSharedSecret from accessor_methods.rs:34.

base_layer/common_types/src/seeds/cipher_seed.rs (2)

415-419: Default implementation correctly updated.

The change from Self::new() to Self::random() in the Default implementation addresses the WASM build failure mentioned in the past review comment. The implementation correctly delegates to the renamed method for both wasm32 and non-wasm32 targets.


474-480: Test updates are consistent with the API change.

All test call sites have been correctly updated from CipherSeed::new() to CipherSeed::random(), maintaining the same test coverage for encryption, decryption, mnemonic conversion, and passphrase handling.

Also applies to: 583-592, 619-642

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (5)
applications/minotari_ledger_wallet/common/Cargo.toml (1)

12-12: Inherit serde version from workspace.

A past review comment flagged that serde should use workspace = true to inherit the version from the workspace definition, which is the standard practice in this monorepo. This ensures consistent versioning across all packages.

Apply this diff:

-serde = { version = "1",  default-features = false, features = ["derive"] }
+serde = { workspace = true, default-features = false, features = ["derive"] }
base_layer/transaction_components/src/key_manager/key_id.rs (3)

23-29: Remove duplicated license disclaimer.

Line 23 repeats the BSD disclaimer that is already present on Lines 15-21; please delete the duplicate block.

-
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
-// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-

85-93: Allow single-segment derived keys.

TariKeyId::Derived displays as derived.foo, but Line 85 still rejects strings where the derived key has only one segment because of the parts.len() < 3 guard. Round-tripping valid IDs therefore fails (same issue noted previously). Please relax the guard to accept two parts and adjust the tests accordingly, e.g.:

-                DERIVED_KEY_BRANCH => {
-                    if parts.len() < 3 {
+                DERIVED_KEY_BRANCH => {
+                    if parts.len() < 2 {
                         return Err("Wrong derived format".to_string());
                     };

And update parse_error_cases so "derived.onlytwo" succeeds:

-        assert_eq!(
-            TariKeyId::from_str("derived.onlytwo").unwrap_err(),
-            "Wrong derived format"
-        );
+        let derived = TariKeyId::from_str("derived.onlytwo").unwrap();
+        assert_eq!(derived.to_string(), "derived.onlytwo");

107-109: Fix typo in DH encrypted data error.

Line 108 still returns "Wrong encryted data format". Please correct the spelling to “encrypted”.

-                        return Err("Wrong encryted data format".to_string());
+                        return Err("Wrong encrypted data format".to_string());
applications/minotari_console_wallet/src/automation/commands.rs (1)

931-933: Correct the ledger_key parameter for nonce variables.

Two of the three get_random_key calls incorrectly pass ledger_key=true:

  • Line 931: script_nonce_key should use get_random_key(None, false) (it's a nonce, not a key requiring ledger management)
  • Line 933: sender_offset_nonce should use get_random_key(None, false) (same reason)
  • Line 932: sender_offset_key correctly uses get_random_key(None, true)

The pattern throughout the codebase reserves ledger_key=true for actual cryptographic keys and uses false for nonces.

Apply this diff:

-let script_nonce_key = key_manager_service.get_random_key(None, true)?;
+let script_nonce_key = key_manager_service.get_random_key(None, false)?;
 let sender_offset_key = key_manager_service.get_random_key(None, true)?;
-let sender_offset_nonce = key_manager_service.get_random_key(None, true)?;
+let sender_offset_nonce = key_manager_service.get_random_key(None, false)?;
🧹 Nitpick comments (1)
base_layer/transaction_components/src/transaction_builder/builder.rs (1)

1036-1192: Consider simplifying test signatures.

The tests continue to use #[tokio::test] and async fn even though all TransactionBuilder operations are now synchronous. While this works correctly, you could simplify by converting test functions to regular synchronous tests unless they need async for other operations.

Example:

-    #[tokio::test]
-    async fn change_edge_case() {
+    #[test]
+    fn change_edge_case() {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf548f3 and ec1ef52.

📒 Files selected for processing (6)
  • applications/minotari_console_wallet/src/automation/commands.rs (31 hunks)
  • applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs (3 hunks)
  • applications/minotari_ledger_wallet/common/Cargo.toml (1 hunks)
  • applications/minotari_ledger_wallet/comms/src/accessor_methods.rs (9 hunks)
  • base_layer/transaction_components/src/key_manager/key_id.rs (1 hunks)
  • base_layer/transaction_components/src/transaction_builder/builder.rs (58 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7432
File: integration_tests/src/ffi/ffi_import.rs:351-356
Timestamp: 2025-08-22T10:22:27.310Z
Learning: The PR "feat!: remove comms from wallet" is a breaking change PR where backward compatibility is intentionally not maintained. The FFI functions `comms_config_create` and `comms_config_destroy` were renamed to `wallet_db_config_create` and `wallet_db_config_destroy` as part of the intentional breaking changes.
📚 Learning: 2025-08-11T15:37:17.354Z
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7387
File: base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs:2896-2927
Timestamp: 2025-08-11T15:37:17.354Z
Learning: The `update_accumulated_difficulty` method in `base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs` is migration-specific code that only runs during a once-off migration when existing ChainHeader data is present in LMDB. The consistency guarantees are upheld by the calling function `process_accumulated_data_for_height`, so additional validation within this method is redundant.

Applied to files:

  • base_layer/transaction_components/src/key_manager/key_id.rs
📚 Learning: 2025-10-03T10:21:49.027Z
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7514
File: base_layer/transaction_components/src/transaction_builder/builder.rs:704-752
Timestamp: 2025-10-03T10:21:49.027Z
Learning: In base_layer/transaction_components/src/transaction_builder/builder.rs, the change_encrypted_data_if_fee_changed helper should only update fees in MemoField instances that already contain a fee (when get_fee() returns Some). It should not set fees in memos where get_fee() returns None, as not all outputs require fees in their memos.

Applied to files:

  • base_layer/transaction_components/src/key_manager/key_id.rs
  • applications/minotari_console_wallet/src/automation/commands.rs
  • base_layer/transaction_components/src/transaction_builder/builder.rs
📚 Learning: 2025-07-15T12:23:14.650Z
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7284
File: applications/minotari_console_wallet/src/automation/commands.rs:0-0
Timestamp: 2025-07-15T12:23:14.650Z
Learning: In applications/minotari_console_wallet/src/automation/commands.rs, the consistent error handling pattern for command execution is to use match statements that: 1) On success: log with debug!, print user feedback, and push tx_id to tx_ids vector for monitoring, 2) On error: print error message with eprintln! using the format "{CommandName} error! {}", rather than using .unwrap() which would panic.

Applied to files:

  • applications/minotari_console_wallet/src/automation/commands.rs
  • applications/minotari_ledger_wallet/comms/src/accessor_methods.rs
📚 Learning: 2025-06-16T14:49:31.831Z
Learnt from: martinserts
Repo: tari-project/tari PR: 7122
File: base_layer/wallet/src/transaction_service/offline_signing/one_sided_signer.rs:0-0
Timestamp: 2025-06-16T14:49:31.831Z
Learning: In Tari's one-sided transaction offline signing implementation, the script_keys vector in the script offset calculation should only include input.output.script_key_id, not output script keys, change output script keys, or recipient script keys. This is by design for the one-sided transaction protocol.

Applied to files:

  • applications/minotari_console_wallet/src/automation/commands.rs
  • applications/minotari_ledger_wallet/comms/src/accessor_methods.rs
  • base_layer/transaction_components/src/transaction_builder/builder.rs
📚 Learning: 2025-09-08T08:43:50.910Z
Learnt from: MCozhusheck
Repo: tari-project/tari PR: 7478
File: base_layer/p2p/src/signature_verification/gpg_keys/seed_peers_http.asc:1-15
Timestamp: 2025-09-08T08:43:50.910Z
Learning: In the Tari codebase, temporary test PGP keys for signature verification are acceptable during development phases when they will be replaced with production keys before final release. The maintainers prefer to keep the implementation simple during drafts rather than over-engineering with test-only conditionals for temporary keys.

Applied to files:

  • applications/minotari_console_wallet/src/automation/commands.rs
📚 Learning: 2025-04-23T05:56:30.985Z
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 6974
File: base_layer/wallet/src/output_manager_service/service.rs:2026-2031
Timestamp: 2025-04-23T05:56:30.985Z
Learning: When setting the fee parameter in `output_to_self` method in the Tari wallet, use an accurate fee estimate instead of `fee_per_gram`. The `input_selection.as_final_fee()` method provides a good initial estimate, and the final fee can be obtained later from `stp.get_fee_amount()`.

Applied to files:

  • applications/minotari_console_wallet/src/automation/commands.rs
🧬 Code graph analysis (3)
base_layer/transaction_components/src/key_manager/key_id.rs (2)
applications/minotari_ledger_wallet/common/src/common_types.rs (3)
  • fmt (157-159)
  • from_str (144-153)
  • as_str (130-138)
base_layer/transaction_key_manager/src/legacy_key_manager/interface.rs (6)
  • fmt (220-243)
  • fmt (276-278)
  • from_str (132-215)
  • from (264-266)
  • from (270-272)
  • as_str (258-260)
applications/minotari_console_wallet/src/automation/commands.rs (5)
base_layer/transaction_components/src/transaction_components/one_sided.rs (1)
  • public_key_to_output_encryption_key (36-43)
base_layer/transaction_components/src/transaction_components/memo_field.rs (1)
  • new_open_from_string (353-355)
base_layer/wallet/src/output_manager_service/service.rs (2)
  • spend_backup_pre_mine_utxo (1342-1525)
  • new (139-185)
base_layer/wallet/src/transaction_service/service.rs (2)
  • spend_backup_pre_mine_utxo (1838-1887)
  • new (197-267)
base_layer/transaction_components/src/transaction_components/unblinded_output.rs (1)
  • from_wallet_output (174-202)
base_layer/transaction_components/src/transaction_builder/builder.rs (4)
base_layer/wallet/src/output_manager_service/service.rs (2)
  • new (139-185)
  • zero (2938-2945)
base_layer/transaction_components/src/test_helpers/test_helpers_functions.rs (1)
  • new (106-127)
base_layer/transaction_components/src/transaction_components/wallet_output.rs (10)
  • new (89-140)
  • commitment_mask_key_id (642-644)
  • script (671-673)
  • sender_offset_public_key (712-714)
  • minimum_value_promise (746-748)
  • input_data (693-695)
  • encrypted_data (742-744)
  • features (660-662)
  • covenant (682-684)
  • commitment (776-778)
base_layer/transaction_components/src/transaction_components/one_sided.rs (2)
  • public_key_to_output_encryption_key (36-43)
  • public_key_to_output_spending_key (46-53)
⏰ 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). (10)
  • GitHub Check: test (nextnet, nextnet)
  • GitHub Check: test (testnet, esmeralda)
  • GitHub Check: test (mainnet, stagenet)
  • GitHub Check: cargo check with msrv
  • GitHub Check: cargo check with stable
  • GitHub Check: ledger build tests
  • GitHub Check: ci
  • GitHub Check: wasm build tests
  • GitHub Check: Cucumber tests / Base Layer
  • GitHub Check: Cucumber tests / FFI
🔇 Additional comments (16)
base_layer/transaction_components/src/transaction_builder/builder.rs (5)

75-247: LGTM! Clean async-to-sync conversion in public API.

The conversion of TransactionBuilder::new, add_recipient, add_stealth_recipient, with_input, and with_output from async to sync is well-executed. All key manager calls now use synchronous interfaces with proper error propagation.


277-563: LGTM! Helper methods correctly converted to synchronous.

The private helper methods (get_pre_build_change_output, add_change_if_required, create_change_memo, build_change) are all properly converted to synchronous operations. The change output generation logic maintains correctness while removing async complexity.


565-676: LGTM! Nonce calculation and fee update logic correctly converted.

The calculate_total_nonce_and_total_public_excess and change_encrypted_data_if_fee_changed methods are properly converted to synchronous operations. The signature change for change_encrypted_data_if_fee_changed to accept key_manager: &KM as a parameter aligns well with the stateless design.


680-930: LGTM! Core build() method successfully converted to synchronous.

The main build() method is comprehensively converted from async to sync. All cryptographic operations (partial signatures, kernel offsets, script offsets) now use synchronous key manager calls. The transaction assembly logic remains intact while removing async complexity.


1193-1987: LGTM! All tests properly updated to synchronous KeyManager.

All test cases are correctly updated to use the synchronous KeyManager API. The tests cover:

  • Single/multiple recipients
  • Stealth addresses
  • Fee validation
  • View-only wallet recovery
  • Historic transaction recovery

All assertions and validations remain intact while successfully removing async complexity.

applications/minotari_console_wallet/src/automation/commands.rs (7)

825-834: LGTM: Consistent memo construction pattern.

The updated MemoField::new_open_from_string calls paired with the detect_tx_metadata helper provide a clean, consistent approach to memo construction. Error handling follows the established pattern of using eprintln! with continue rather than panicking.

Also applies to: 1177-1186, 1710-1722, 1746-1755, 1774-1780, 1940-1946, 1969-1977, 2001-2009, 2032-2040, 2526-2538


934-957: LGTM: Key manager API correctly updated for stateless operation.

The synchronous key manager operations correctly use:

  • Key ID references for Diffie-Hellman shared secret computation
  • TariKeyId::LedgerKey with LedgerKeyBranch::PreMine for pre-mine outputs
  • Direct signing operations without async overhead

These changes align well with the PR's goal of simplifying key manager usage.


1812-1812: LGTM: Synchronous UnblindedOutput conversion.

The updated UnblindedOutput::from_wallet_output calls correctly pass a reference to the key manager service and operate synchronously, consistent with the stateless key manager refactor.

Also applies to: 1883-1883


1420-1509: LGTM: PreMineSigs signature operations correctly refactored.

The signature generation logic properly uses:

  • Synchronous sign_with_nonce_and_challenge with key ID references
  • import_key and verify_mask for commitment validation
  • get_script_offset with key ID slices
  • public_key_to_output_encryption_key for encryption key derivation

All operations are now synchronous and follow the established error handling pattern.


2169-2172: LGTM: Synchronous view and spend key access.

Direct synchronous access to get_view_key(), get_spend_key(), and get_private_view_key() demonstrates one of the benefits of the stateless key manager refactor—eliminating async overhead for simple key retrieval operations.


2847-2863: LGTM: Well-structured transaction type detection helper.

The detect_tx_metadata helper centralizes the logic for determining whether a transaction is a self-payment or payment to others. The fallback to TxType::PaymentToOther when wallet addresses are unavailable is a safe default.


2750-2750: LGTM: Synchronous message signing operations.

The updated sign_message_with_spend_key and sign_script_message_with_spend_key calls operate synchronously and correctly handle the optional sender offset parameter, consistent with the stateless key manager design.

Also applies to: 2776-2777

applications/minotari_ledger_wallet/comms/src/accessor_methods.rs (4)

26-26: LGTM! Consistent type migration to LedgerKeyBranch.

The import changes and enum updates align with the PR objective to make the key manager stateless. The addition of the Derived variant to ScriptSignatureKey enables direct private key usage alongside managed keys.

Also applies to: 34-34, 47-48


107-107: LGTM! Parameter types consistently updated.

All function signatures have been uniformly migrated from TransactionKeyManagerBranch to LedgerKeyBranch, which is consistent with the stateless key manager refactor.

Also applies to: 231-231, 334-336, 469-471, 513-513


52-636: ****

All public functions in this file are actively used. The verification shows every function has confirmed usage:

  • verify_ledger_application, ledger_get_app_name, ledger_get_version: used in ledger_demo examples
  • ledger_get_public_key, ledger_get_script_signature, ledger_get_script_offset, ledger_get_dh_shared_secret, ledger_get_raw_schnorr_signature, ledger_get_script_schnorr_signature, ledger_get_one_sided_metadata_signature: used in base_layer/transaction_components key_manager
  • ledger_get_public_spend_key, ledger_get_view_key: used in console_wallet initialization

The premise that functions are unused is incorrect. Ignore this review comment.

Likely an incorrect or invalid review comment.


431-463: Type change is functionally compatible; no misuse detected.

The return type change from DiffieHellmanSharedSecret<RistrettoPublicKey> to CompressedPublicKey (which is CompressedKey<RistrettoPublicKey>) is valid because both represent compressed curve points. Verification shows:

  • key_manager.rs: Function signature already updated to return CompressedPublicKey (line 376), and the direct return at line 381 is type-compatible.
  • ledger_demo/main.rs: Correctly uses .as_bytes().to_vec().to_hex() for serialization (line 225).
  • No misuse: Neither usage treats the shared point as an authentication key; the point is only serialized or directly returned as expected.

While "CompressedPublicKey" is semantically imprecise for a DH shared point, it's mathematically equivalent and causes no functional issues.

})
}

fn add_offset_to_key(
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.

This name is not 100% clear. I am not sure what a better name is though

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.

It should also be clear that it saves the key. e.g. add_offset_to_key_and_import

))
}

fn ledger_get_public_key_wrapper(
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.

Perhaps all of these ledger methods should move to a new ledger file.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (8)
base_layer/transaction_components/src/key_manager/manager.rs (6)

153-153: Inconsistent domain string construction.

This line uses .as_bytes().to_vec() while line 433 uses the same approach, but Rust's b"..." literal syntax is more idiomatic and efficient.

Apply this diff for consistency:

-        let domain = "KEY_MANAGER_private_key".as_bytes().to_vec();
+        let domain = b"KEY_MANAGER_private_key".to_vec();

Based on past review comments.


164-183: Method name could be clearer about its behavior.

The method add_offset_to_key creates a new encrypted key ID but doesn't clearly indicate this in its name. Consider renaming to something like derive_offset_key or create_offset_key_id to better reflect that it returns a new TariKeyId rather than mutating the input.

Based on past review comments.


194-429: Consider organizing ledger methods into a separate module.

The file contains numerous ledger_*_wrapper methods (lines 194-429) that are all conditionally compiled. These methods follow a consistent pattern and could be better organized in a separate module (e.g., manager/ledger.rs) to improve maintainability and readability of the core KeyManager implementation.

Based on past review comments.


433-433: Use byte string literal for domain constant.

For consistency and efficiency, use Rust's byte string literal syntax.

Apply this diff:

-        let domain = "KEY_MANAGER_private_key".as_bytes().to_vec();
+        let domain = b"KEY_MANAGER_private_key".to_vec();

Based on past review comments.


151-152: Sensitive key material should be zeroized.

The pvt_bytes variable holds sensitive private key material in memory without zeroization. Consider using Zeroizing<Vec<u8>> or the Hidden wrapper to ensure the bytes are securely erased when dropped.

Based on past review comments.


432-432: Sensitive decryption key material should be zeroized.

The private_decryption_key variable holds sensitive key bytes without zeroization. Use Zeroizing<Vec<u8>> or retain the Hidden wrapper to prevent key material from lingering in memory.

Based on past review comments.

applications/minotari_console_wallet/src/automation/commands.rs (2)

825-843: Memo creation for PreMineSpendBackupUtxo follows wallet metadata conventions

Using MemoField::new_open_from_string together with detect_tx_metadata(&wallet, &args.recipient_address).await ensures the memo’s TxType reflects payment-to-self vs payment-to-other, and the error path uses eprintln! + break instead of panicking, matching the established command error‑handling pattern.

Based on learnings


931-977: Use non‑ledger keys for nonce values in pre‑mine party step

script_nonce_key and sender_offset_nonce are ephemeral nonces, but all three calls use get_random_key(None, true) which flags them as ledger keys; only sender_offset_key should be ledger‑managed, while nonces should be created with the non‑ledger flag (as in other call sites across the codebase).

I recommend:

-                    let script_nonce_key = key_manager_service.get_random_key(None, true)?;
-                    let sender_offset_key = key_manager_service.get_random_key(None, true)?;
-                    let sender_offset_nonce = key_manager_service.get_random_key(None, true)?;
+                    let script_nonce_key = key_manager_service.get_random_key(None, false)?;
+                    let sender_offset_key = key_manager_service.get_random_key(None, true)?;
+                    let sender_offset_nonce = key_manager_service.get_random_key(None, false)?;

TariKeyId::LedgerKey { branch: LedgerKeyBranch::PreMine, .. }, get_public_key_at_key_id, and sign_script_message usage looks correct for the actual script key.

🧹 Nitpick comments (3)
base_layer/transaction_components/src/key_manager/manager.rs (2)

473-1275: Consider adding documentation for public trait methods.

The TransactionKeyManagerInterface implementation lacks doc comments on its public methods. Adding documentation would help users understand:

  • Expected behavior and guarantees
  • Parameter meanings and constraints
  • Return value semantics
  • Potential error conditions

While the trait itself may have documentation, implementation-specific details (e.g., Ledger vs. software key handling) would benefit from inline comments.


103-471: Consider modularizing the KeyManager implementation.

The KeyManager implementation (~1340 lines) combines several concerns:

  • Core key management (encryption, derivation)
  • Ledger integration (multiple wrapper methods)
  • Metadata signature operations
  • Recovery and encryption helpers

Consider organizing this into submodules:

  • manager/core.rs - main struct and core operations
  • manager/ledger.rs - ledger-specific wrappers
  • manager/signatures.rs - signature-related operations
  • manager/recovery.rs - key recovery and encryption

This would improve maintainability as the codebase grows.

base_layer/transaction_components/src/coinbase_builder.rs (1)

93-98: Consider removing the unused KeyManagerServiceError variant.

After the migration to the synchronous KeyManager API, the KeyManagerServiceError variant on line 95 appears to be unused. The From<KeyManagerError> implementation on lines 106-110 suggests that all key manager errors now map to KeyManagerError.

If confirmed unused, apply this diff:

     KeyManagerError(String),
-    #[error("Key manager service error: `{0}`")]
-    KeyManagerServiceError(String),
     #[error("Conversion error: {0}")]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9af76b4 and 88e0ffc.

📒 Files selected for processing (6)
  • applications/minotari_console_wallet/src/automation/commands.rs (31 hunks)
  • base_layer/core/src/blocks/pre_mine/mod.rs (6 hunks)
  • base_layer/core/src/test_helpers/mod.rs (3 hunks)
  • base_layer/transaction_components/src/coinbase_builder.rs (30 hunks)
  • base_layer/transaction_components/src/key_manager/interface.rs (1 hunks)
  • base_layer/transaction_components/src/key_manager/manager.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • base_layer/core/src/test_helpers/mod.rs
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7432
File: integration_tests/src/ffi/ffi_import.rs:351-356
Timestamp: 2025-08-22T10:22:27.310Z
Learning: The PR "feat!: remove comms from wallet" is a breaking change PR where backward compatibility is intentionally not maintained. The FFI functions `comms_config_create` and `comms_config_destroy` were renamed to `wallet_db_config_create` and `wallet_db_config_destroy` as part of the intentional breaking changes.
📚 Learning: 2025-08-18T14:27:34.527Z
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7421
File: base_layer/core/src/base_node/sync/header_sync/synchronizer.rs:774-781
Timestamp: 2025-08-18T14:27:34.527Z
Learning: In the header sync synchronizer (base_layer/core/src/base_node/sync/header_sync/synchronizer.rs), non-banable errors from the sync process are handled by the calling function, not within the sync methods themselves. This means that internal state errors like `current_valid_chain_tip_header()` returning `None` will be properly categorized and handled upstream without incorrectly banning peers.

Applied to files:

  • base_layer/transaction_components/src/coinbase_builder.rs
📚 Learning: 2025-06-16T14:49:31.831Z
Learnt from: martinserts
Repo: tari-project/tari PR: 7122
File: base_layer/wallet/src/transaction_service/offline_signing/one_sided_signer.rs:0-0
Timestamp: 2025-06-16T14:49:31.831Z
Learning: In Tari's one-sided transaction offline signing implementation, the script_keys vector in the script offset calculation should only include input.output.script_key_id, not output script keys, change output script keys, or recipient script keys. This is by design for the one-sided transaction protocol.

Applied to files:

  • base_layer/transaction_components/src/coinbase_builder.rs
  • base_layer/core/src/blocks/pre_mine/mod.rs
  • applications/minotari_console_wallet/src/automation/commands.rs
  • base_layer/transaction_components/src/key_manager/manager.rs
  • base_layer/transaction_components/src/key_manager/interface.rs
📚 Learning: 2025-10-03T10:21:49.027Z
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7514
File: base_layer/transaction_components/src/transaction_builder/builder.rs:704-752
Timestamp: 2025-10-03T10:21:49.027Z
Learning: In base_layer/transaction_components/src/transaction_builder/builder.rs, the change_encrypted_data_if_fee_changed helper should only update fees in MemoField instances that already contain a fee (when get_fee() returns Some). It should not set fees in memos where get_fee() returns None, as not all outputs require fees in their memos.

Applied to files:

  • base_layer/transaction_components/src/coinbase_builder.rs
  • applications/minotari_console_wallet/src/automation/commands.rs
📚 Learning: 2025-07-15T12:23:14.650Z
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7284
File: applications/minotari_console_wallet/src/automation/commands.rs:0-0
Timestamp: 2025-07-15T12:23:14.650Z
Learning: In applications/minotari_console_wallet/src/automation/commands.rs, the consistent error handling pattern for command execution is to use match statements that: 1) On success: log with debug!, print user feedback, and push tx_id to tx_ids vector for monitoring, 2) On error: print error message with eprintln! using the format "{CommandName} error! {}", rather than using .unwrap() which would panic.

Applied to files:

  • applications/minotari_console_wallet/src/automation/commands.rs
📚 Learning: 2025-09-08T08:43:50.910Z
Learnt from: MCozhusheck
Repo: tari-project/tari PR: 7478
File: base_layer/p2p/src/signature_verification/gpg_keys/seed_peers_http.asc:1-15
Timestamp: 2025-09-08T08:43:50.910Z
Learning: In the Tari codebase, temporary test PGP keys for signature verification are acceptable during development phases when they will be replaced with production keys before final release. The maintainers prefer to keep the implementation simple during drafts rather than over-engineering with test-only conditionals for temporary keys.

Applied to files:

  • applications/minotari_console_wallet/src/automation/commands.rs
📚 Learning: 2025-08-11T15:37:17.354Z
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7387
File: base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs:2896-2927
Timestamp: 2025-08-11T15:37:17.354Z
Learning: The `update_accumulated_difficulty` method in `base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs` is migration-specific code that only runs during a once-off migration when existing ChainHeader data is present in LMDB. The consistency guarantees are upheld by the calling function `process_accumulated_data_for_height`, so additional validation within this method is redundant.

Applied to files:

  • applications/minotari_console_wallet/src/automation/commands.rs
📚 Learning: 2025-04-23T05:56:30.985Z
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 6974
File: base_layer/wallet/src/output_manager_service/service.rs:2026-2031
Timestamp: 2025-04-23T05:56:30.985Z
Learning: When setting the fee parameter in `output_to_self` method in the Tari wallet, use an accurate fee estimate instead of `fee_per_gram`. The `input_selection.as_final_fee()` method provides a good initial estimate, and the final fee can be obtained later from `stp.get_fee_amount()`.

Applied to files:

  • applications/minotari_console_wallet/src/automation/commands.rs
📚 Learning: 2025-08-22T10:22:27.310Z
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7432
File: integration_tests/src/ffi/ffi_import.rs:351-356
Timestamp: 2025-08-22T10:22:27.310Z
Learning: The PR "feat!: remove comms from wallet" is a breaking change PR where backward compatibility is intentionally not maintained. The FFI functions `comms_config_create` and `comms_config_destroy` were renamed to `wallet_db_config_create` and `wallet_db_config_destroy` as part of the intentional breaking changes.

Applied to files:

  • base_layer/transaction_components/src/key_manager/interface.rs
🧬 Code graph analysis (5)
base_layer/transaction_components/src/coinbase_builder.rs (6)
base_layer/transaction_key_manager/src/legacy_key_manager/wrapper.rs (2)
  • key_manager (141-143)
  • new (107-121)
base_layer/transaction_key_manager/src/legacy_key_manager/inner.rs (1)
  • key_manager (105-107)
base_layer/transaction_components/src/key_manager/error.rs (4)
  • from (59-61)
  • from (65-67)
  • from (71-73)
  • from (77-79)
base_layer/transaction_components/src/transaction_builder/builder.rs (2)
  • build (680-930)
  • new (75-106)
base_layer/transaction_components/src/test_helpers/test_helpers_functions.rs (2)
  • new (106-127)
  • default (202-213)
base_layer/transaction_components/src/key_manager/manager.rs (1)
  • new_random (139-144)
base_layer/core/src/blocks/pre_mine/mod.rs (6)
base_layer/transaction_key_manager/src/legacy_key_manager/wrapper.rs (1)
  • key_manager (141-143)
base_layer/transaction_key_manager/src/legacy_key_manager/inner.rs (1)
  • key_manager (105-107)
base_layer/transaction_components/src/key_manager/manager.rs (1)
  • new_random (139-144)
base_layer/transaction_components/src/key_manager/wallet_types.rs (1)
  • new_random (52-56)
base_layer/transaction_key_manager/src/legacy_key_manager/tari_key_manager.rs (1)
  • random (77-84)
base_layer/transaction_components/src/transaction_components/transaction_kernel.rs (1)
  • build_kernel_signature_challenge (169-183)
applications/minotari_console_wallet/src/automation/commands.rs (3)
infrastructure/tari_script/src/lib.rs (1)
  • push_pubkey_script (54-56)
base_layer/transaction_components/src/transaction_components/one_sided.rs (1)
  • public_key_to_output_encryption_key (36-43)
base_layer/transaction_components/src/transaction_components/memo_field.rs (1)
  • new_open_from_string (353-355)
base_layer/transaction_components/src/key_manager/manager.rs (4)
applications/minotari_ledger_wallet/comms/src/accessor_methods.rs (7)
  • ledger_get_dh_shared_secret (431-463)
  • ledger_get_one_sided_metadata_signature (551-636)
  • ledger_get_public_key (228-259)
  • ledger_get_raw_schnorr_signature (466-507)
  • ledger_get_script_offset (330-408)
  • ledger_get_script_schnorr_signature (510-548)
  • ledger_get_script_signature (262-327)
base_layer/common_types/src/encryption.rs (2)
  • decrypt_bytes_integral_nonce (53-75)
  • encrypt_bytes_integral_nonce (78-104)
base_layer/transaction_components/src/transaction_components/one_sided.rs (3)
  • diffie_hellman_stealth_domain_hasher (63-67)
  • public_key_to_output_encryption_key (36-43)
  • public_key_to_output_spending_key (46-53)
base_layer/transaction_components/src/key_manager/interface.rs (5)
  • get_random_key (62-66)
  • get_public_key_at_key_id (69-69)
  • get_script_signature (119-126)
  • get_script_offset (184-188)
  • get_one_sided_metadata_signature (202-212)
base_layer/transaction_components/src/key_manager/interface.rs (3)
base_layer/transaction_key_manager/src/legacy_key_manager/wrapper.rs (33)
  • key_manager (141-143)
  • get_random_key (169-176)
  • get_public_key_at_key_id (178-180)
  • create_encrypted_key (182-189)
  • get_commitment (191-198)
  • verify_mask (200-208)
  • get_view_key (210-212)
  • get_private_view_key (214-216)
  • get_spend_key (218-220)
  • get_next_commitment_mask_and_script_key (231-234)
  • find_script_key_id_from_commitment_mask_key_id (236-243)
  • get_diffie_hellman_shared_secret (245-252)
  • construct_range_proof (254-262)
  • get_script_signature (264-279)
  • get_partial_script_signature (281-298)
  • get_partial_txo_kernel_signature (300-321)
  • get_txo_kernel_signature_excess_with_offset (323-330)
  • get_txo_private_kernel_offset (332-339)
  • encrypt_data_for_recovery (341-354)
  • try_output_key_recovery (356-364)
  • is_this_output_ours (366-374)
  • get_script_offset (376-383)
  • get_metadata_signature (385-402)
  • get_one_sided_metadata_signature (404-425)
  • sign_message_with_spend_key (222-229)
  • sign_script_message (427-434)
  • sign_script_message_with_spend_key (436-443)
  • sign_with_nonce_and_challenge (445-453)
  • get_receiver_partial_metadata_signature (455-475)
  • get_sender_partial_metadata_signature (480-498)
  • generate_burn_claim_signature (500-511)
  • stealth_address_script_spending_key (513-520)
  • get_private_key (527-529)
base_layer/transaction_key_manager/src/legacy_key_manager/inner.rs (33)
  • key_manager (105-107)
  • get_random_key (207-213)
  • get_public_key_at_key_id (215-217)
  • create_encrypted_key (259-265)
  • get_commitment (286-292)
  • verify_mask (295-302)
  • get_view_key (247-249)
  • get_private_view_key (267-269)
  • get_spend_key (251-253)
  • get_next_commitment_mask_and_script_key (255-257)
  • find_script_key_id_from_commitment_mask_key_id (273-280)
  • get_diffie_hellman_shared_secret (304-311)
  • construct_range_proof (367-375)
  • get_script_signature (327-342)
  • get_partial_script_signature (344-361)
  • get_partial_txo_kernel_signature (521-542)
  • get_txo_kernel_signature_excess_with_offset (544-551)
  • get_txo_private_kernel_offset (512-519)
  • encrypt_data_for_recovery (557-566)
  • try_output_key_recovery (583-591)
  • is_this_output_ours (593-601)
  • get_script_offset (378-385)
  • get_metadata_signature (423-440)
  • get_one_sided_metadata_signature (442-463)
  • sign_message_with_spend_key (395-402)
  • sign_script_message (387-393)
  • sign_script_message_with_spend_key (404-411)
  • sign_with_nonce_and_challenge (413-421)
  • get_receiver_partial_metadata_signature (465-484)
  • get_sender_partial_metadata_signature (489-506)
  • generate_burn_claim_signature (313-321)
  • stealth_address_script_spending_key (603-610)
  • get_private_key (219-221)
base_layer/transaction_components/src/key_manager/manager.rs (32)
  • get_random_key (474-500)
  • get_public_key_at_key_id (502-566)
  • create_encrypted_key (568-579)
  • get_commitment (581-590)
  • verify_mask (592-603)
  • get_view_key (605-609)
  • get_private_view_key (611-613)
  • get_spend_key (615-622)
  • get_next_commitment_mask_and_script_key (624-634)
  • find_script_key_id_from_commitment_mask_key_id (636-653)
  • get_diffie_hellman_shared_secret (655-668)
  • construct_range_proof (670-712)
  • get_script_signature (714-764)
  • get_partial_script_signature (766-800)
  • get_partial_txo_kernel_signature (802-841)
  • get_txo_kernel_signature_excess_with_offset (843-852)
  • get_txo_private_kernel_offset (854-873)
  • encrypt_data_for_recovery (875-898)
  • try_output_key_recovery (900-940)
  • is_this_output_ours (942-962)
  • get_script_offset (964-984)
  • get_metadata_signature (986-1021)
  • get_one_sided_metadata_signature (1023-1060)
  • sign_message_with_spend_key (1062-1091)
  • sign_script_message (1093-1108)
  • sign_script_message_with_spend_key (1110-1125)
  • sign_with_nonce_and_challenge (1127-1161)
  • get_receiver_partial_metadata_signature (1163-1199)
  • get_sender_partial_metadata_signature (1204-1239)
  • generate_burn_claim_signature (1241-1259)
  • stealth_address_script_spending_key (1261-1274)
  • get_private_key (1278-1340)
⏰ 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). (10)
  • GitHub Check: wasm build tests
  • GitHub Check: test (mainnet, stagenet)
  • GitHub Check: cargo check with msrv
  • GitHub Check: ledger build tests
  • GitHub Check: test (nextnet, nextnet)
  • GitHub Check: ci
  • GitHub Check: cargo check with stable
  • GitHub Check: test (testnet, esmeralda)
  • GitHub Check: Cucumber tests / Base Layer
  • GitHub Check: Cucumber tests / FFI
🔇 Additional comments (28)
base_layer/transaction_components/src/key_manager/manager.rs (1)

1277-1341: LGTM: Private key derivation logic is well-structured.

The get_private_key implementation correctly handles all TariKeyId variants with appropriate domain separation for derived keys and proper error handling for ledger keys. The match statement is comprehensive and the cryptographic derivations follow the expected patterns.

base_layer/transaction_components/src/key_manager/interface.rs (2)

60-275: Trait method signatures are consistent and well-defined.

The TransactionKeyManagerInterface trait provides a comprehensive synchronous API for transaction key management operations. The method signatures are clear, consistent in their error handling with KeyManagerError, and properly bounded with Clone + Send + Sync + 'static for concurrent usage.

Note: The async → sync conversion is a breaking change already flagged in past review comments.


277-280: LGTM: Secret key interface is appropriately minimal.

The SecretTransactionKeyManagerInterface trait correctly extends TransactionKeyManagerInterface and exposes only the essential private key access method. This design properly segregates secret key operations from public key operations.

base_layer/transaction_components/src/coinbase_builder.rs (4)

216-225: LGTM: Clean async-to-sync conversion.

The build method has been successfully converted from async to synchronous, aligning with the PR's objective to make the key manager stateless. The call to build_with_reward on line 224 is now synchronous.


356-470: LGTM: Public coinbase generation functions converted to synchronous.

The public functions generate_coinbase and generate_coinbase_with_wallet_output have been successfully converted from async to sync. All key manager operations (lines 420, 439, 454) are now synchronous, and error handling remains correct.


472-1177: LGTM: Test suite successfully updated to use synchronous KeyManager.

The test module has been comprehensively updated:

  • KeyManager::new_random() replaces async memory-backed key manager initialization (line 505)
  • All key manager operations are now synchronous (lines 584-586, 795, 950-951, 1068-1069, 1137)
  • Test functions remain async (for tokio::test) but correctly invoke synchronous APIs
  • Test logic and assertions unchanged

233-351: I need to verify the cryptographic design decision for nonce generation in this codebase.

The review comment is based on an incorrect assumption and should be dismissed.

Evidence shows that random nonces are required for Schnorr signatures, with each signature requiring a random private key, and reusing the secret nonce value on different messages allows private key recovery. The codebase consistently uses get_random_key(None, false) for nonce generation across all transaction types (kernel signatures, wallet outputs, coinbase transactions). This is not a behavioral change introduced by the async-to-sync conversion—it is the established, cryptographically correct pattern throughout the implementation. The coinbase builder at line 257 follows the same pattern as every other nonce generation in the codebase, confirming that random nonce generation is intentional and appropriate.

Likely an incorrect or invalid review comment.

base_layer/core/src/blocks/pre_mine/mod.rs (5)

27-38: LGTM: Import changes align with stateless KeyManager refactor.

The addition of KeyManager to the imports alongside the existing trait interfaces is correct for the stateless key manager migration.


795-795: LGTM: Function signature correctly updated to synchronous.

The removal of async from the function signature aligns with the PR objective to remove all async usage from the key manager.


862-864: LGTM: Synchronous signing and building methods correctly updated.

The conversion from async to synchronous calls for sign_as_sender_and_receiver and try_build is correct, with proper reference passing to the key manager.


871-879: LGTM: Kernel signature challenge building correctly updated.

The synchronous kernel signature challenge construction with explicit parameters matches the expected build_kernel_signature_challenge method signature and correctly uses TransactionKernelVersion::get_current_version().


970-970: LGTM: Test correctly updated to call synchronous function.

The removal of .await from the function call is correct for the now-synchronous create_pre_mine_genesis_block_info function.

applications/minotari_console_wallet/src/automation/commands.rs (16)

38-106: New key/ledger imports are consistent with downstream usage

The added imports for LedgerKeyBranch, WalletType, TariKeyId, TransactionKeyManagerInterface, LegacyWalletType, RistrettoSecretKey, and public_key_to_output_encryption_key are all used coherently in the updated pre‑mine, VN registration, and key‑export flows and fit the new stateless key manager model.


1177-1209: Pre‑mine encumber memo and parameter wiring look correct

The memo for each encumbered output now uses MemoField::new_open_from_string with detect_tx_metadata(&wallet, &current_recipient_address).await, and the resulting memo plus current_recipient_address are threaded through to encumber_aggregate_utxo; error handling is non‑panicking and consistent with other commands.


1420-1516: Key‑manager based signing, decryption, and script/metadata offsets are wired coherently

Using TransactionInputVersion::get_current_version() and TransactionOutputVersion::get_current_version() for challenges, sign_with_nonce_and_challenge for both script and metadata signatures, deriving the encryption key via public_key_to_output_encryption_key, and then calling create_encrypted_key/verify_mask and stealth_address_script_spending_key/get_script_offset forms a consistent, key‑manager‑centric pipeline; all fallible steps are guarded with explicit error handling rather than unwraps.


1710-1730: Stealth one‑sided send memo construction is robust

SendOneSidedToStealthAddress now builds a memo using MemoField::new_open_from_string and detect_tx_metadata and passes the resulting memo into send_one_sided_to_stealth_address; on failure it logs a specific error and continues, matching the console‑wallet command error pattern.


1746-1767: MakeItRain memo handling integrates cleanly with the new Tx metadata helper

The command now derives a TxType via detect_tx_metadata(&wallet, &args.destination).await, constructs a memo accordingly, and threads it through to make_it_rain, while preserving best‑effort error handling (clear eprintln! and early continue on memo failure).


1774-1786: Coin split memo uses dedicated TxType::CoinSplit

CoinSplit’s memo is now created with MemoField::new_open_from_string(&args.payment_id, TxType::CoinSplit), which accurately tags this as a coin‑split transaction, and the error path is non‑panicking and descriptive.


1812-1813: UnblindedOutput::from_wallet_output updated correctly for key‑manager dependency

Both ExportUtxos and ExportSpentUtxos now pass &wallet.key_manager_service into UnblindedOutput::from_wallet_output, which is consistent with the stateless key manager refactor and keeps the CLI commands from owning key‑reconstruction logic themselves.

Also applies to: 1882-1884


1969-1987: Finalise SHA atomic swap memo is correctly plumbed

FinaliseShaAtomicSwap now builds a memo using TxType::ClaimAtomicSwap and passes it through to finalise_sha_atomic_swap; memo errors are reported with a clear, command‑specific message before continuing.


2001-2018: HTLC refund memo tagging is appropriate

ClaimShaAtomicSwapRefund uses TxType::HtlcAtomicSwapRefund for its memo and propagates it into claim_htlc_refund; the error path is descriptive and non‑fatal to the CLI process.


2032-2061: Validator node registration: memo and signature construction look sound

The VN registration command now includes a memo (TxType::ValidatorNodeRegistration) and passes it to register_validator_node. The CompressedSignature construction from validator_node_public_nonce and RistrettoSecretKey::from_vec(...) plus optional sidechain_deployment_key via from_canonical_bytes(...) is consistent with the ristretto key usage elsewhere; all fallible conversions use ? rather than panicking.


2170-2173: View/spend key export uses new key manager API correctly

ExportViewKeyAndSpendKey now calls get_view_key, get_spend_key, and get_private_view_key on key_manager_service and serializes the public/private view key plus spend key pubkey as expected for this command; no async or panics involved.


2235-2253: Importing paper wallet with LegacyWalletType::DerivedKeys fits the migration story

For the temporary wallet used to import a paper wallet, using LegacyWalletType::DerivedKeys and threading it into init_wallet via Some(wallet_type) is consistent with the Legacy wallet type refactor and keeps this flow explicitly in the legacy key space.


2501-2501: SignMessage correctly delegates to spend‑key signing

Switching to key_manager_service.sign_message_with_spend_key(&commitment_bytes, sender_offset) centralizes signing in the key manager and avoids ad‑hoc key handling in the CLI; error handling remains via CommandError.


2776-2778: SignScriptMessage now uses spend‑key-based script signing

sign_script_message_with_spend_key mirrors the pattern from SignMessage, pushing script‑message signing into the key manager while preserving the same sender offset semantics.


2526-2551: One‑sided prepare‑for‑signing: memo integration is clean

PrepareOneSidedTransactionForSigning now derives a memo using detect_tx_metadata(&wallet, &args.destination).await and passes it into prepare_one_sided_transaction_for_signing along with destination, amount, selection criteria, output features, and fee; memo failures are handled with clear eprintln! output and continue, not panics.


2847-2863: detect_tx_metadata helper correctly classifies self vs other payments

The new async helper compares the destination against both the wallet’s interactive and one‑sided addresses and returns TxType::PaymentToSelf only in those cases, defaulting to PaymentToOther on mismatch or retrieval error; this centralizes memo TxType selection and is used consistently by commands that need per‑destination metadata.

Comment on lines +1940 to 1954
let memo = match MemoField::new_open_from_string(&args.payment_id, TxType::ClaimAtomicSwap) {
Ok(v) => v,
Err(e) => {
eprintln!("CoinSplit error! Could not create memo field from payment id string: {e}");
continue;
},
};
match init_sha_atomic_swap(
transaction_service.clone(),
config.fee_per_gram,
args.amount,
UtxoSelectionCriteria::default(),
args.destination,
MemoField::open_from_string(&args.payment_id, TxType::ClaimAtomicSwap),
memo,
)
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.

⚠️ Potential issue | 🟡 Minor

Fix error label in InitShaAtomicSwap memo construction

Logic‑wise the memo creation and propagation into init_sha_atomic_swap look good, but the error message still says "CoinSplit error!", which is confusing for users invoking the HTLC init command.

Consider:

-        eprintln!("CoinSplit error! Could not create memo field from payment id string: {e}");
+        eprintln!("InitShaAtomicSwap error! Could not create memo field from payment id string: {e}");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let memo = match MemoField::new_open_from_string(&args.payment_id, TxType::ClaimAtomicSwap) {
Ok(v) => v,
Err(e) => {
eprintln!("CoinSplit error! Could not create memo field from payment id string: {e}");
continue;
},
};
match init_sha_atomic_swap(
transaction_service.clone(),
config.fee_per_gram,
args.amount,
UtxoSelectionCriteria::default(),
args.destination,
MemoField::open_from_string(&args.payment_id, TxType::ClaimAtomicSwap),
memo,
)
let memo = match MemoField::new_open_from_string(&args.payment_id, TxType::ClaimAtomicSwap) {
Ok(v) => v,
Err(e) => {
eprintln!("InitShaAtomicSwap error! Could not create memo field from payment id string: {e}");
continue;
},
};
match init_sha_atomic_swap(
transaction_service.clone(),
config.fee_per_gram,
args.amount,
UtxoSelectionCriteria::default(),
args.destination,
memo,
)
🤖 Prompt for AI Agents
In applications/minotari_console_wallet/src/automation/commands.rs around lines
1940 to 1954, the error message when MemoField::new_open_from_string fails
incorrectly uses the label "CoinSplit error!" which is misleading for the
InitShaAtomicSwap flow; change the stderr message to reflect the correct
operation (e.g., "InitShaAtomicSwap error! Could not create memo field from
payment id string: {e}") so users see the right context, leaving the existing
continue behavior intact.

Comment on lines +813 to +834
let key_manager = KeyManager::new_random().map_err(|e| e.to_string())?;
let view_key = public_key_to_output_encryption_key(&CompressedPublicKey::new_from_pk(total_script_key))
.map_err(|e| e.to_string())?;
let view_key_id = key_manager
.import_key(view_key.clone(), None)
.await
.create_encrypted_key(view_key.clone(), None)
.map_err(|e| e.to_string())?;
let address_len = u8::try_from(public_keys.len()).map_err(|e| e.to_string())?;

let (commitment_mask, script_key) = key_manager
.get_next_commitment_mask_and_script_key()
.await
.map_err(|e| e.to_string())?;
total_private_key = total_private_key +
&key_manager
.get_private_key(&commitment_mask.key_id)
.await
.map_err(|e| e.to_string())?;
let commitment = key_manager
.get_commitment(&commitment_mask.key_id, &item.value.into())
.await
.map_err(|e| e.to_string())?;
let mut commitment_bytes = [0u8; 32];
commitment_bytes.clone_from_slice(commitment.as_bytes());

let sender_offset = key_manager
.get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key())
.await
.map_err(|e| e.to_string())?;
let sender_offset = key_manager.get_random_key(None, true).map_err(|e| e.to_string())?;
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.

⚠️ Potential issue | 🔴 Critical

Critical: Non-deterministic key generation breaks genesis block consensus.

Using KeyManager::new_random() on line 813 creates non-deterministic keys for each output, which makes the genesis block different on every execution. This is a critical consensus issue because:

  1. The total_private_key (accumulated on lines 824-827) will differ each time
  2. The kernel signature (lines 869-891) depends on total_private_key, making it non-deterministic
  3. All commitment masks, script keys, and sender offsets will be random and unreproducible
  4. Different nodes would generate different genesis blocks, causing a network fork at genesis

For genesis block creation, the key manager must use a deterministic seed or the keys must be passed as parameters to ensure reproducibility across all nodes.

Consider passing a seeded/deterministic KeyManager as a parameter to this function, or derive keys deterministically from the input parameters.

🤖 Prompt for AI Agents
In base_layer/core/src/blocks/pre_mine/mod.rs around lines 813 to 834, the code
calls KeyManager::new_random() which creates non-deterministic keys and thus
makes genesis block construction non-reproducible; replace this by using a
deterministic KeyManager instance (either accept a KeyManager parameter to the
function or construct one from a fixed/derived seed) and remove any calls to
new_random(); ensure all subsequent calls (create_encrypted_key,
get_next_commitment_mask_and_script_key, get_private_key, get_commitment,
get_random_key for sender_offset, etc.) use that deterministic manager so
total_private_key, commitment masks, script keys and sender offsets are
reproducible across nodes.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
base_layer/transaction_components/src/key_manager/manager.rs (3)

147-162: Consider extracting the domain string to a constant.

The domain string "KEY_MANAGER_private_key" appears both here (line 153) and in decrypt_encrypted_key (line 433). While a past review comment about this was disputed, extracting it to a named constant (e.g., const ENCRYPTION_DOMAIN: &[u8] = b"KEY_MANAGER_private_key";) would improve maintainability and prevent typos if the domain ever needs to change.


211-213: Inefficient string conversion pattern.

The pattern TariKeyId::from_str(key_str.to_string().as_str()) converts Box<str>String&str → parse back to TariKeyId. Since key is already a Box<str>, you can use TariKeyId::from_str(&**key) or TariKeyId::from_str(key.as_ref()) to avoid the unnecessary String allocation. This pattern appears throughout the file at lines 277, 297, 506, 538, 549, 557, 1286, 1318, 1329, and 1335.

Apply this pattern throughout:

-                let key = TariKeyId::from_str(key_str.to_string().as_str())
+                let key = TariKeyId::from_str(key_str.as_ref())
                     .map_err(|_| KeyManagerError::InvalidKeyId(script_key_id.to_string()))?;

901-941: Consider extracting helper methods for improved readability.

The try_output_key_recovery method contains nested match statements and handles two different decryption paths (view key vs. DH shared secret). Consider extracting helper methods like try_decrypt_with_view_key and try_decrypt_with_dh_secret to reduce cognitive complexity and improve testability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88e0ffc and 446b162.

📒 Files selected for processing (1)
  • base_layer/transaction_components/src/key_manager/manager.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: hansieodendaal
Repo: tari-project/tari PR: 7432
File: integration_tests/src/ffi/ffi_import.rs:351-356
Timestamp: 2025-08-22T10:22:27.310Z
Learning: The PR "feat!: remove comms from wallet" is a breaking change PR where backward compatibility is intentionally not maintained. The FFI functions `comms_config_create` and `comms_config_destroy` were renamed to `wallet_db_config_create` and `wallet_db_config_destroy` as part of the intentional breaking changes.
📚 Learning: 2025-06-16T14:49:31.831Z
Learnt from: martinserts
Repo: tari-project/tari PR: 7122
File: base_layer/wallet/src/transaction_service/offline_signing/one_sided_signer.rs:0-0
Timestamp: 2025-06-16T14:49:31.831Z
Learning: In Tari's one-sided transaction offline signing implementation, the script_keys vector in the script offset calculation should only include input.output.script_key_id, not output script keys, change output script keys, or recipient script keys. This is by design for the one-sided transaction protocol.

Applied to files:

  • base_layer/transaction_components/src/key_manager/manager.rs
🧬 Code graph analysis (1)
base_layer/transaction_components/src/key_manager/manager.rs (4)
applications/minotari_ledger_wallet/comms/src/accessor_methods.rs (7)
  • ledger_get_dh_shared_secret (431-463)
  • ledger_get_one_sided_metadata_signature (551-636)
  • ledger_get_public_key (228-259)
  • ledger_get_raw_schnorr_signature (466-507)
  • ledger_get_script_offset (330-408)
  • ledger_get_script_schnorr_signature (510-548)
  • ledger_get_script_signature (262-327)
base_layer/common_types/src/encryption.rs (2)
  • decrypt_bytes_integral_nonce (53-75)
  • encrypt_bytes_integral_nonce (78-104)
base_layer/transaction_components/src/transaction_components/one_sided.rs (3)
  • diffie_hellman_stealth_domain_hasher (63-67)
  • public_key_to_output_encryption_key (36-43)
  • public_key_to_output_spending_key (46-53)
base_layer/transaction_components/src/transaction_components/encrypted_data.rs (2)
  • encrypt_data (84-129)
  • decrypt_data (134-194)
🔇 Additional comments (2)
base_layer/transaction_components/src/key_manager/manager.rs (2)

803-842: LGTM! Good handling of coinbase and input/output cases.

The method correctly handles the special case where coinbase transactions don't use kernel offsets (lines 818-822), and appropriately negates the signing key for inputs vs. outputs (lines 826-830). The comments clearly explain the non-obvious logic, which is essential for maintaining this code.


1128-1162: Good validation logic for ledger key pairing.

The method correctly enforces that both private_key_id and nonce must be either ledger keys or non-ledger keys, preventing unsafe mixing of key types. This is important for security.

Copy link
Copy Markdown
Contributor

@hansieodendaal hansieodendaal left a comment

Choose a reason for hiding this comment

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

It is looking good.

I would like to know which system-level tests have been done on this branch? Recovery and spending for mainnet ledger pre-mine wallets and normal mainnet wallets come to mind.

@SWvheerden SWvheerden merged commit d177274 into tari-project:development Nov 19, 2025
16 of 17 checks passed
@SWvheerden SWvheerden deleted the sw_change_key_manager branch November 19, 2025 06:30
@coderabbitai coderabbitai bot mentioned this pull request Nov 25, 2025
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants