Skip to content

fix(swift-sdk): keychain privk storage#3946

Merged
ZocoLini merged 1 commit into
v3.1-devfrom
fix/keychain-privk-storage
Jun 23, 2026
Merged

fix(swift-sdk): keychain privk storage#3946
ZocoLini merged 1 commit into
v3.1-devfrom
fix/keychain-privk-storage

Conversation

@ZocoLini

@ZocoLini ZocoLini commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

How Private keys were been stored didn't work well with the legacy keyring the swift tests where using without signing the app, what I do with this PR is updated the store/load logic to use the public key as the keyring entry label, being able to query for a single priv key instead of requesting a bunch oof items to later iterate over them, what doesn't work well in macOS file based keyring

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • Refactor
    • Improved the speed and efficiency of private-key retrieval by switching to a direct lookup keyed by the corresponding public key.
    • Updated how private keys are stored to associate them with the public key label for faster matching.
    • Retrieval now more reliably returns results (or nil) based on that direct match, without scanning unrelated stored items.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: de19663f-6a02-4d56-b7f4-0766c2d2ce80

📥 Commits

Reviewing files that changed from the base of the PR and between 7ccee69 and 1804f3f.

📒 Files selected for processing (1)
  • packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift

📝 Walkthrough

Walkthrough

KeychainManager.storeIdentityPrivateKey is updated to write metadata.publicKey (lowercased) into kSecAttrLabel when inserting a Keychain item. retrieveIdentityPrivateKey(publicKeyHex:) is rewritten to perform a single direct SecItemCopyMatching lookup by kSecAttrLabel instead of fetching all items and scanning their decoded metadata.

Changes

Keychain Identity Key Lookup Optimization

Layer / File(s) Summary
Store label and direct-lookup retrieve
packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift
storeIdentityPrivateKey adds kSecAttrLabel set to metadata.publicKey.lowercased() in the Keychain insert query. retrieveIdentityPrivateKey(publicKeyHex:) replaces the full-scan loop (fetch all identity_privkey.* rows, decode IdentityPrivateKeyMetadata from kSecAttrGeneric, match by publicKey) with a single SecItemCopyMatching call using kSecAttrLabel == publicKeyHex.lowercased() and kSecMatchLimitOne, returning nil on lookup failure.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • shumkov
  • QuantumExplorer
  • bezibalazs

Poem

🐇 Hop, hop, no more scanning the burrow!
The label is stamped, the lookup is thorough.
One key, one query, straight to the prize,
No decoding loops under weary eyes.
The keychain is tidy — what a surprise! 🗝️

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(swift-sdk): keychain privk storage' is concise and directly related to the main change — optimizing private key storage and retrieval in the Keychain by using the public key as a label for direct lookup instead of batch fetching.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/keychain-privk-storage

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.

@ZocoLini ZocoLini force-pushed the fix/keychain-privk-storage branch from 59a6796 to 7ccee69 Compare June 21, 2026 14:36
@ZocoLini ZocoLini changed the title Fix/keychain privk storage fix(swift-sdk): keychain privk storage Jun 22, 2026
@ZocoLini ZocoLini marked this pull request as ready for review June 22, 2026 07:17
@thepastaclaw

thepastaclaw commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

✅ Review complete (commit 1804f3f)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift (1)

633-636: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stale comment: describes old scan-all behavior that no longer applies.

Lines 633-636 state that retrieveIdentityPrivateKey(publicKeyHex:) "scans every identity_privkey.* item and matches on the metadata's publicKey hex", but the method now performs a direct kSecAttrLabel lookup. This mismatch will confuse future maintainers.

📝 Suggested comment update
         // Including the wallet id (hex) in the account name keeps
         // every wallet's identity-key set independent. Reads via
