Repository navigation
feat: add signer-based signing with sign_with, sign_with_operator and set_operator_with - #2682
Conversation
… set_operator_with Route every signing path through a signer callback so keys can stay in an HSM or KMS. - Add Signer type and Transaction.sign_with(public_key, signer); sign() now wraps it - Add Transaction.sign_with_operator(client) and internal _apply_signers() - Add Client.set_operator_with(account_id, public_key, signer); set_operator returns self - Extend Operator with public_key and signer fields - Sign query payments with the operator signer instead of the private key - execute() signs with the operator only when the operator is the payer - batchify() signs through sign_with_operator() Signed-off-by: Ntege Daniel <danientege785@gmail.com>
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #2682 +/- ##
==========================================
+ Coverage 95.65% 95.69% +0.04%
==========================================
Files 166 167 +1
Lines 10832 10890 +58
==========================================
+ Hits 10361 10421 +60
+ Misses 471 469 -2 🚀 New features to boost your workflow:
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughThe SDK adds callback-based signing for transactions and client operators. Transaction execution, batchification, and paid queries use signer callbacks. New examples and tests demonstrate these paths. ChangesSigner-Based Signing
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Transaction
participant SignerCallback
participant SignatureMap
Transaction->>SignerCallback: Send transaction body bytes
SignerCallback-->>Transaction: Return signature bytes
Transaction->>SignatureMap: Store signature pair
Merge Risk: 🔵 Low · up to The example appears mergeable. Two existing edge cases remain: re-signing can miss a body added after signer registration, and an invalid manually configured query operator produces an unclear error. Neither was found on the example’s normal execution path. Security Architecture Review
🚥 Pre-merge checks | ✅ 5
✨ Finishing Touches
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: hiero-ledger/hiero-sdk-python/.coderabbit.yaml
Review profile: ASSERTIVE
Plan: Advanced
Run ID: 63fbe761-050c-4f0f-84b1-ca5932f38e0d
📒 Files selected for processing (4)
src/hiero_sdk_python/__init__.pysrc/hiero_sdk_python/client/client.pysrc/hiero_sdk_python/query/query.pysrc/hiero_sdk_python/transaction/transaction.py
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.
manishdait
left a comment
There was a problem hiding this comment.
@danielmarv, great work!
Client.from_env() already loads .env. Signed-off-by: Ntege Daniel <danientege785@gmail.com>
|
Hello, this is the OfficeHourBot. This is a reminder that the Hiero Python SDK Office Hours will begin in approximately 3 hours and 55 minutes (14:00 UTC). This session provides an opportunity to ask questions regarding this Pull Request. Details:
Disclaimer: This is an automated reminder. Please verify the schedule here for any changes. From, |
|
@danielmarv This looks great! Please update the branch when you have a moment. Thank you!! |
Description:
This pull request adds the signer-based signing core described in #2678. Signing can now go through a callback,
Signer = Callable[[bytes], bytes], instead of aPrivateKeyheld in the process. This lets keys stay in an HSM or KMS, and lets the client operator itself be signer-based. All existing signing paths (sign,execute,batchifyand query payment) now route through it. This matchessignWith,signWithOperatorandsetOperatorWithin the Java, JS and Go SDKs.New API:
Signertype incrypto/signer.py, exported from the package root.Transaction.sign_with(public_key, signer):from_bytes.ed25519orECDSA_secp256k1field from the public key.TypeErrorif the signer is not callable or returns something other thanbytes.Transaction.sign_with_operator(client)raisesValueErrorwithout an operator, and freezes the transaction with the client first if it is not frozen.Transaction._apply_signers()re-signs bodies that lack a signature from a registered signer. It is the hook for rebuilt bodies (see feat(transaction): regenerate transaction ID on expiry #2657).Client.set_operator_with(account_id, public_key, signer), with newoperator_public_keyandoperator_signerproperties.Changes to existing code:
sign()is now a thin wrapper aroundsign_with; its behavior and deduplication are unchanged.set_operatordelegates toset_operator_with, still setsoperator_private_key, and now returnsself(previouslyNone).operator_public_keyandoperator_signerare derived fromoperator_private_keywhen it is set. Code that assignsclient.operator_private_keydirectly therefore keeps signing.Operatorgainspublic_keyandsignerfields with defaults, soOperator(account_id, private_key)still works.Query._build_query_payment_transactionsigns with the operator's public key and signer, and reuses the shared signature-pair helper instead of its own copy. AnOperatorbuilt by hand with only a private key still works.execute()signs with the operator only when the operator is the payer, and no longer crashes withAttributeErroron a client with no operator key.batchify()callssign_with_operator(client).Behavior change:
A transaction whose payer is not the operator no longer gets the operator's signature automatically. If the operator is also a required signer, for example a token treasury or the sender in a transfer, callers must call
sign_with_operator(client)explicitly. This matches the Java, JS and Go SDKs. The repo no longer has aCHANGELOG.md(removed in #2112), so the change is documented here.Testing:
tests/unit/transaction_sign_with_test.py(27 tests) covers every case in the issue's test list. Highlights:sign_withon multi-node and chunkedFileAppendTransactions: one signer call per body, with the exact body bytes, and every signature verified.bytessignatures.from_bytesdeduplication and_apply_signers.sign_with_operator, plusset_operatorandset_operator_withcompatibility.execute()payer rules, including a client with no operator.batchify()through a signer-based operator.tests/integration/transaction_sign_with_e2e_test.py(Solo):set_operator_withclient running a transaction and a paid query.FileAppendTransactionsigned withsign_with.examples/transaction/transaction_sign_with_external_signer.pyandexamples/client/client_set_operator_with.py.Related issue(s):
Fixes #2678
Notes for reviewer:
mainfails identically, so none are caused by this change:0.0.2is a system account that Solo does not charge: insufficient-fee or insufficient-payment errors are not raised,transaction_feeis 0, and fee estimation fails.localhost:5600).Checklist