feat(wallet-daemon-signer): add stealth transfer support#110
feat(wallet-daemon-signer): add stealth transfer support#110metalaureate wants to merge 1 commit intotari-project:mainfrom
Conversation
Add stealth transfer methods to WalletDaemonSigner: - stealthTransfer(): all-in-one stealth transfer via daemon - createStealthTransferStatement(): low-level statement generation - associateStealthResource(): register stealth resource tracking - stealthUtxosList(): query stealth UTXOs - stealthUtxosDecryptValue(): decrypt blinded UTXO values - getClient(): expose underlying WalletDaemonClient Add DaemonStealthFactory implementing StealthOutputStatementFactory: - Delegates crypto (DH-KDF, Pedersen commitments, range proofs, balance proofs) to the wallet daemon via JRPC - Compatible with StealthTransfer builder for custom tx construction - No WASM crypto required on the client side Re-export commonly used stealth types from ootle-ts-bindings for convenience. Note: The tari.js StealthTransferStatement type does not match the bindings type. DaemonStealthFactory returns the bindings format (which is what the on-chain deposit_stealth method expects) with a type cast. A follow-up PR should align the ootle types with the canonical bindings types.
There was a problem hiding this comment.
Code Review
This pull request introduces the DaemonStealthFactory class to facilitate stealth transfer statement generation via the wallet daemon and extends WalletDaemonSigner with high-level stealth operation methods. Feedback highlights a potential risk where the recipient public key parameter is ignored in favor of the factory's configured address, suggesting a validation check to prevent funds from being sent to the wrong destination. Additionally, it is recommended to make the hardcoded input_selection strategy configurable to provide users with more flexibility.
| * @param _recipientPublicKeyHex - Ignored in the daemon flow. The recipient is | ||
| * identified by the `recipientAddress` provided at construction time (which | ||
| * embeds the public key). This parameter is accepted to satisfy the | ||
| * `StealthOutputStatementFactory` interface. | ||
| * @param amounts - Amount(s) to send in each stealth output. | ||
| */ | ||
| public async generateOutputsStatement( | ||
| _recipientPublicKeyHex: string, | ||
| amounts: bigint[], |
There was a problem hiding this comment.
The _recipientPublicKeyHex parameter is ignored in favor of the recipientAddress provided during construction. This creates a risk where a caller of the StealthTransfer builder might specify a different recipient via .to(key, amount) than the one this factory is configured for, leading to funds being sent to the wrong destination without any warning or error.
Since the StealthOutputStatementFactory interface only provides a hex public key, but the wallet daemon requires a full OotleAddress (which includes the view key), this factory is effectively pinned to a single recipient. You should consider adding a check to verify that _recipientPublicKeyHex matches the public key part of this.recipientAddress (if possible to decode) or at least document this limitation more explicitly in the method body to prevent accidental misuse.
| { | ||
| sender_account: this.senderAccount, | ||
| resource_address: this.resourceAddress, | ||
| input_selection: { Selection: "PreferConfidential" }, |
There was a problem hiding this comment.
Summary
Add stealth transfer support to
WalletDaemonSignerand introduceDaemonStealthFactory— a concrete implementation ofStealthOutputStatementFactorythat delegates cryptographic operations to the wallet daemon.Problem
The
StealthOutputStatementFactoryinterface exists in@tari-project/ootlebut has zero implementations. The underlying crypto primitives (DH-KDF, Pedersen commitments, range proofs, balance proofs) are not available inootle-wasm, making a pure-browser implementation impossible today.However, the wallet daemon already implements all of this crypto server-side, and
@tari-project/ootle-ts-bindingsalready defines the request/response types (AccountsCreateStealthTransferStatementRequest, etc.). The JRPC endpointaccounts.create_stealth_transfer_statementexists on the daemon but lacks a typed client method.Changes
WalletDaemonSigner— 6 new methodsstealthTransfer()accounts.stealth_transfercreateStealthTransferStatement()accounts.create_stealth_transfer_statementassociateStealthResource()accounts.associate_stealth_resourcestealthUtxosList()stealth_utxos.liststealthUtxosDecryptValue()stealth_utxos.decrypt_valuegetClient()WalletDaemonClientDaemonStealthFactory(new)Implements
StealthOutputStatementFactoryby callingaccounts.create_stealth_transfer_statement. This enables the existingStealthTransferbuilder to work with a wallet daemon backend:Re-exports
Commonly used stealth types from
@tari-project/ootle-ts-bindingsare re-exported for convenience.Known issue: type mismatch
The
StealthTransferStatementin@tari-project/ootleuses a simplified shape:The canonical bindings type uses:
DaemonStealthFactoryreturns the bindings format (whichdeposit_stealthexpects on-chain) with a type cast. A follow-up PR should align the@tari-project/ootletypes with the bindings.Motivation
This work is motivated by Cairn, a cryptographic trust infrastructure for investigative journalism that uses stealth transfers for unlinkable source submissions. The daemon-backed factory provides a working path while pure-WASM stealth crypto is developed.