# Stealth module

> Export-by-export reference for @privacy-pools-v2/sdk/stealth: ERC-5564 derivation, scanning, announcement binding, and the deposit-payload carrier.

The stealth module ships on its own subpath, `@privacy-pools-v2/sdk/stealth`, separate from the main barrel; nothing in it is reachable from `@privacy-pools-v2/sdk`. It implements ERC-5564 [stealth addresses](/concepts/stealth-addresses) for two schemes and the glue that binds an announcement to a pool withdrawal. Usage walkthroughs live on [Stealth withdraw](/operations/stealth-withdraw); this page is the export reference.

```ts

```

## Schemes and constants

```ts
SCHEME_CANONICAL            // 0x0001, EIP-5564 secp256k1 scheme 1
SCHEME_FLUIDKEY             // 0xfffe, FluidKey recipe (placeholder id until registered)
CANONICAL_ANNOUNCER_ADDRESS // 0x55649e01b5df198d18d95b5cc5051630cfd45564
assertCanonicalAnnouncer(announcer: EvmAddress): void
VIEW_TAG_BYTES              // 1
ERC5564_ANNOUNCE_ABI        // the announce(uint256,address,bytes,bytes) ABI fragment
```

`assertCanonicalAnnouncer` throws `StealthAddressAnnouncementError` for any address other than the canonical CREATE2 singleton; every builder that emits an announce call runs it.

## Sender side

```ts
resolveRecipient(identifier: RecipientIdentifier): MetaAddress
generateEphemeralKeyPair(scheme: SchemeId): EphemeralKeyPair
deriveStealthAddress(metaAddress: MetaAddress, ephemeral: EphemeralKeyPair, schemeId: SchemeId): StealthDerivation
deriveStealthSharedSecret(...)                       // the ECDH shared-secret hash the scheme uses
computeViewTag(...)                                  // first byte of the shared hash
encodeViewTagAsMetadata(viewTag): Hex                // the 1-byte announcement metadata
buildAnnounceCall(announcement: Announcement, announcer: EvmAddress): EvmCall
composeWithdrawalBundle(withdrawCall: DeclaredWithdrawCall, announcement: Announcement, announcer: EvmAddress): StealthWithdrawalBundle
prepareStealthWithdrawal(request: StealthWithdrawalRequest): Promise<StealthWithdrawalBundle>
toStealthAnnouncementFields(derivation: StealthDerivation): StealthAnnouncementFields
```

-   `resolveRecipient` maps an identifier to a typed meta-address. `{ kind: "raw", spendingPubKey }` (65-byte uncompressed) yields a FluidKey meta-address at `0xfffe`; `{ kind: "canonical", spendingPubKey, viewingPubKey }` (two 33-byte compressed keys, both curve-validated) yields a canonical meta-address at `0x0001`.
-   `generateEphemeralKeyPair` draws from WebCrypto only (rejection-sampled, no fallback) and produces the ephemeral public key in the scheme's wire format: compressed for canonical, uncompressed for FluidKey.
-   `deriveStealthAddress` dispatches on `metaAddress.scheme`; the explicit `schemeId` argument is defensive and must match (`StealthAddressSchemeMismatchError` otherwise). It returns `{ stealthAddress, announcement }` and deliberately drops the meta-address from the result.
-   `composeWithdrawalBundle` requires the withdraw call's `declaredDestination` to equal the announcement's `stealthAddress`, the client-side form of the binding [`PrivacyPoolRelay.relayAndAnnounce`](/protocol/contracts/privacy-pool-relay) enforces on-chain.
-   `prepareStealthWithdrawal` is the orchestrator: resolve, derive, call your `generateWithdrawCall(stealthAddress)` callback, and compose the bundle against `request.announcerAddress`.
-   `toStealthAnnouncementFields` converts a derivation into the `announcement` object `RelayerInteractor.relayWithAnnounce` expects.

## Recipient side

