Skip to content

feat!: siegel key manager for Safe Smart Account 🧧#355

Open
paolodamico wants to merge 16 commits into
mainfrom
siegel
Open

feat!: siegel key manager for Safe Smart Account 🧧#355
paolodamico wants to merge 16 commits into
mainfrom
siegel

Conversation

@paolodamico
Copy link
Copy Markdown
Contributor

@paolodamico paolodamico commented May 15, 2026

Introduces Siegel to securely pass the secret bytes of the EOA private key to Rust for one-off signature operations. This ensures the private key exists in application memory solely for the duration of the signature process and then zeroized (and while the key is in memory there's protections against under/overflows).

Outstanding

Other places still pass secrets to Rust via the UniFFI boundary (e.g. backup manager) and must be updated.


Note

High Risk
Breaking UniFFI constructor and redesigned private-key lifecycle for all Safe signing; incorrect platform KeyManager/siegel_fill behavior could break signatures or widen secret exposure.

Overview
Safe Smart Account no longer accepts a hex private key at construction. Callers must supply a SmartAccountKeyManager (UniFFI foreign trait) that returns a fresh SiegelSession (64-byte ASCII hex EOA key) per use; Rust validates the key once at init (caches EOA address), then signs via one-shot read_once sessions that zeroize the secret after each operation.

Signing paths (personal_sign, typed data, Safe txs, ERC-4337) now load the key through sign_hash_sync instead of a long-lived in-process signer. siegel-uniffi is wired into bedrock (dependency, UniFFI re-export). SafeSmartAccountError::SiegelSession maps siegel session failures. Tests and Swift/Kotlin harnesses use InMemoryKeyManager / TestKeyManager + from_private_key_hex; mobile build scripts copy all generated UniFFI Swift sources/headers (bedrock + siegel).

This is a breaking foreign API change (privateKeykeyManager). EoaSigner and other UniFFI secret paths are unchanged in this PR.

Reviewed by Cursor Bugbot for commit 6e32ec8. Bugbot is set up for automated code reviews on this repo. Configure here.

@paolodamico paolodamico changed the title feat!: siegel key manager for Safe Smart Account feat!: siegel key manager for Safe Smart Account 🧧 May 15, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ba38efb96

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bedrock/src/smart_account/mod.rs Outdated
Copy link
Copy Markdown

@NnnOooPppEee NnnOooPppEee left a comment

Choose a reason for hiding this comment

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

Security review of the Siegel integration. Two inline notes below. One additional pre-existing observation that doesn't touch this PR's diff:

EoaSigner::new at signer.rs L260-L267 — pre-existing in this file (not from this PR): hex::decode(private_key) returns a plain Vec<u8> with the raw 32 key bytes, and Vec::drop doesn't zero memory — so after new() returns, those bytes sit in the allocator's free list until something reuses the slot. Same pattern as TOB-Parity-022 (p.23). One-line fix: Zeroizing::new(hex::decode(private_key)?). Worth folding in here since EoaSigner will need the same treatment when it migrates to SmartAccountKeyManager anyway.

Full write-up and the equivalent upstream Siegel hardening PR are tracked separately.

Comment thread bedrock/Cargo.toml Outdated
Comment thread bedrock/src/smart_account/mod.rs
Comment thread swift/build_swift.sh
fi
shift
;;
--help | -h)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

my ide now formats bash files, looks reasonable

@paolodamico paolodamico requested a review from NnnOooPppEee June 2, 2026 08:16
Comment thread bedrock/src/smart_account/signer.rs Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f22b83f. Configure here.

Comment thread bedrock/src/smart_account/mod.rs Outdated

// Read the key once to validate it and derive the EOA address.
// The session is consumed and zeroized inside `read_once`.
let siegel = key_manager.get_eoa_private_key();
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.

Will get_eoa_private_key() eventually go through biometric or some sort of user presence check on device? If so, that prompt would be triggered at construction instead of at signing, which is going to feel off for the user. I will let @NnnOooPppEee weigh in here as I think just in time secret access is a principle we want to hold to. I do see the value of what we're doing here though, failing early if we can't load the secret and checking the public address.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a decision to be made on world app. I believe the best approach is to have it when unlocked if the user doesn't enable Face ID or require Face ID for the initial access id the user has Face ID protection in the app