-        // `retrieveIdentityPrivateKey(publicKeyHex:)` still work
-        // unmodified — that path scans every `identity_privkey.*`
-        // item and matches on the metadata's `publicKey` hex, so the
-        // account-name shape doesn't matter for lookup. The direct
+        // `retrieveIdentityPrivateKey(publicKeyHex:)` still work
+        // because that path queries `kSecAttrLabel` (the lowercased
+        // public-key hex written at store time), so the account-name
+        // shape doesn't matter for lookup. The direct
         // `retrieveKeyData(identifier:)` path uses whatever string
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift`
around lines 633 - 636, The comment block near lines 633-636 describes outdated
behavior for the retrieveIdentityPrivateKey(publicKeyHex:) method, stating it
scans every identity_privkey item and matches on metadata's publicKey hex, when
it actually now performs a direct kSecAttrLabel lookup. Update this comment
block to accurately describe the current implementation behavior of the method,
removing references to the scan-all approach and clarifying that it uses a
direct label-based lookup instead.
🧹 Nitpick comments (1)
packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift (1)

737-771: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider aligning hasIdentityPrivateKey with the new direct-lookup pattern.

This method still uses the scan-all-and-decode approach while retrieveIdentityPrivateKey now uses direct kSecAttrLabel lookup. The current implementation is correct (uses caseInsensitiveCompare), but the inconsistency may cause confusion and the scan is less efficient.

Since this is a UI diagnostic helper per the doc comment, the performance impact is likely negligible, so this could be deferred.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift`
around lines 737 - 771, The hasIdentityPrivateKey method currently uses a
scan-all-and-decode pattern with kSecMatchLimitAll to iterate through all items
and compare public keys, while retrieveIdentityPrivateKey has been refactored to
use direct kSecAttrLabel lookup which is more efficient. Align
hasIdentityPrivateKey with the same direct-lookup pattern by using kSecAttrLabel
to query for the specific identity private key entry directly, eliminating the
need to scan and decode all items. This improves consistency and performance,
though the impact is minimal since this is a UI diagnostic helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift`:
- Around line 633-636: The comment block near lines 633-636 describes outdated
behavior for the retrieveIdentityPrivateKey(publicKeyHex:) method, stating it
scans every identity_privkey item and matches on metadata's publicKey hex, when
it actually now performs a direct kSecAttrLabel lookup. Update this comment
block to accurately describe the current implementation behavior of the method,
removing references to the scan-all approach and clarifying that it uses a
direct label-based lookup instead.

---

Nitpick comments:
In `@packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift`:
- Around line 737-771: The hasIdentityPrivateKey method currently uses a
scan-all-and-decode pattern with kSecMatchLimitAll to iterate through all items
and compare public keys, while retrieveIdentityPrivateKey has been refactored to
use direct kSecAttrLabel lookup which is more efficient. Align
hasIdentityPrivateKey with the same direct-lookup pattern by using kSecAttrLabel
to query for the specific identity private key entry directly, eliminating the
need to scan and decode all items. This improves consistency and performance,
though the impact is minimal since this is a UI diagnostic helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77148128-55c9-48e7-8d1b-ba0ea616be4e

📥 Commits

Reviewing files that changed from the base of the PR and between 40c1126 and 7ccee69.

📒 Files selected for processing (1)
  • packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review

Small, focused fix that switches identity-private-key lookup from a scan-and-decode loop to a direct kSecAttrLabel query, addressing macOS file-keyring iteration quirks. One legitimate upgrade-compatibility concern (legacy unlabeled rows become invisible to the fallback path) and two stale docstrings/comments that still describe the removed scan behavior. Speculative defense-in-depth concerns from the security passes do not represent real issues and are dropped.

🟡 1 suggestion(s) | 💬 2 nitpick(s)

2 additional finding(s) omitted (not in diff).

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift`:
- [SUGGESTION] packages/swift-sdk/Sources/SwiftDashSDK/Security/KeychainManager.swift:710-726: Legacy keychain rows without kSecAttrLabel become invisible to this fallback
  Before this PR, `storeIdentityPrivateKey` never set `kSecAttrLabel`, so any row already on a device from a prior build has no label attribute. The new query pins `kSecAttrLabel == publicKeyHex.lowercased()`, so `SecItemCopyMatching` returns `errSecItemNotFound` for those legacy rows and the signer trampoline treats the secret as missing until the persister happens to re-`storeIdentityPrivateKey` and overwrite the row with a labeled version. The primary signing path (`retrieveKeyData(identifier:)` via `PersistentPublicKey.privateKeyKeychainIdentifier`) still works, so this only affects the public-key fallback used mid-registration / before the SwiftData row is written — but for users upgrading mid-flow it silently breaks a previously working path. If pre-existing user data is possible, either (a) fall back to the prior metadata scan when the labeled query misses, (b) run a one-shot `SecItemUpdate` migration over `identity_privkey.*` rows to backfill `kSecAttrLabel`, or (c) document that callers must trigger a re-persist after upgrade. If the SDK has not shipped to real users yet, call that out explicitly so this is closed knowingly.

@ZocoLini ZocoLini force-pushed the fix/keychain-privk-storage branch from 7ccee69 to 1804f3f Compare June 23, 2026 09:46
@ZocoLini ZocoLini merged commit c1ab74e into v3.1-dev Jun 23, 2026
13 of 15 checks passed
@ZocoLini ZocoLini deleted the fix/keychain-privk-storage branch June 23, 2026 09:48
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