```ts
scanAnnouncement(announcement: Announcement, credentials: ScanCredentials): EvmAddress | null
deriveStealthPrivateKey(announcement: Announcement, credentials: DerivePrivCredentials): Scalar32
recoverStealthSharedSecret(...)
verifyAnnouncementBindsToWithdrawal(announcement, withdrawalLog: WithdrawalLogEvent): boolean
scanDepositNoteData(data: Hex, credentials: ScanCredentials): DepositStealthMatch | null
encodeStealthDepositPayload(payload: StealthDepositPayload): Hex
decodeStealthDepositPayload(data: Hex): StealthDepositPayload
```

-   `scanAnnouncement` requires `announcement.schemeId === credentials.scheme`, recomputes the shared hash from the recipient's key material (viewing private key for canonical, spending private key for FluidKey), compares its first byte to the view tag, and only on a hit reconstructs the candidate and compares it to the announced address.
-   `deriveStealthPrivateKey` produces the spending key for a matched address and self-validates it against the announced address (`StealthAddressScanIntegrityError` on mismatch).
-   `verifyAnnouncementBindsToWithdrawal` is the recipient-side mirror of the on-chain binding: it checks an announcement against a same-transaction withdrawal event.
-   The deposit-payload trio carries an ephemeral key, view tag, and ECDH-encrypted note payload inside a deposit's opaque `noteData.data` instead of a public announcement. Unlike `scanAnnouncement` there is no separately claimed on-chain address to verify against, so the derived candidate *is* the result. Used by the cross-chain [reshield](/operations/reshield) dust rescue.

## Types

```ts
type SchemeId = typeof SCHEME_CANONICAL | typeof SCHEME_FLUIDKEY;
type MetaAddress = CanonicalMetaAddress | FluidKeyMetaAddress;   // discriminated on `scheme`
type Announcement = { schemeId; stealthAddress; ephemeralPubKey; viewTag; ... };
type StealthDerivation = { stealthAddress: EvmAddress; announcement: Announcement };
type RecipientIdentifier =
  | { kind: "raw"; spendingPubKey: Hex }
  | { kind: "canonical"; spendingPubKey: Hex; viewingPubKey: Hex };
type StealthWithdrawalRequest = {
  recipient: RecipientIdentifier;
  generateWithdrawCall: (stealthAddress: EvmAddress) => Promise<EvmCall> | EvmCall;
  announcerAddress: EvmAddress;
  schemeId?: SchemeId;
};
type StealthWithdrawalBundle = { withdraw: EvmCall; announce: EvmCall };
type DeclaredWithdrawCall = { to: EvmAddress; data: Hex; declaredDestination: EvmAddress };
type EvmCall = { to: EvmAddress; data: Hex };
type ScanCredentials / DerivePrivCredentials   // per-scheme unions of the recipient's key material
type StealthDepositPayload / DepositStealthMatch / WithdrawalLogEvent
```

Branded hex types (`CompressedPubKey`, `UncompressedPubKey`, `Scalar32`, `EvmAddress`) validate width, prefix, and curve prefix at the codec boundary. `assertNever` is exported for exhaustive switches over the scheme union.

## Errors

All extend `StealthAddressError`: `StealthAddressDeriveError`, `StealthAddressUnsupportedSchemeError`, `StealthAddressSchemeMismatchError`, `StealthAddressAnnouncementError`, `StealthAddressScanIntegrityError`. They are runtime values on this subpath, so `instanceof` works here, unlike most errors on the main barrel.

## Relation to the main SDK

`RelayerInteractor.relayWithAnnounce(relayer, params)` on the main barrel is the transport for the atomic relayed path; its `params` are `WithdrawalRelayParams & { announcement: StealthAnnouncementFields }`. `PoolSessionReshieldCrosschain` accepts a `dustRescue: { recipientIdentifier }` opt-in that reuses this module's derivation for the destination-chain dust deposit. Stealth does not compose with [batch withdrawal](/operations/batch-withdraw) or with a [yield](/concepts/yield-shares) unwrap.

Source: `v2-monorepo/packages/sdk/src/stealth/`