Comment thread swift/test_swift.sh
Comment on lines +45 to +49
shopt -s nullglob
swift_files=("$BASE_PATH/$SOURCES_PATH_NAME"/*.swift)
shopt -u nullglob
if [ ${#swift_files[@]} -eq 0 ]; then
echo -e "${RED}✗ Could not find any generated Swift bindings in: $BASE_PATH/$SOURCES_PATH_NAME${NC}"
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.

Just a side comment, I would like to advocate for doing scripts in a higher level language, nodejs, python, rust even. Bash is famlously hard to understand or review.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

will have this in the back burner to migrate

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Introduces Siegel-backed ephemeral key handling for Safe Smart Account signing by replacing long-lived private-key material in Rust with per-operation SiegelSession reads supplied by a foreign SmartAccountKeyManager (UniFFI foreign trait). This is a breaking UniFFI API change (privateKeykeyManager) and adjusts Rust, Swift, and Kotlin tests/build tooling accordingly.

Changes:

  • Redesign Safe signing to fetch the EOA key via one-shot SiegelSession::read_once from a SmartAccountKeyManager, caching only the derived EOA address.
  • Add test-only helpers (from_private_key_hex, in-memory key managers) and update Rust/Swift/Kotlin tests to use the new key-manager constructor flow.
  • Wire siegel-uniffi into the build/bindgen pipeline and ensure generated Swift/Kotlin bindings are copied/compiled for both bedrock + siegel.

Reviewed changes

Copilot reviewed 29 out of 30 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
swift/tests/BedrockTests/BedrockSolMacroTests.swift Updates SafeSmartAccount initialization in Swift tests to use TestKeyManager.
swift/tests/BedrockTests/BedrockSmartAccountTests.swift Adds Swift test SmartAccountKeyManager implementation and switches tests to new constructor.
swift/test_swift.sh Copies all generated Swift binding files (bedrock + siegel) into the Swift test package.
swift/build_swift.sh Moves all generated UniFFI Swift sources/headers and concatenates modulemaps to support multiple linked UniFFI crates.
kotlin/bedrock-tests/src/test/kotlin/bedrock/TestKeyManager.kt Adds Kotlin test SmartAccountKeyManager using JNA to call siegel_fill.
kotlin/bedrock-tests/src/test/kotlin/bedrock/BedrockSolMacroTests.kt Updates SafeSmartAccount initialization in Kotlin tests to use TestKeyManager.
kotlin/bedrock-tests/src/test/kotlin/bedrock/BedrockSmartAccountTests.kt Refactors Kotlin tests to use a helper that constructs accounts via TestKeyManager.
kotlin/bedrock-tests/build.gradle.kts Adds generated uniffi/siegel_uniffi sources to the Kotlin test source set.
Cargo.lock Records new siegel and siegel-uniffi dependencies.
bedrock/tests/test_smart_account_world_gift_manager_gift_redeem.rs Migrates tests to SafeSmartAccount::from_private_key_hex.
bedrock/tests/test_smart_account_world_gift_manager_gift_cancel.rs Migrates tests to SafeSmartAccount::from_private_key_hex.
bedrock/tests/test_smart_account_wld_vault.rs Migrates tests to SafeSmartAccount::from_private_key_hex.
bedrock/tests/test_smart_account_wa_get_user_operation_receipt.rs Migrates tests to SafeSmartAccount::from_private_key_hex.
bedrock/tests/test_smart_account_usd_vault.rs Migrates tests to SafeSmartAccount::from_private_key_hex.
bedrock/tests/test_smart_account_transfer.rs Migrates tests to SafeSmartAccount::from_private_key_hex.
bedrock/tests/test_smart_account_sign_typed_data.rs Migrates tests to SafeSmartAccount::from_private_key_hex.
bedrock/tests/test_smart_account_personal_sign.rs Migrates tests to SafeSmartAccount::from_private_key_hex.
bedrock/tests/test_smart_account_permit2_transfer.rs Migrates tests to SafeSmartAccount::from_private_key_hex.
bedrock/tests/test_smart_account_morpho.rs Migrates tests to SafeSmartAccount::from_private_key_hex.
bedrock/tests/test_smart_account_erc4337_transaction_execution.rs Migrates tests to SafeSmartAccount::from_private_key_hex.
bedrock/tests/test_smart_account_bundler_sponsored.rs Migrates tests to SafeSmartAccount::from_private_key_hex.
bedrock/tests/test_permit2_approval_processor.rs Migrates processor test to SafeSmartAccount::from_private_key_hex.
bedrock/src/transactions/mod.rs Updates docs/examples to reflect new key-manager based constructor signature.
bedrock/src/test_utils.rs Adds InMemoryKeyManager test helper backed by SiegelSession + siegel_fill.
bedrock/src/smart_account/transaction_4337.rs Updates unit tests to use from_private_key_hex.
bedrock/src/smart_account/signer.rs Changes signing to build a one-shot signer from siegel bytes per signing call.
bedrock/src/smart_account/mod.rs Replaces stored signer with SmartAccountKeyManager + cached EOA address; introduces error mapping for Siegel session failures; adds from_private_key_hex test constructor and the foreign trait definition.
bedrock/src/siwe/test.rs Updates SIWE tests to use from_private_key_hex.
bedrock/src/lib.rs Re-exports UniFFI scaffolding for siegel bindings.
bedrock/Cargo.toml Adds a pinned dependency on siegel-uniffi.

Comment thread bedrock/src/test_utils.rs
Comment on lines +38 to +43
pub const fn new(private_key_hex: String) -> Self {
// Normally we'd verify the sk length here, but because we have tests that require
// passing invalid secrets, we don't enforce it.
Self {
hex_bytes: private_key_hex.into_bytes(),
}
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.

4 participants