@orbinum/sdk 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1925,6 +1925,11 @@ declare class NoteBuilder {
1925
1925
  * Layout (180 bytes, ECDH):
1926
1926
  * nonce(12) || ciphertext+MAC(136) || ephPk_packed(32) = 180
1927
1927
  *
1928
+ * View tag (memos built by SDK ≥ 0.15): nonce[0] = deriveViewTag(sharedSecret)
1929
+ * — see memo.ts. Layout and size are unchanged; legacy memos carry a random
1930
+ * byte there instead, so the filter is only sound at/after the wallet's
1931
+ * tagActivationLeaf.
1932
+ *
1928
1933
  * Plaintext layout (120 bytes):
1929
1934
  * value_lo(8 LE) || value_hi(8 LE) || owner_pk(32) || blinding(32) || asset_id(4 LE) || counterparty_pk(32) || circuit_version(4 LE)
1930
1935
  *
@@ -2007,10 +2012,42 @@ declare const EncryptedMemo: {
2007
2012
  * @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
2008
2013
  */
2009
2014
  extractSharedSecret(memoBytes: Uint8Array, viewingSecretKey: Uint8Array): Uint8Array | null;
2015
+ /**
2016
+ * Cheap view-tag check: does memo nonce[0] match the tag derived from
2017
+ * `sharedSecret`? One SHA256 + one byte compare — no AEAD work.
2018
+ *
2019
+ * Only meaningful for memos built with view tags (commitments at/after
2020
+ * the wallet's tagActivationLeaf): a legacy memo carries a random byte
2021
+ * there and would false-negative 255/256 of the time.
2022
+ */
2023
+ checkViewTag(memoBytes: Uint8Array, sharedSecret: Uint8Array): boolean;
2024
+ /**
2025
+ * Decrypt with an already-computed shared secret (from
2026
+ * extractSharedSecret), skipping the ECDH. Pair with checkViewTag for the
2027
+ * fast scan path: ECDH once → tag check → decrypt only on match.
2028
+ */
2029
+ decryptWithSharedSecret(memoBytes: Uint8Array, commitment: Uint8Array, sharedSecret: Uint8Array): DecryptedMemo | null;
2010
2030
  /** @internal */
2011
2031
  _decrypt(memoBytes: Uint8Array, commitment: Uint8Array, viewingSecretKey: Uint8Array): DecryptedMemo | null;
2012
2032
  };
2013
2033
 
2034
+ /**
2035
+ * Derive the 1-byte view tag (Monero-style fast-scan filter) from the ECDH
2036
+ * shared secret:
2037
+ * view_tag = SHA256("orbinum-view-tag-v1" || sharedSecret)[0]
2038
+ *
2039
+ * The sender embeds it as the memo's first nonce byte (nonce[0]); a scanner
2040
+ * that has computed the shared secret compares one byte and skips the AEAD
2041
+ * decrypt on mismatch — 255/256 of foreign notes. Safe to publish: without
2042
+ * the viewing key the shared secret is unknowable, so the byte reads as
2043
+ * uniform noise to any observer.
2044
+ *
2045
+ * Nonce safety: the encryption key is unique per note (see
2046
+ * deriveEncryptionKey), so each key encrypts exactly one message and the
2047
+ * remaining 11 random nonce bytes are more than enough.
2048
+ */
2049
+ declare function deriveViewTag(sharedSecret: Uint8Array): number;
2050
+
2014
2051
  /**
2015
2052
  * NoteDecryptor
2016
2053
  *
@@ -2034,6 +2071,17 @@ declare const EncryptedMemo: {
2034
2071
  * deriveSpendingKeyFromSignature.
2035
2072
  */
2036
2073
  declare function computeNullifier(commitment: bigint, spendingKey: bigint): bigint;
2074
+ interface TryDecryptOptions {
2075
+ /**
2076
+ * View-tag fast path: compute the ECDH shared secret once, compare the
2077
+ * 1-byte tag in memo nonce[0], and skip the AEAD decrypt on mismatch
2078
+ * (255/256 of foreign notes; `reason: 'view_tag_mismatch'`).
2079
+ *
2080
+ * Only enable for commitments at/after the wallet's tagActivationLeaf —
2081
+ * legacy memos carry a random byte there and would be silently dropped.
2082
+ */
2083
+ viewTag?: boolean;
2084
+ }
2037
2085
  /**
2038
2086
  * Attempt to decrypt an on-chain commitment using the recipient's viewing secret key.
2039
2087
  *
@@ -2047,13 +2095,14 @@ declare function computeNullifier(commitment: bigint, spendingKey: bigint): bigi
2047
2095
  * @param spendingKey Spending key bigint (for nullifier computation).
2048
2096
  * @param ownOwnerPk The viewer's global BabyJubJub Ax (ownerPk). Required for stealth detection.
2049
2097
  * Pass 0n to disable stealth detection (legacy/own-note-only scanning).
2098
+ * @param opts See TryDecryptOptions (view-tag fast path).
2050
2099
  */
2051
- declare function tryDecryptNote(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint): ZkNote | null;
2100
+ declare function tryDecryptNote(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint, opts?: TryDecryptOptions): ZkNote | null;
2052
2101
  /**
2053
2102
  * Like tryDecryptNote but also returns a human-readable reason for failure.
2054
2103
  * Useful for debugging scan issues (wrong key, corrupted memo, commitment mismatch).
2055
2104
  */
2056
- declare function tryDecryptNoteVerbose(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint): {
2105
+ declare function tryDecryptNoteVerbose(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint, opts?: TryDecryptOptions): {
2057
2106
  note: ZkNote | null;
2058
2107
  reason?: string;
2059
2108
  };
@@ -4172,4 +4221,4 @@ interface ExtrinsicFailedData {
4172
4221
  dispatch_info: DispatchInfo;
4173
4222
  }
4174
4223
 
4175
- export { type AccountListing, type AccountMappedData, type AccountMappedEvent, type AccountMappingCall, type AccountMappingEvent, AccountMappingModule, AccountMappingPrecompile, type AccountMetadata, type AccountUnmappedEvent, type ActiveVersionSetEvent, type AddChainLinkArgs, type AddChainLinkParams, type AddSupportedChainArgs, type AliasFullIdentity, type AliasInfo, type AliasListedForSaleEvent, type AliasOnSaleData, type AliasRegisteredData, type AliasRegisteredEvent, type AliasReleasedEvent, type AliasSaleCancelledEvent, type AliasSoldData, type AliasSoldEvent, type AliasTransferredData, type AliasTransferredEvent, type AssetRegisteredEvent, type AssetUnverifiedEvent, type AssetVerifiedEvent, BABYJUB_SUBORDER, BN254_R, type BatchRegisterVerificationKeysArgs, type BatchVerificationKeysRegisteredEvent, type BlockInfo, type BuyAliasArgs, type Bytes32, CURRENT_CIRCUIT_VERSION, type ChainInfo, type ChainLink, type ChainLinkAddedEvent, type ChainLinkRemovedEvent, CircuitId, CircuitId as CircuitIdType, CircuitVersionResolver, type ClaimShieldedFeesParams, type ClientProviderConfig, type CommitmentsInsertedEvent, type CommitmentsInsertedEventData, type ConnectionStatus, CryptoPrecompiles, type DecodedAddChainLinkArgs, type DecodedBatchArgs, type DecodedDispatchAsPrivateLinkArgs, type DecodedEthereumTransactArgs, type DecodedEvmCallArgs, type DecodedPrecompile, type DecodedPrivateTransferArgs, type DecodedPutAliasForSaleArgs, type DecodedRegisterAliasArgs, type DecodedRemarkArgs, type DecodedRevealPrivateLinkArgs, type DecodedSetAccountMetadataArgs, type DecodedShieldArgs, type DecodedShieldBatchArgs, type DecodedShieldBatchOperation, type DecodedSudoArgs, type DecodedTransferAllArgs, type DecodedTransferArgs, type DecodedTransferKeepAliveArgs, type DecodedUnshieldArgs, type DecryptedMemo, type DispatchAsLinkedAccountArgs, type DispatchAsLinkedParams, type DispatchAsPrivateLinkArgs, type DispatchError, type DispatchInfo, type DynamicBuilder, ENCRYPTED_MEMO_SIZE, EncryptedMemo, type EncryptedNoteRecord, type EndowedEventData, type EthereumExecutedData, type EventData, type EventPhase, type EventRecord, type EvmAddressInfo, type EvmBlock, EvmClient, type EvmExecutedData, type EvmExitReason, EvmExplorer, type EvmLog, type EvmSigner, type EvmTransaction, type EvmTxRequest, type EvmTxSummary, type ExtrinsicDecoder, type ExtrinsicFailedData, type ExtrinsicSuccessData, type FeeClaimProofInputs, type FormatOptions, KNOWN_PRECOMPILES, type KnownPrecompileInfo, type ListingInfo, type MerkleRootUpdatedData, type MerkleRootUpdatedEvent, type MerkleTreeInfo, type MetadataUpdatedEvent, NoteBuilder, type NoteDisclosure, type NoteInput, type NoteStatusUpdate, type NullifiersSpentEvent, type NullifiersSpentEventData, OrbinumClient, type OrbinumClientConfig, OrbinumClientProvider, PRECOMPILE_ADDR, PrivacyKeyManager, type PrivacyMerkleProof, PrivacyModule, type PrivateChainLinkAddedEvent, type PrivateChainLinkRemovedEvent, type PrivateChainLinkRevealedEvent, type PrivateLink, type PrivateLinkDispatchExecutedEvent, type PrivateTransferArgs, type PrivateTransferInput, type PrivateTransferOutput, type PrivateTransferParams, type PrivateTransferProofInputs, type ProofVerificationFailedEvent, type ProofVerifiedEvent, type ProxyCallExecutedEvent, type PutAliasOnSaleArgs, type PutOnSaleParams, type RawBlock, type RawBlockHeader, type RawTransferInput, type RawTransferOutput, type RegisterAliasArgs, type RegisterAssetArgs, type RegisterPrivateLinkArgs, type RegisterVerificationKeyArgs, type RelayerInfo, RelayerStatusModule, type RemoveChainLinkArgs, type RemovePrivateLinkArgs, type RemoveSupportedChainArgs, type RemoveVerificationKeyArgs, type ReservedEventData, type ResolvedAlias, type ResolvedSpendVersion, type RevealPrivateLinkArgs, type RpcV2MerkleProof, type RpcV2NullifierStatus, type RpcV2PoolAssetBalance, type RpcV2PoolStats, SLIP0044_NAMESPACE, type ScanCommitment, type SetAccountMetadataArgs, type SetActiveVersionArgs, type SetMetadataParams, type ShieldArgs, type ShieldBatchArgs, type ShieldBatchItem, type ShieldBatchParams, type ShieldOperation, type ShieldParams, type ShieldedEvent, type ShieldedEventData, type ShieldedPoolCall, type ShieldedPoolEvent, ShieldedPoolModule, ShieldedPoolPrecompile, SignatureScheme, type StatusChangeEvent, type StatusListener, SubstrateClient, type SupportedChain, type SupportedChainAddedEvent, type SupportedChainRemovedEvent, type SystemHealth, type TokenInfo, type TokenTransfer, type TransferAliasArgs, type TransferEventData, type TransferInputNote, type TransferOutputNote, type TxResult, type UnsafeTxOptions, type UnshieldArgs, type UnshieldParams, type UnshieldProofInputs, type UnshieldedEvent, type UnshieldedEventData, type UnverifyAssetArgs, VaultLockedError, type VerificationKeyRegisteredEvent, type VerificationKeyRemovedEvent, type VerifyAssetArgs, type VerifyProofArgs, type VkEntry, type ZkNote, type ZkVerifierCall, type ZkVerifierCircuitVersionInfo, type ZkVerifierEvent, type ZkVerifierHistoricalVersion, ZkVerifierModule, type ZkVerifierVersionStats, type ZkVerifierVkHash, accountIdHexToSs58, addressToAccountIdHex, applyNoteStatus, bigintTo32Be, bigintTo32Le, bigintTo32LeArr, blindTag, buildDummyTransferInput, bytesToBigintLE, computeNullifier, computePathIndices, createNoteDisclosureKey, decodeNoteDisclosureKey, decodePrecompileCalldata, decryptJson, decryptNoteRecord, deriveMasterKeyBytes, deriveOwnerPk, deriveSpendingKeyFromSignature, deriveSpendingKeyMessage, deriveStealthOwnerPk, deriveStealthSk, deriveVaultBlindKey, deriveVaultKey, deriveViewingPublicKey, deriveViewingSecretKey, encryptJson, encryptNote, ensureHexPrefix, evmAddressToAccountId, evmToImplicitSubstrate, evmToMappedAccountHex, evmToSubstrate, formatBalance, formatORB, fromBase64, fromHex, generateFeeClaimProof, generateTransferProof, generateUnshieldProof, getPrecompileLabel, hexToBigint, hexToNumber, implicitSubstrateToEvm, isEvmAddress, isImplicitEvmAccount, isSs58, isSubstrateAddress, isUnifiedAddress, leHexToBigint, mapExtrinsicArgs, mapZkEventData, normalizeEvmAddress, noteBlindTag, randomBlinding, recoverOwnerPkPoint, selectNotes, shortHash, substrateSs58ToAccountIdHex, substrateToEvm, toBase64, toHex, toTxResult, truncateMiddle, tryDecryptNote, tryDecryptNoteVerbose, vaultReplacer, vaultReviver };
4224
+ export { type AccountListing, type AccountMappedData, type AccountMappedEvent, type AccountMappingCall, type AccountMappingEvent, AccountMappingModule, AccountMappingPrecompile, type AccountMetadata, type AccountUnmappedEvent, type ActiveVersionSetEvent, type AddChainLinkArgs, type AddChainLinkParams, type AddSupportedChainArgs, type AliasFullIdentity, type AliasInfo, type AliasListedForSaleEvent, type AliasOnSaleData, type AliasRegisteredData, type AliasRegisteredEvent, type AliasReleasedEvent, type AliasSaleCancelledEvent, type AliasSoldData, type AliasSoldEvent, type AliasTransferredData, type AliasTransferredEvent, type AssetRegisteredEvent, type AssetUnverifiedEvent, type AssetVerifiedEvent, BABYJUB_SUBORDER, BN254_R, type BatchRegisterVerificationKeysArgs, type BatchVerificationKeysRegisteredEvent, type BlockInfo, type BuyAliasArgs, type Bytes32, CURRENT_CIRCUIT_VERSION, type ChainInfo, type ChainLink, type ChainLinkAddedEvent, type ChainLinkRemovedEvent, CircuitId, CircuitId as CircuitIdType, CircuitVersionResolver, type ClaimShieldedFeesParams, type ClientProviderConfig, type CommitmentsInsertedEvent, type CommitmentsInsertedEventData, type ConnectionStatus, CryptoPrecompiles, type DecodedAddChainLinkArgs, type DecodedBatchArgs, type DecodedDispatchAsPrivateLinkArgs, type DecodedEthereumTransactArgs, type DecodedEvmCallArgs, type DecodedPrecompile, type DecodedPrivateTransferArgs, type DecodedPutAliasForSaleArgs, type DecodedRegisterAliasArgs, type DecodedRemarkArgs, type DecodedRevealPrivateLinkArgs, type DecodedSetAccountMetadataArgs, type DecodedShieldArgs, type DecodedShieldBatchArgs, type DecodedShieldBatchOperation, type DecodedSudoArgs, type DecodedTransferAllArgs, type DecodedTransferArgs, type DecodedTransferKeepAliveArgs, type DecodedUnshieldArgs, type DecryptedMemo, type DispatchAsLinkedAccountArgs, type DispatchAsLinkedParams, type DispatchAsPrivateLinkArgs, type DispatchError, type DispatchInfo, type DynamicBuilder, ENCRYPTED_MEMO_SIZE, EncryptedMemo, type EncryptedNoteRecord, type EndowedEventData, type EthereumExecutedData, type EventData, type EventPhase, type EventRecord, type EvmAddressInfo, type EvmBlock, EvmClient, type EvmExecutedData, type EvmExitReason, EvmExplorer, type EvmLog, type EvmSigner, type EvmTransaction, type EvmTxRequest, type EvmTxSummary, type ExtrinsicDecoder, type ExtrinsicFailedData, type ExtrinsicSuccessData, type FeeClaimProofInputs, type FormatOptions, KNOWN_PRECOMPILES, type KnownPrecompileInfo, type ListingInfo, type MerkleRootUpdatedData, type MerkleRootUpdatedEvent, type MerkleTreeInfo, type MetadataUpdatedEvent, NoteBuilder, type NoteDisclosure, type NoteInput, type NoteStatusUpdate, type NullifiersSpentEvent, type NullifiersSpentEventData, OrbinumClient, type OrbinumClientConfig, OrbinumClientProvider, PRECOMPILE_ADDR, PrivacyKeyManager, type PrivacyMerkleProof, PrivacyModule, type PrivateChainLinkAddedEvent, type PrivateChainLinkRemovedEvent, type PrivateChainLinkRevealedEvent, type PrivateLink, type PrivateLinkDispatchExecutedEvent, type PrivateTransferArgs, type PrivateTransferInput, type PrivateTransferOutput, type PrivateTransferParams, type PrivateTransferProofInputs, type ProofVerificationFailedEvent, type ProofVerifiedEvent, type ProxyCallExecutedEvent, type PutAliasOnSaleArgs, type PutOnSaleParams, type RawBlock, type RawBlockHeader, type RawTransferInput, type RawTransferOutput, type RegisterAliasArgs, type RegisterAssetArgs, type RegisterPrivateLinkArgs, type RegisterVerificationKeyArgs, type RelayerInfo, RelayerStatusModule, type RemoveChainLinkArgs, type RemovePrivateLinkArgs, type RemoveSupportedChainArgs, type RemoveVerificationKeyArgs, type ReservedEventData, type ResolvedAlias, type ResolvedSpendVersion, type RevealPrivateLinkArgs, type RpcV2MerkleProof, type RpcV2NullifierStatus, type RpcV2PoolAssetBalance, type RpcV2PoolStats, SLIP0044_NAMESPACE, type ScanCommitment, type SetAccountMetadataArgs, type SetActiveVersionArgs, type SetMetadataParams, type ShieldArgs, type ShieldBatchArgs, type ShieldBatchItem, type ShieldBatchParams, type ShieldOperation, type ShieldParams, type ShieldedEvent, type ShieldedEventData, type ShieldedPoolCall, type ShieldedPoolEvent, ShieldedPoolModule, ShieldedPoolPrecompile, SignatureScheme, type StatusChangeEvent, type StatusListener, SubstrateClient, type SupportedChain, type SupportedChainAddedEvent, type SupportedChainRemovedEvent, type SystemHealth, type TokenInfo, type TokenTransfer, type TransferAliasArgs, type TransferEventData, type TransferInputNote, type TransferOutputNote, type TryDecryptOptions, type TxResult, type UnsafeTxOptions, type UnshieldArgs, type UnshieldParams, type UnshieldProofInputs, type UnshieldedEvent, type UnshieldedEventData, type UnverifyAssetArgs, VaultLockedError, type VerificationKeyRegisteredEvent, type VerificationKeyRemovedEvent, type VerifyAssetArgs, type VerifyProofArgs, type VkEntry, type ZkNote, type ZkVerifierCall, type ZkVerifierCircuitVersionInfo, type ZkVerifierEvent, type ZkVerifierHistoricalVersion, ZkVerifierModule, type ZkVerifierVersionStats, type ZkVerifierVkHash, accountIdHexToSs58, addressToAccountIdHex, applyNoteStatus, bigintTo32Be, bigintTo32Le, bigintTo32LeArr, blindTag, buildDummyTransferInput, bytesToBigintLE, computeNullifier, computePathIndices, createNoteDisclosureKey, decodeNoteDisclosureKey, decodePrecompileCalldata, decryptJson, decryptNoteRecord, deriveMasterKeyBytes, deriveOwnerPk, deriveSpendingKeyFromSignature, deriveSpendingKeyMessage, deriveStealthOwnerPk, deriveStealthSk, deriveVaultBlindKey, deriveVaultKey, deriveViewTag, deriveViewingPublicKey, deriveViewingSecretKey, encryptJson, encryptNote, ensureHexPrefix, evmAddressToAccountId, evmToImplicitSubstrate, evmToMappedAccountHex, evmToSubstrate, formatBalance, formatORB, fromBase64, fromHex, generateFeeClaimProof, generateTransferProof, generateUnshieldProof, getPrecompileLabel, hexToBigint, hexToNumber, implicitSubstrateToEvm, isEvmAddress, isImplicitEvmAccount, isSs58, isSubstrateAddress, isUnifiedAddress, leHexToBigint, mapExtrinsicArgs, mapZkEventData, normalizeEvmAddress, noteBlindTag, randomBlinding, recoverOwnerPkPoint, selectNotes, shortHash, substrateSs58ToAccountIdHex, substrateToEvm, toBase64, toHex, toTxResult, truncateMiddle, tryDecryptNote, tryDecryptNoteVerbose, vaultReplacer, vaultReviver };
package/dist/index.d.ts CHANGED
@@ -1925,6 +1925,11 @@ declare class NoteBuilder {
1925
1925
  * Layout (180 bytes, ECDH):
1926
1926
  * nonce(12) || ciphertext+MAC(136) || ephPk_packed(32) = 180
1927
1927
  *
1928
+ * View tag (memos built by SDK ≥ 0.15): nonce[0] = deriveViewTag(sharedSecret)
1929
+ * — see memo.ts. Layout and size are unchanged; legacy memos carry a random
1930
+ * byte there instead, so the filter is only sound at/after the wallet's
1931
+ * tagActivationLeaf.
1932
+ *
1928
1933
  * Plaintext layout (120 bytes):
1929
1934
  * value_lo(8 LE) || value_hi(8 LE) || owner_pk(32) || blinding(32) || asset_id(4 LE) || counterparty_pk(32) || circuit_version(4 LE)
1930
1935
  *
@@ -2007,10 +2012,42 @@ declare const EncryptedMemo: {
2007
2012
  * @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
2008
2013
  */
2009
2014
  extractSharedSecret(memoBytes: Uint8Array, viewingSecretKey: Uint8Array): Uint8Array | null;
2015
+ /**
2016
+ * Cheap view-tag check: does memo nonce[0] match the tag derived from
2017
+ * `sharedSecret`? One SHA256 + one byte compare — no AEAD work.
2018
+ *
2019
+ * Only meaningful for memos built with view tags (commitments at/after
2020
+ * the wallet's tagActivationLeaf): a legacy memo carries a random byte
2021
+ * there and would false-negative 255/256 of the time.
2022
+ */
2023
+ checkViewTag(memoBytes: Uint8Array, sharedSecret: Uint8Array): boolean;
2024
+ /**
2025
+ * Decrypt with an already-computed shared secret (from
2026
+ * extractSharedSecret), skipping the ECDH. Pair with checkViewTag for the
2027
+ * fast scan path: ECDH once → tag check → decrypt only on match.
2028
+ */
2029
+ decryptWithSharedSecret(memoBytes: Uint8Array, commitment: Uint8Array, sharedSecret: Uint8Array): DecryptedMemo | null;
2010
2030
  /** @internal */
2011
2031
  _decrypt(memoBytes: Uint8Array, commitment: Uint8Array, viewingSecretKey: Uint8Array): DecryptedMemo | null;
2012
2032
  };
2013
2033
 
2034
+ /**
2035
+ * Derive the 1-byte view tag (Monero-style fast-scan filter) from the ECDH
2036
+ * shared secret:
2037
+ * view_tag = SHA256("orbinum-view-tag-v1" || sharedSecret)[0]
2038
+ *
2039
+ * The sender embeds it as the memo's first nonce byte (nonce[0]); a scanner
2040
+ * that has computed the shared secret compares one byte and skips the AEAD
2041
+ * decrypt on mismatch — 255/256 of foreign notes. Safe to publish: without
2042
+ * the viewing key the shared secret is unknowable, so the byte reads as
2043
+ * uniform noise to any observer.
2044
+ *
2045
+ * Nonce safety: the encryption key is unique per note (see
2046
+ * deriveEncryptionKey), so each key encrypts exactly one message and the
2047
+ * remaining 11 random nonce bytes are more than enough.
2048
+ */
2049
+ declare function deriveViewTag(sharedSecret: Uint8Array): number;
2050
+
2014
2051
  /**
2015
2052
  * NoteDecryptor
2016
2053
  *
@@ -2034,6 +2071,17 @@ declare const EncryptedMemo: {
2034
2071
  * deriveSpendingKeyFromSignature.
2035
2072
  */
2036
2073
  declare function computeNullifier(commitment: bigint, spendingKey: bigint): bigint;
2074
+ interface TryDecryptOptions {
2075
+ /**
2076
+ * View-tag fast path: compute the ECDH shared secret once, compare the
2077
+ * 1-byte tag in memo nonce[0], and skip the AEAD decrypt on mismatch
2078
+ * (255/256 of foreign notes; `reason: 'view_tag_mismatch'`).
2079
+ *
2080
+ * Only enable for commitments at/after the wallet's tagActivationLeaf —
2081
+ * legacy memos carry a random byte there and would be silently dropped.
2082
+ */
2083
+ viewTag?: boolean;
2084
+ }
2037
2085
  /**
2038
2086
  * Attempt to decrypt an on-chain commitment using the recipient's viewing secret key.
2039
2087
  *
@@ -2047,13 +2095,14 @@ declare function computeNullifier(commitment: bigint, spendingKey: bigint): bigi
2047
2095
  * @param spendingKey Spending key bigint (for nullifier computation).
2048
2096
  * @param ownOwnerPk The viewer's global BabyJubJub Ax (ownerPk). Required for stealth detection.
2049
2097
  * Pass 0n to disable stealth detection (legacy/own-note-only scanning).
2098
+ * @param opts See TryDecryptOptions (view-tag fast path).
2050
2099
  */
2051
- declare function tryDecryptNote(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint): ZkNote | null;
2100
+ declare function tryDecryptNote(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint, opts?: TryDecryptOptions): ZkNote | null;
2052
2101
  /**
2053
2102
  * Like tryDecryptNote but also returns a human-readable reason for failure.
2054
2103
  * Useful for debugging scan issues (wrong key, corrupted memo, commitment mismatch).
2055
2104
  */
2056
- declare function tryDecryptNoteVerbose(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint): {
2105
+ declare function tryDecryptNoteVerbose(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint, opts?: TryDecryptOptions): {
2057
2106
  note: ZkNote | null;
2058
2107
  reason?: string;
2059
2108
  };
@@ -4172,4 +4221,4 @@ interface ExtrinsicFailedData {
4172
4221
  dispatch_info: DispatchInfo;
4173
4222
  }
4174
4223
 
4175
- export { type AccountListing, type AccountMappedData, type AccountMappedEvent, type AccountMappingCall, type AccountMappingEvent, AccountMappingModule, AccountMappingPrecompile, type AccountMetadata, type AccountUnmappedEvent, type ActiveVersionSetEvent, type AddChainLinkArgs, type AddChainLinkParams, type AddSupportedChainArgs, type AliasFullIdentity, type AliasInfo, type AliasListedForSaleEvent, type AliasOnSaleData, type AliasRegisteredData, type AliasRegisteredEvent, type AliasReleasedEvent, type AliasSaleCancelledEvent, type AliasSoldData, type AliasSoldEvent, type AliasTransferredData, type AliasTransferredEvent, type AssetRegisteredEvent, type AssetUnverifiedEvent, type AssetVerifiedEvent, BABYJUB_SUBORDER, BN254_R, type BatchRegisterVerificationKeysArgs, type BatchVerificationKeysRegisteredEvent, type BlockInfo, type BuyAliasArgs, type Bytes32, CURRENT_CIRCUIT_VERSION, type ChainInfo, type ChainLink, type ChainLinkAddedEvent, type ChainLinkRemovedEvent, CircuitId, CircuitId as CircuitIdType, CircuitVersionResolver, type ClaimShieldedFeesParams, type ClientProviderConfig, type CommitmentsInsertedEvent, type CommitmentsInsertedEventData, type ConnectionStatus, CryptoPrecompiles, type DecodedAddChainLinkArgs, type DecodedBatchArgs, type DecodedDispatchAsPrivateLinkArgs, type DecodedEthereumTransactArgs, type DecodedEvmCallArgs, type DecodedPrecompile, type DecodedPrivateTransferArgs, type DecodedPutAliasForSaleArgs, type DecodedRegisterAliasArgs, type DecodedRemarkArgs, type DecodedRevealPrivateLinkArgs, type DecodedSetAccountMetadataArgs, type DecodedShieldArgs, type DecodedShieldBatchArgs, type DecodedShieldBatchOperation, type DecodedSudoArgs, type DecodedTransferAllArgs, type DecodedTransferArgs, type DecodedTransferKeepAliveArgs, type DecodedUnshieldArgs, type DecryptedMemo, type DispatchAsLinkedAccountArgs, type DispatchAsLinkedParams, type DispatchAsPrivateLinkArgs, type DispatchError, type DispatchInfo, type DynamicBuilder, ENCRYPTED_MEMO_SIZE, EncryptedMemo, type EncryptedNoteRecord, type EndowedEventData, type EthereumExecutedData, type EventData, type EventPhase, type EventRecord, type EvmAddressInfo, type EvmBlock, EvmClient, type EvmExecutedData, type EvmExitReason, EvmExplorer, type EvmLog, type EvmSigner, type EvmTransaction, type EvmTxRequest, type EvmTxSummary, type ExtrinsicDecoder, type ExtrinsicFailedData, type ExtrinsicSuccessData, type FeeClaimProofInputs, type FormatOptions, KNOWN_PRECOMPILES, type KnownPrecompileInfo, type ListingInfo, type MerkleRootUpdatedData, type MerkleRootUpdatedEvent, type MerkleTreeInfo, type MetadataUpdatedEvent, NoteBuilder, type NoteDisclosure, type NoteInput, type NoteStatusUpdate, type NullifiersSpentEvent, type NullifiersSpentEventData, OrbinumClient, type OrbinumClientConfig, OrbinumClientProvider, PRECOMPILE_ADDR, PrivacyKeyManager, type PrivacyMerkleProof, PrivacyModule, type PrivateChainLinkAddedEvent, type PrivateChainLinkRemovedEvent, type PrivateChainLinkRevealedEvent, type PrivateLink, type PrivateLinkDispatchExecutedEvent, type PrivateTransferArgs, type PrivateTransferInput, type PrivateTransferOutput, type PrivateTransferParams, type PrivateTransferProofInputs, type ProofVerificationFailedEvent, type ProofVerifiedEvent, type ProxyCallExecutedEvent, type PutAliasOnSaleArgs, type PutOnSaleParams, type RawBlock, type RawBlockHeader, type RawTransferInput, type RawTransferOutput, type RegisterAliasArgs, type RegisterAssetArgs, type RegisterPrivateLinkArgs, type RegisterVerificationKeyArgs, type RelayerInfo, RelayerStatusModule, type RemoveChainLinkArgs, type RemovePrivateLinkArgs, type RemoveSupportedChainArgs, type RemoveVerificationKeyArgs, type ReservedEventData, type ResolvedAlias, type ResolvedSpendVersion, type RevealPrivateLinkArgs, type RpcV2MerkleProof, type RpcV2NullifierStatus, type RpcV2PoolAssetBalance, type RpcV2PoolStats, SLIP0044_NAMESPACE, type ScanCommitment, type SetAccountMetadataArgs, type SetActiveVersionArgs, type SetMetadataParams, type ShieldArgs, type ShieldBatchArgs, type ShieldBatchItem, type ShieldBatchParams, type ShieldOperation, type ShieldParams, type ShieldedEvent, type ShieldedEventData, type ShieldedPoolCall, type ShieldedPoolEvent, ShieldedPoolModule, ShieldedPoolPrecompile, SignatureScheme, type StatusChangeEvent, type StatusListener, SubstrateClient, type SupportedChain, type SupportedChainAddedEvent, type SupportedChainRemovedEvent, type SystemHealth, type TokenInfo, type TokenTransfer, type TransferAliasArgs, type TransferEventData, type TransferInputNote, type TransferOutputNote, type TxResult, type UnsafeTxOptions, type UnshieldArgs, type UnshieldParams, type UnshieldProofInputs, type UnshieldedEvent, type UnshieldedEventData, type UnverifyAssetArgs, VaultLockedError, type VerificationKeyRegisteredEvent, type VerificationKeyRemovedEvent, type VerifyAssetArgs, type VerifyProofArgs, type VkEntry, type ZkNote, type ZkVerifierCall, type ZkVerifierCircuitVersionInfo, type ZkVerifierEvent, type ZkVerifierHistoricalVersion, ZkVerifierModule, type ZkVerifierVersionStats, type ZkVerifierVkHash, accountIdHexToSs58, addressToAccountIdHex, applyNoteStatus, bigintTo32Be, bigintTo32Le, bigintTo32LeArr, blindTag, buildDummyTransferInput, bytesToBigintLE, computeNullifier, computePathIndices, createNoteDisclosureKey, decodeNoteDisclosureKey, decodePrecompileCalldata, decryptJson, decryptNoteRecord, deriveMasterKeyBytes, deriveOwnerPk, deriveSpendingKeyFromSignature, deriveSpendingKeyMessage, deriveStealthOwnerPk, deriveStealthSk, deriveVaultBlindKey, deriveVaultKey, deriveViewingPublicKey, deriveViewingSecretKey, encryptJson, encryptNote, ensureHexPrefix, evmAddressToAccountId, evmToImplicitSubstrate, evmToMappedAccountHex, evmToSubstrate, formatBalance, formatORB, fromBase64, fromHex, generateFeeClaimProof, generateTransferProof, generateUnshieldProof, getPrecompileLabel, hexToBigint, hexToNumber, implicitSubstrateToEvm, isEvmAddress, isImplicitEvmAccount, isSs58, isSubstrateAddress, isUnifiedAddress, leHexToBigint, mapExtrinsicArgs, mapZkEventData, normalizeEvmAddress, noteBlindTag, randomBlinding, recoverOwnerPkPoint, selectNotes, shortHash, substrateSs58ToAccountIdHex, substrateToEvm, toBase64, toHex, toTxResult, truncateMiddle, tryDecryptNote, tryDecryptNoteVerbose, vaultReplacer, vaultReviver };
4224
+ export { type AccountListing, type AccountMappedData, type AccountMappedEvent, type AccountMappingCall, type AccountMappingEvent, AccountMappingModule, AccountMappingPrecompile, type AccountMetadata, type AccountUnmappedEvent, type ActiveVersionSetEvent, type AddChainLinkArgs, type AddChainLinkParams, type AddSupportedChainArgs, type AliasFullIdentity, type AliasInfo, type AliasListedForSaleEvent, type AliasOnSaleData, type AliasRegisteredData, type AliasRegisteredEvent, type AliasReleasedEvent, type AliasSaleCancelledEvent, type AliasSoldData, type AliasSoldEvent, type AliasTransferredData, type AliasTransferredEvent, type AssetRegisteredEvent, type AssetUnverifiedEvent, type AssetVerifiedEvent, BABYJUB_SUBORDER, BN254_R, type BatchRegisterVerificationKeysArgs, type BatchVerificationKeysRegisteredEvent, type BlockInfo, type BuyAliasArgs, type Bytes32, CURRENT_CIRCUIT_VERSION, type ChainInfo, type ChainLink, type ChainLinkAddedEvent, type ChainLinkRemovedEvent, CircuitId, CircuitId as CircuitIdType, CircuitVersionResolver, type ClaimShieldedFeesParams, type ClientProviderConfig, type CommitmentsInsertedEvent, type CommitmentsInsertedEventData, type ConnectionStatus, CryptoPrecompiles, type DecodedAddChainLinkArgs, type DecodedBatchArgs, type DecodedDispatchAsPrivateLinkArgs, type DecodedEthereumTransactArgs, type DecodedEvmCallArgs, type DecodedPrecompile, type DecodedPrivateTransferArgs, type DecodedPutAliasForSaleArgs, type DecodedRegisterAliasArgs, type DecodedRemarkArgs, type DecodedRevealPrivateLinkArgs, type DecodedSetAccountMetadataArgs, type DecodedShieldArgs, type DecodedShieldBatchArgs, type DecodedShieldBatchOperation, type DecodedSudoArgs, type DecodedTransferAllArgs, type DecodedTransferArgs, type DecodedTransferKeepAliveArgs, type DecodedUnshieldArgs, type DecryptedMemo, type DispatchAsLinkedAccountArgs, type DispatchAsLinkedParams, type DispatchAsPrivateLinkArgs, type DispatchError, type DispatchInfo, type DynamicBuilder, ENCRYPTED_MEMO_SIZE, EncryptedMemo, type EncryptedNoteRecord, type EndowedEventData, type EthereumExecutedData, type EventData, type EventPhase, type EventRecord, type EvmAddressInfo, type EvmBlock, EvmClient, type EvmExecutedData, type EvmExitReason, EvmExplorer, type EvmLog, type EvmSigner, type EvmTransaction, type EvmTxRequest, type EvmTxSummary, type ExtrinsicDecoder, type ExtrinsicFailedData, type ExtrinsicSuccessData, type FeeClaimProofInputs, type FormatOptions, KNOWN_PRECOMPILES, type KnownPrecompileInfo, type ListingInfo, type MerkleRootUpdatedData, type MerkleRootUpdatedEvent, type MerkleTreeInfo, type MetadataUpdatedEvent, NoteBuilder, type NoteDisclosure, type NoteInput, type NoteStatusUpdate, type NullifiersSpentEvent, type NullifiersSpentEventData, OrbinumClient, type OrbinumClientConfig, OrbinumClientProvider, PRECOMPILE_ADDR, PrivacyKeyManager, type PrivacyMerkleProof, PrivacyModule, type PrivateChainLinkAddedEvent, type PrivateChainLinkRemovedEvent, type PrivateChainLinkRevealedEvent, type PrivateLink, type PrivateLinkDispatchExecutedEvent, type PrivateTransferArgs, type PrivateTransferInput, type PrivateTransferOutput, type PrivateTransferParams, type PrivateTransferProofInputs, type ProofVerificationFailedEvent, type ProofVerifiedEvent, type ProxyCallExecutedEvent, type PutAliasOnSaleArgs, type PutOnSaleParams, type RawBlock, type RawBlockHeader, type RawTransferInput, type RawTransferOutput, type RegisterAliasArgs, type RegisterAssetArgs, type RegisterPrivateLinkArgs, type RegisterVerificationKeyArgs, type RelayerInfo, RelayerStatusModule, type RemoveChainLinkArgs, type RemovePrivateLinkArgs, type RemoveSupportedChainArgs, type RemoveVerificationKeyArgs, type ReservedEventData, type ResolvedAlias, type ResolvedSpendVersion, type RevealPrivateLinkArgs, type RpcV2MerkleProof, type RpcV2NullifierStatus, type RpcV2PoolAssetBalance, type RpcV2PoolStats, SLIP0044_NAMESPACE, type ScanCommitment, type SetAccountMetadataArgs, type SetActiveVersionArgs, type SetMetadataParams, type ShieldArgs, type ShieldBatchArgs, type ShieldBatchItem, type ShieldBatchParams, type ShieldOperation, type ShieldParams, type ShieldedEvent, type ShieldedEventData, type ShieldedPoolCall, type ShieldedPoolEvent, ShieldedPoolModule, ShieldedPoolPrecompile, SignatureScheme, type StatusChangeEvent, type StatusListener, SubstrateClient, type SupportedChain, type SupportedChainAddedEvent, type SupportedChainRemovedEvent, type SystemHealth, type TokenInfo, type TokenTransfer, type TransferAliasArgs, type TransferEventData, type TransferInputNote, type TransferOutputNote, type TryDecryptOptions, type TxResult, type UnsafeTxOptions, type UnshieldArgs, type UnshieldParams, type UnshieldProofInputs, type UnshieldedEvent, type UnshieldedEventData, type UnverifyAssetArgs, VaultLockedError, type VerificationKeyRegisteredEvent, type VerificationKeyRemovedEvent, type VerifyAssetArgs, type VerifyProofArgs, type VkEntry, type ZkNote, type ZkVerifierCall, type ZkVerifierCircuitVersionInfo, type ZkVerifierEvent, type ZkVerifierHistoricalVersion, ZkVerifierModule, type ZkVerifierVersionStats, type ZkVerifierVkHash, accountIdHexToSs58, addressToAccountIdHex, applyNoteStatus, bigintTo32Be, bigintTo32Le, bigintTo32LeArr, blindTag, buildDummyTransferInput, bytesToBigintLE, computeNullifier, computePathIndices, createNoteDisclosureKey, decodeNoteDisclosureKey, decodePrecompileCalldata, decryptJson, decryptNoteRecord, deriveMasterKeyBytes, deriveOwnerPk, deriveSpendingKeyFromSignature, deriveSpendingKeyMessage, deriveStealthOwnerPk, deriveStealthSk, deriveVaultBlindKey, deriveVaultKey, deriveViewTag, deriveViewingPublicKey, deriveViewingSecretKey, encryptJson, encryptNote, ensureHexPrefix, evmAddressToAccountId, evmToImplicitSubstrate, evmToMappedAccountHex, evmToSubstrate, formatBalance, formatORB, fromBase64, fromHex, generateFeeClaimProof, generateTransferProof, generateUnshieldProof, getPrecompileLabel, hexToBigint, hexToNumber, implicitSubstrateToEvm, isEvmAddress, isImplicitEvmAccount, isSs58, isSubstrateAddress, isUnifiedAddress, leHexToBigint, mapExtrinsicArgs, mapZkEventData, normalizeEvmAddress, noteBlindTag, randomBlinding, recoverOwnerPkPoint, selectNotes, shortHash, substrateSs58ToAccountIdHex, substrateToEvm, toBase64, toHex, toTxResult, truncateMiddle, tryDecryptNote, tryDecryptNoteVerbose, vaultReplacer, vaultReviver };
package/dist/index.js CHANGED
@@ -79,6 +79,7 @@ __export(index_exports, {
79
79
  deriveStealthSk: () => deriveStealthSk,
80
80
  deriveVaultBlindKey: () => deriveVaultBlindKey,
81
81
  deriveVaultKey: () => deriveVaultKey,
82
+ deriveViewTag: () => deriveViewTag,
82
83
  deriveViewingPublicKey: () => deriveViewingPublicKey,
83
84
  deriveViewingSecretKey: () => deriveViewingSecretKey,
84
85
  encryptJson: () => encryptJson,
@@ -226,7 +227,7 @@ var SubstrateClient = class _SubstrateClient {
226
227
  * Throws if the node does not respond within `timeoutMs`.
227
228
  */
228
229
  static async connect(wsUrl, timeoutMs = 15e3) {
229
- const provider = (0, import_ws.getWsProvider)(wsUrl);
230
+ const provider = (0, import_ws.getWsProvider)(wsUrl, { heartbeatTimeout: 3e4 });
230
231
  const papi = (0, import_polkadot_api.createClient)(provider);
231
232
  let timer;
232
233
  try {
@@ -1262,6 +1263,7 @@ var BABYJUB_SUBORDER = 273603035897990940278080071815715938607681397215856725920
1262
1263
  // src/shielded-pool/protocol/memo.ts
1263
1264
  var import_sha2 = require("@noble/hashes/sha2.js");
1264
1265
  var KEY_DOMAIN = new TextEncoder().encode("orbinum-note-encryption-v1");
1266
+ var VIEW_TAG_DOMAIN = new TextEncoder().encode("orbinum-view-tag-v1");
1265
1267
  var MEMO_PLAINTEXT_SIZE = 120;
1266
1268
  function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk, circuitVersion) {
1267
1269
  const buf = new Uint8Array(MEMO_PLAINTEXT_SIZE);
@@ -1282,6 +1284,12 @@ function deriveEncryptionKey(sharedSecret, commitment) {
1282
1284
  h.update(KEY_DOMAIN);
1283
1285
  return h.digest();
1284
1286
  }
1287
+ function deriveViewTag(sharedSecret) {
1288
+ const h = import_sha2.sha256.create();
1289
+ h.update(VIEW_TAG_DOMAIN);
1290
+ h.update(sharedSecret);
1291
+ return h.digest()[0];
1292
+ }
1285
1293
 
1286
1294
  // src/shielded-pool/protocol/EncryptedMemo.ts
1287
1295
  var NONCE_SIZE = 12;
@@ -1358,6 +1366,7 @@ var EncryptedMemo = {
1358
1366
  const sharedPoint = (0, import_baby_jubjub.mulPointEscalar)(ivkPoint, ephSkScalar);
1359
1367
  sharedSecret = bigintTo32Le(sharedPoint[0]);
1360
1368
  }
1369
+ nonce[0] = deriveViewTag(sharedSecret);
1361
1370
  const encKey = deriveEncryptionKey(sharedSecret, commitment);
1362
1371
  const cipher = (0, import_chacha.chacha20poly1305)(encKey, nonce);
1363
1372
  const ciphertext = cipher.encrypt(plaintext);
@@ -1447,24 +1456,35 @@ var EncryptedMemo = {
1447
1456
  const sharedPoint = (0, import_baby_jubjub.mulPointEscalar)(ephPkPoint, ivskScalar);
1448
1457
  return bigintTo32Le(sharedPoint[0]);
1449
1458
  },
1450
- /** @internal */
1451
- _decrypt(memoBytes, commitment, viewingSecretKey) {
1459
+ /**
1460
+ * Cheap view-tag check: does memo nonce[0] match the tag derived from
1461
+ * `sharedSecret`? One SHA256 + one byte compare — no AEAD work.
1462
+ *
1463
+ * Only meaningful for memos built with view tags (commitments at/after
1464
+ * the wallet's tagActivationLeaf): a legacy memo carries a random byte
1465
+ * there and would false-negative 255/256 of the time.
1466
+ */
1467
+ checkViewTag(memoBytes, sharedSecret) {
1468
+ if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return false;
1469
+ return memoBytes[0] === deriveViewTag(sharedSecret);
1470
+ },
1471
+ /**
1472
+ * Decrypt with an already-computed shared secret (from
1473
+ * extractSharedSecret), skipping the ECDH. Pair with checkViewTag for the
1474
+ * fast scan path: ECDH once → tag check → decrypt only on match.
1475
+ */
1476
+ decryptWithSharedSecret(memoBytes, commitment, sharedSecret) {
1477
+ if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return null;
1452
1478
  const nonce = memoBytes.slice(0, NONCE_SIZE);
1453
1479
  const ciphertextWithMac = memoBytes.slice(NONCE_SIZE, NONCE_SIZE + CIPHERTEXT_SIZE);
1454
- const ephPkPackedBytes = memoBytes.slice(NONCE_SIZE + CIPHERTEXT_SIZE);
1455
- const ephPkPackedBigint = bytesToBigintLE(ephPkPackedBytes);
1456
- let sharedSecret;
1457
- if (ephPkPackedBigint === 0n) {
1458
- sharedSecret = new Uint8Array(32);
1459
- } else {
1460
- const ephPkPoint = (0, import_baby_jubjub.unpackPoint)(ephPkPackedBigint);
1461
- if (!ephPkPoint) return null;
1462
- const ivskScalar = bytesToBjjScalar(viewingSecretKey);
1463
- const sharedPoint = (0, import_baby_jubjub.mulPointEscalar)(ephPkPoint, ivskScalar);
1464
- sharedSecret = bigintTo32Le(sharedPoint[0]);
1465
- }
1466
1480
  const encKey = deriveEncryptionKey(sharedSecret, commitment);
1467
1481
  return parsePlaintext(nonce, ciphertextWithMac, encKey);
1482
+ },
1483
+ /** @internal */
1484
+ _decrypt(memoBytes, commitment, viewingSecretKey) {
1485
+ const sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
1486
+ if (!sharedSecret) return null;
1487
+ return EncryptedMemo.decryptWithSharedSecret(memoBytes, commitment, sharedSecret);
1468
1488
  }
1469
1489
  };
1470
1490
 
@@ -3869,10 +3889,10 @@ var import_poseidon_lite2 = require("poseidon-lite");
3869
3889
  function computeNullifier(commitment, spendingKey) {
3870
3890
  return (0, import_poseidon_lite2.poseidon2)([commitment, spendingKey]);
3871
3891
  }
3872
- function tryDecryptNote(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
3873
- return tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk).note;
3892
+ function tryDecryptNote(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n, opts) {
3893
+ return tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk, opts).note;
3874
3894
  }
3875
- function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
3895
+ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n, opts) {
3876
3896
  if (!commitment.encryptedMemo) return { note: null, reason: "no_memo" };
3877
3897
  let commitmentBytes;
3878
3898
  let memoBytes;
@@ -3888,21 +3908,29 @@ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwn
3888
3908
  reason: `memo_size_mismatch:got_${memoBytes.length}_expected_${ENCRYPTED_MEMO_SIZE}`
3889
3909
  };
3890
3910
  }
3891
- const plaintext = EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingSecretKey);
3911
+ let sharedSecret = null;
3912
+ if (opts?.viewTag) {
3913
+ sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
3914
+ if (!sharedSecret) return { note: null, reason: "stealth_shared_secret_failed" };
3915
+ if (!EncryptedMemo.checkViewTag(memoBytes, sharedSecret)) {
3916
+ return { note: null, reason: "view_tag_mismatch" };
3917
+ }
3918
+ }
3919
+ const plaintext = sharedSecret ? EncryptedMemo.decryptWithSharedSecret(memoBytes, commitmentBytes, sharedSecret) : EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingSecretKey);
3892
3920
  if (!plaintext) return { note: null, reason: "decrypt_failed:wrong_key_or_corrupt_mac" };
3893
3921
  let effectiveOwnerPk = plaintext.ownerPk;
3894
3922
  let effectiveSpendingKey = spendingKey;
3895
3923
  if (ownOwnerPk !== 0n && plaintext.ownerPk !== ownOwnerPk) {
3896
- const sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
3897
- if (!sharedSecret) return { note: null, reason: "stealth_shared_secret_failed" };
3924
+ const ss = sharedSecret ?? EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
3925
+ if (!ss) return { note: null, reason: "stealth_shared_secret_failed" };
3898
3926
  const ownPkPoint = recoverOwnerPkPoint(ownOwnerPk);
3899
3927
  if (!ownPkPoint) return { note: null, reason: "stealth_invalid_own_owner_pk" };
3900
- const stealthOwnerPk = deriveStealthOwnerPk(sharedSecret, ownOwnerPk, ownPkPoint);
3928
+ const stealthOwnerPk = deriveStealthOwnerPk(ss, ownOwnerPk, ownPkPoint);
3901
3929
  if (stealthOwnerPk !== plaintext.ownerPk) {
3902
3930
  return { note: null, reason: "commitment_mismatch" };
3903
3931
  }
3904
3932
  effectiveOwnerPk = stealthOwnerPk;
3905
- effectiveSpendingKey = deriveStealthSk(sharedSecret, ownOwnerPk, spendingKey);
3933
+ effectiveSpendingKey = deriveStealthSk(ss, ownOwnerPk, spendingKey);
3906
3934
  }
3907
3935
  const recomputed = (0, import_poseidon_lite2.poseidon4)([
3908
3936
  plaintext.value,
@@ -5303,6 +5331,7 @@ var import_polkadot_api4 = require("polkadot-api");
5303
5331
  deriveStealthSk,
5304
5332
  deriveVaultBlindKey,
5305
5333
  deriveVaultKey,
5334
+ deriveViewTag,
5306
5335
  deriveViewingPublicKey,
5307
5336
  deriveViewingSecretKey,
5308
5337
  encryptJson,
package/dist/index.mjs CHANGED
@@ -95,7 +95,7 @@ var SubstrateClient = class _SubstrateClient {
95
95
  * Throws if the node does not respond within `timeoutMs`.
96
96
  */
97
97
  static async connect(wsUrl, timeoutMs = 15e3) {
98
- const provider = getWsProvider(wsUrl);
98
+ const provider = getWsProvider(wsUrl, { heartbeatTimeout: 3e4 });
99
99
  const papi = createClient(provider);
100
100
  let timer;
101
101
  try {
@@ -1131,6 +1131,7 @@ var BABYJUB_SUBORDER = 273603035897990940278080071815715938607681397215856725920
1131
1131
  // src/shielded-pool/protocol/memo.ts
1132
1132
  import { sha256 } from "@noble/hashes/sha2.js";
1133
1133
  var KEY_DOMAIN = new TextEncoder().encode("orbinum-note-encryption-v1");
1134
+ var VIEW_TAG_DOMAIN = new TextEncoder().encode("orbinum-view-tag-v1");
1134
1135
  var MEMO_PLAINTEXT_SIZE = 120;
1135
1136
  function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk, circuitVersion) {
1136
1137
  const buf = new Uint8Array(MEMO_PLAINTEXT_SIZE);
@@ -1151,6 +1152,12 @@ function deriveEncryptionKey(sharedSecret, commitment) {
1151
1152
  h.update(KEY_DOMAIN);
1152
1153
  return h.digest();
1153
1154
  }
1155
+ function deriveViewTag(sharedSecret) {
1156
+ const h = sha256.create();
1157
+ h.update(VIEW_TAG_DOMAIN);
1158
+ h.update(sharedSecret);
1159
+ return h.digest()[0];
1160
+ }
1154
1161
 
1155
1162
  // src/shielded-pool/protocol/EncryptedMemo.ts
1156
1163
  var NONCE_SIZE = 12;
@@ -1227,6 +1234,7 @@ var EncryptedMemo = {
1227
1234
  const sharedPoint = mulPointEscalar(ivkPoint, ephSkScalar);
1228
1235
  sharedSecret = bigintTo32Le(sharedPoint[0]);
1229
1236
  }
1237
+ nonce[0] = deriveViewTag(sharedSecret);
1230
1238
  const encKey = deriveEncryptionKey(sharedSecret, commitment);
1231
1239
  const cipher = chacha20poly1305(encKey, nonce);
1232
1240
  const ciphertext = cipher.encrypt(plaintext);
@@ -1316,24 +1324,35 @@ var EncryptedMemo = {
1316
1324
  const sharedPoint = mulPointEscalar(ephPkPoint, ivskScalar);
1317
1325
  return bigintTo32Le(sharedPoint[0]);
1318
1326
  },
1319
- /** @internal */
1320
- _decrypt(memoBytes, commitment, viewingSecretKey) {
1327
+ /**
1328
+ * Cheap view-tag check: does memo nonce[0] match the tag derived from
1329
+ * `sharedSecret`? One SHA256 + one byte compare — no AEAD work.
1330
+ *
1331
+ * Only meaningful for memos built with view tags (commitments at/after
1332
+ * the wallet's tagActivationLeaf): a legacy memo carries a random byte
1333
+ * there and would false-negative 255/256 of the time.
1334
+ */
1335
+ checkViewTag(memoBytes, sharedSecret) {
1336
+ if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return false;
1337
+ return memoBytes[0] === deriveViewTag(sharedSecret);
1338
+ },
1339
+ /**
1340
+ * Decrypt with an already-computed shared secret (from
1341
+ * extractSharedSecret), skipping the ECDH. Pair with checkViewTag for the
1342
+ * fast scan path: ECDH once → tag check → decrypt only on match.
1343
+ */
1344
+ decryptWithSharedSecret(memoBytes, commitment, sharedSecret) {
1345
+ if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return null;
1321
1346
  const nonce = memoBytes.slice(0, NONCE_SIZE);
1322
1347
  const ciphertextWithMac = memoBytes.slice(NONCE_SIZE, NONCE_SIZE + CIPHERTEXT_SIZE);
1323
- const ephPkPackedBytes = memoBytes.slice(NONCE_SIZE + CIPHERTEXT_SIZE);
1324
- const ephPkPackedBigint = bytesToBigintLE(ephPkPackedBytes);
1325
- let sharedSecret;
1326
- if (ephPkPackedBigint === 0n) {
1327
- sharedSecret = new Uint8Array(32);
1328
- } else {
1329
- const ephPkPoint = unpackPoint(ephPkPackedBigint);
1330
- if (!ephPkPoint) return null;
1331
- const ivskScalar = bytesToBjjScalar(viewingSecretKey);
1332
- const sharedPoint = mulPointEscalar(ephPkPoint, ivskScalar);
1333
- sharedSecret = bigintTo32Le(sharedPoint[0]);
1334
- }
1335
1348
  const encKey = deriveEncryptionKey(sharedSecret, commitment);
1336
1349
  return parsePlaintext(nonce, ciphertextWithMac, encKey);
1350
+ },
1351
+ /** @internal */
1352
+ _decrypt(memoBytes, commitment, viewingSecretKey) {
1353
+ const sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
1354
+ if (!sharedSecret) return null;
1355
+ return EncryptedMemo.decryptWithSharedSecret(memoBytes, commitment, sharedSecret);
1337
1356
  }
1338
1357
  };
1339
1358
 
@@ -3741,10 +3760,10 @@ import { poseidon2 as poseidon22, poseidon4 as poseidon42 } from "poseidon-lite"
3741
3760
  function computeNullifier(commitment, spendingKey) {
3742
3761
  return poseidon22([commitment, spendingKey]);
3743
3762
  }
3744
- function tryDecryptNote(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
3745
- return tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk).note;
3763
+ function tryDecryptNote(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n, opts) {
3764
+ return tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk, opts).note;
3746
3765
  }
3747
- function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
3766
+ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n, opts) {
3748
3767
  if (!commitment.encryptedMemo) return { note: null, reason: "no_memo" };
3749
3768
  let commitmentBytes;
3750
3769
  let memoBytes;
@@ -3760,21 +3779,29 @@ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwn
3760
3779
  reason: `memo_size_mismatch:got_${memoBytes.length}_expected_${ENCRYPTED_MEMO_SIZE}`
3761
3780
  };
3762
3781
  }
3763
- const plaintext = EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingSecretKey);
3782
+ let sharedSecret = null;
3783
+ if (opts?.viewTag) {
3784
+ sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
3785
+ if (!sharedSecret) return { note: null, reason: "stealth_shared_secret_failed" };
3786
+ if (!EncryptedMemo.checkViewTag(memoBytes, sharedSecret)) {
3787
+ return { note: null, reason: "view_tag_mismatch" };
3788
+ }
3789
+ }
3790
+ const plaintext = sharedSecret ? EncryptedMemo.decryptWithSharedSecret(memoBytes, commitmentBytes, sharedSecret) : EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingSecretKey);
3764
3791
  if (!plaintext) return { note: null, reason: "decrypt_failed:wrong_key_or_corrupt_mac" };
3765
3792
  let effectiveOwnerPk = plaintext.ownerPk;
3766
3793
  let effectiveSpendingKey = spendingKey;
3767
3794
  if (ownOwnerPk !== 0n && plaintext.ownerPk !== ownOwnerPk) {
3768
- const sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
3769
- if (!sharedSecret) return { note: null, reason: "stealth_shared_secret_failed" };
3795
+ const ss = sharedSecret ?? EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
3796
+ if (!ss) return { note: null, reason: "stealth_shared_secret_failed" };
3770
3797
  const ownPkPoint = recoverOwnerPkPoint(ownOwnerPk);
3771
3798
  if (!ownPkPoint) return { note: null, reason: "stealth_invalid_own_owner_pk" };
3772
- const stealthOwnerPk = deriveStealthOwnerPk(sharedSecret, ownOwnerPk, ownPkPoint);
3799
+ const stealthOwnerPk = deriveStealthOwnerPk(ss, ownOwnerPk, ownPkPoint);
3773
3800
  if (stealthOwnerPk !== plaintext.ownerPk) {
3774
3801
  return { note: null, reason: "commitment_mismatch" };
3775
3802
  }
3776
3803
  effectiveOwnerPk = stealthOwnerPk;
3777
- effectiveSpendingKey = deriveStealthSk(sharedSecret, ownOwnerPk, spendingKey);
3804
+ effectiveSpendingKey = deriveStealthSk(ss, ownOwnerPk, spendingKey);
3778
3805
  }
3779
3806
  const recomputed = poseidon42([
3780
3807
  plaintext.value,
@@ -5197,6 +5224,7 @@ export {
5197
5224
  deriveStealthSk,
5198
5225
  deriveVaultBlindKey,
5199
5226
  deriveVaultKey,
5227
+ deriveViewTag,
5200
5228
  deriveViewingPublicKey,
5201
5229
  deriveViewingSecretKey,
5202
5230
  encryptJson,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbinum/sdk",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Official TypeScript SDK for Orbinum.",
5
5
  "author": "Orbinum",
6
6
  "license": "MIT",