@orbinum/sdk 0.14.1 → 0.16.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
@@ -664,6 +664,13 @@ type NoteInput = {
664
664
  counterpartyPk?: bigint;
665
665
  /** Circuit version to stamp on the note. Defaults to `CURRENT_CIRCUIT_VERSION`. */
666
666
  circuitVersion?: number;
667
+ /**
668
+ * 32-byte ephemeral secret for the memo ECDH. Self-notes pass a
669
+ * deterministic one (deriveSelfEphSk) so a cold restore recognizes them by
670
+ * ephPk equality with no trial ECDH. Ignored on the stealth path (it
671
+ * generates its own coordinated ephSk). Default: random.
672
+ */
673
+ ephSkOverride?: Uint8Array;
667
674
  };
668
675
  /**
669
676
  * Circuit version notes are created under today. A note carries its version
@@ -1920,11 +1927,17 @@ declare class NoteBuilder {
1920
1927
  /**
1921
1928
  * EncryptedMemo — TypeScript implementation of Orbinum's encrypted note memo.
1922
1929
  *
1923
- * Mirrors primitives/encrypted-memo in the node repository; no WASM required.
1930
+ * Native TypeScript implementation no WASM required. This file IS the
1931
+ * normative memo format: the chain treats memos as opaque 180-byte blobs.
1924
1932
  *
1925
1933
  * Layout (180 bytes, ECDH):
1926
1934
  * nonce(12) || ciphertext+MAC(136) || ephPk_packed(32) = 180
1927
1935
  *
1936
+ * View tag (memos built by SDK ≥ 0.15): nonce[0] = deriveViewTag(sharedSecret)
1937
+ * — see memo.ts. Layout and size are unchanged; legacy memos carry a random
1938
+ * byte there instead, so the filter is only sound at/after the wallet's
1939
+ * tagActivationLeaf.
1940
+ *
1928
1941
  * Plaintext layout (120 bytes):
1929
1942
  * 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
1943
  *
@@ -2007,10 +2020,67 @@ declare const EncryptedMemo: {
2007
2020
  * @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
2008
2021
  */
2009
2022
  extractSharedSecret(memoBytes: Uint8Array, viewingSecretKey: Uint8Array): Uint8Array | null;
2023
+ /**
2024
+ * Cheap view-tag check: does memo nonce[0] match the tag derived from
2025
+ * `sharedSecret`? One SHA256 + one byte compare — no AEAD work.
2026
+ *
2027
+ * Only meaningful for memos built with view tags (commitments at/after
2028
+ * the wallet's tagActivationLeaf): a legacy memo carries a random byte
2029
+ * there and would false-negative 255/256 of the time.
2030
+ */
2031
+ checkViewTag(memoBytes: Uint8Array, sharedSecret: Uint8Array): boolean;
2032
+ /**
2033
+ * Decrypt with an already-computed shared secret (from
2034
+ * extractSharedSecret), skipping the ECDH. Pair with checkViewTag for the
2035
+ * fast scan path: ECDH once → tag check → decrypt only on match.
2036
+ */
2037
+ decryptWithSharedSecret(memoBytes: Uint8Array, commitment: Uint8Array, sharedSecret: Uint8Array): DecryptedMemo | null;
2010
2038
  /** @internal */
2011
2039
  _decrypt(memoBytes: Uint8Array, commitment: Uint8Array, viewingSecretKey: Uint8Array): DecryptedMemo | null;
2012
2040
  };
2013
2041
 
2042
+ /**
2043
+ * Derive the 1-byte view tag (Monero-style fast-scan filter) from the ECDH
2044
+ * shared secret:
2045
+ * view_tag = SHA256("orbinum-view-tag-v1" || sharedSecret)[0]
2046
+ *
2047
+ * The sender embeds it as the memo's first nonce byte (nonce[0]); a scanner
2048
+ * that has computed the shared secret compares one byte and skips the AEAD
2049
+ * decrypt on mismatch — 255/256 of foreign notes. Safe to publish: without
2050
+ * the viewing key the shared secret is unknowable, so the byte reads as
2051
+ * uniform noise to any observer.
2052
+ *
2053
+ * Nonce safety: the encryption key is unique per note (see
2054
+ * deriveEncryptionKey), so each key encrypts exactly one message and the
2055
+ * remaining 11 random nonce bytes are more than enough.
2056
+ */
2057
+ declare function deriveViewTag(sharedSecret: Uint8Array): number;
2058
+
2059
+ /**
2060
+ * Derive the deterministic 32-byte ephemeral secret for self-note `index`.
2061
+ * Feed it to EncryptedMemo.encrypt / NoteBuilder.build as `ephSkOverride`.
2062
+ */
2063
+ declare function deriveSelfEphSk(spendingKey: bigint, index: number): Uint8Array;
2064
+ /** One precomputed self-note window entry. */
2065
+ interface SelfEphWindowEntry {
2066
+ index: number;
2067
+ /** 0x-prefixed LE-packed ephPk — byte-identical to the memo's last 32 bytes. */
2068
+ ephPkHex: string;
2069
+ /** ECDH shared secret vs the wallet's own ivk — feeds decryptWithSharedSecret. */
2070
+ sharedSecret: Uint8Array;
2071
+ }
2072
+ /**
2073
+ * Precompute the self-note discovery window [from, from+count): for each
2074
+ * index, the ephPk the wallet would have published and the shared secret
2075
+ * needed to decrypt the memo. One EC pass up front; scanning then matches
2076
+ * hints by ephPk hex equality with no per-hint EC work.
2077
+ *
2078
+ * @param spendingKey Wallet spending key (the seed of the derivation).
2079
+ * @param ivkPacked The wallet's OWN 32-byte LE packed viewing public key —
2080
+ * self memos are encrypted to it.
2081
+ */
2082
+ declare function selfEphWindow(spendingKey: bigint, ivkPacked: Uint8Array, from: number, count: number): SelfEphWindowEntry[];
2083
+
2014
2084
  /**
2015
2085
  * NoteDecryptor
2016
2086
  *
@@ -2034,6 +2104,24 @@ declare const EncryptedMemo: {
2034
2104
  * deriveSpendingKeyFromSignature.
2035
2105
  */
2036
2106
  declare function computeNullifier(commitment: bigint, spendingKey: bigint): bigint;
2107
+ interface TryDecryptOptions {
2108
+ /**
2109
+ * View-tag fast path: compute the ECDH shared secret once, compare the
2110
+ * 1-byte tag in memo nonce[0], and skip the AEAD decrypt on mismatch
2111
+ * (255/256 of foreign notes; `reason: 'view_tag_mismatch'`).
2112
+ *
2113
+ * Only enable for commitments at/after the wallet's tagActivationLeaf —
2114
+ * legacy memos carry a random byte there and would be silently dropped.
2115
+ */
2116
+ viewTag?: boolean;
2117
+ /**
2118
+ * Precomputed ECDH shared secret (self-note discovery: the caller matched
2119
+ * the hint's ephPk against a selfEphWindow and already holds the secret).
2120
+ * Skips the ECDH and the view-tag gate entirely; the decrypt + commitment
2121
+ * check still validate the note as usual.
2122
+ */
2123
+ sharedSecret?: Uint8Array;
2124
+ }
2037
2125
  /**
2038
2126
  * Attempt to decrypt an on-chain commitment using the recipient's viewing secret key.
2039
2127
  *
@@ -2047,13 +2135,14 @@ declare function computeNullifier(commitment: bigint, spendingKey: bigint): bigi
2047
2135
  * @param spendingKey Spending key bigint (for nullifier computation).
2048
2136
  * @param ownOwnerPk The viewer's global BabyJubJub Ax (ownerPk). Required for stealth detection.
2049
2137
  * Pass 0n to disable stealth detection (legacy/own-note-only scanning).
2138
+ * @param opts See TryDecryptOptions (view-tag fast path).
2050
2139
  */
2051
- declare function tryDecryptNote(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint): ZkNote | null;
2140
+ declare function tryDecryptNote(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint, opts?: TryDecryptOptions): ZkNote | null;
2052
2141
  /**
2053
2142
  * Like tryDecryptNote but also returns a human-readable reason for failure.
2054
2143
  * Useful for debugging scan issues (wrong key, corrupted memo, commitment mismatch).
2055
2144
  */
2056
- declare function tryDecryptNoteVerbose(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint): {
2145
+ declare function tryDecryptNoteVerbose(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint, opts?: TryDecryptOptions): {
2057
2146
  note: ZkNote | null;
2058
2147
  reason?: string;
2059
2148
  };
@@ -4172,4 +4261,4 @@ interface ExtrinsicFailedData {
4172
4261
  dispatch_info: DispatchInfo;
4173
4262
  }
4174
4263
 
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 };
4264
+ 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 SelfEphWindowEntry, 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, deriveSelfEphSk, 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, selfEphWindow, shortHash, substrateSs58ToAccountIdHex, substrateToEvm, toBase64, toHex, toTxResult, truncateMiddle, tryDecryptNote, tryDecryptNoteVerbose, vaultReplacer, vaultReviver };
package/dist/index.d.ts CHANGED
@@ -664,6 +664,13 @@ type NoteInput = {
664
664
  counterpartyPk?: bigint;
665
665
  /** Circuit version to stamp on the note. Defaults to `CURRENT_CIRCUIT_VERSION`. */
666
666
  circuitVersion?: number;
667
+ /**
668
+ * 32-byte ephemeral secret for the memo ECDH. Self-notes pass a
669
+ * deterministic one (deriveSelfEphSk) so a cold restore recognizes them by
670
+ * ephPk equality with no trial ECDH. Ignored on the stealth path (it
671
+ * generates its own coordinated ephSk). Default: random.
672
+ */
673
+ ephSkOverride?: Uint8Array;
667
674
  };
668
675
  /**
669
676
  * Circuit version notes are created under today. A note carries its version
@@ -1920,11 +1927,17 @@ declare class NoteBuilder {
1920
1927
  /**
1921
1928
  * EncryptedMemo — TypeScript implementation of Orbinum's encrypted note memo.
1922
1929
  *
1923
- * Mirrors primitives/encrypted-memo in the node repository; no WASM required.
1930
+ * Native TypeScript implementation no WASM required. This file IS the
1931
+ * normative memo format: the chain treats memos as opaque 180-byte blobs.
1924
1932
  *
1925
1933
  * Layout (180 bytes, ECDH):
1926
1934
  * nonce(12) || ciphertext+MAC(136) || ephPk_packed(32) = 180
1927
1935
  *
1936
+ * View tag (memos built by SDK ≥ 0.15): nonce[0] = deriveViewTag(sharedSecret)
1937
+ * — see memo.ts. Layout and size are unchanged; legacy memos carry a random
1938
+ * byte there instead, so the filter is only sound at/after the wallet's
1939
+ * tagActivationLeaf.
1940
+ *
1928
1941
  * Plaintext layout (120 bytes):
1929
1942
  * 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
1943
  *
@@ -2007,10 +2020,67 @@ declare const EncryptedMemo: {
2007
2020
  * @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
2008
2021
  */
2009
2022
  extractSharedSecret(memoBytes: Uint8Array, viewingSecretKey: Uint8Array): Uint8Array | null;
2023
+ /**
2024
+ * Cheap view-tag check: does memo nonce[0] match the tag derived from
2025
+ * `sharedSecret`? One SHA256 + one byte compare — no AEAD work.
2026
+ *
2027
+ * Only meaningful for memos built with view tags (commitments at/after
2028
+ * the wallet's tagActivationLeaf): a legacy memo carries a random byte
2029
+ * there and would false-negative 255/256 of the time.
2030
+ */
2031
+ checkViewTag(memoBytes: Uint8Array, sharedSecret: Uint8Array): boolean;
2032
+ /**
2033
+ * Decrypt with an already-computed shared secret (from
2034
+ * extractSharedSecret), skipping the ECDH. Pair with checkViewTag for the
2035
+ * fast scan path: ECDH once → tag check → decrypt only on match.
2036
+ */
2037
+ decryptWithSharedSecret(memoBytes: Uint8Array, commitment: Uint8Array, sharedSecret: Uint8Array): DecryptedMemo | null;
2010
2038
  /** @internal */
2011
2039
  _decrypt(memoBytes: Uint8Array, commitment: Uint8Array, viewingSecretKey: Uint8Array): DecryptedMemo | null;
2012
2040
  };
2013
2041
 
2042
+ /**
2043
+ * Derive the 1-byte view tag (Monero-style fast-scan filter) from the ECDH
2044
+ * shared secret:
2045
+ * view_tag = SHA256("orbinum-view-tag-v1" || sharedSecret)[0]
2046
+ *
2047
+ * The sender embeds it as the memo's first nonce byte (nonce[0]); a scanner
2048
+ * that has computed the shared secret compares one byte and skips the AEAD
2049
+ * decrypt on mismatch — 255/256 of foreign notes. Safe to publish: without
2050
+ * the viewing key the shared secret is unknowable, so the byte reads as
2051
+ * uniform noise to any observer.
2052
+ *
2053
+ * Nonce safety: the encryption key is unique per note (see
2054
+ * deriveEncryptionKey), so each key encrypts exactly one message and the
2055
+ * remaining 11 random nonce bytes are more than enough.
2056
+ */
2057
+ declare function deriveViewTag(sharedSecret: Uint8Array): number;
2058
+
2059
+ /**
2060
+ * Derive the deterministic 32-byte ephemeral secret for self-note `index`.
2061
+ * Feed it to EncryptedMemo.encrypt / NoteBuilder.build as `ephSkOverride`.
2062
+ */
2063
+ declare function deriveSelfEphSk(spendingKey: bigint, index: number): Uint8Array;
2064
+ /** One precomputed self-note window entry. */
2065
+ interface SelfEphWindowEntry {
2066
+ index: number;
2067
+ /** 0x-prefixed LE-packed ephPk — byte-identical to the memo's last 32 bytes. */
2068
+ ephPkHex: string;
2069
+ /** ECDH shared secret vs the wallet's own ivk — feeds decryptWithSharedSecret. */
2070
+ sharedSecret: Uint8Array;
2071
+ }
2072
+ /**
2073
+ * Precompute the self-note discovery window [from, from+count): for each
2074
+ * index, the ephPk the wallet would have published and the shared secret
2075
+ * needed to decrypt the memo. One EC pass up front; scanning then matches
2076
+ * hints by ephPk hex equality with no per-hint EC work.
2077
+ *
2078
+ * @param spendingKey Wallet spending key (the seed of the derivation).
2079
+ * @param ivkPacked The wallet's OWN 32-byte LE packed viewing public key —
2080
+ * self memos are encrypted to it.
2081
+ */
2082
+ declare function selfEphWindow(spendingKey: bigint, ivkPacked: Uint8Array, from: number, count: number): SelfEphWindowEntry[];
2083
+
2014
2084
  /**
2015
2085
  * NoteDecryptor
2016
2086
  *
@@ -2034,6 +2104,24 @@ declare const EncryptedMemo: {
2034
2104
  * deriveSpendingKeyFromSignature.
2035
2105
  */
2036
2106
  declare function computeNullifier(commitment: bigint, spendingKey: bigint): bigint;
2107
+ interface TryDecryptOptions {
2108
+ /**
2109
+ * View-tag fast path: compute the ECDH shared secret once, compare the
2110
+ * 1-byte tag in memo nonce[0], and skip the AEAD decrypt on mismatch
2111
+ * (255/256 of foreign notes; `reason: 'view_tag_mismatch'`).
2112
+ *
2113
+ * Only enable for commitments at/after the wallet's tagActivationLeaf —
2114
+ * legacy memos carry a random byte there and would be silently dropped.
2115
+ */
2116
+ viewTag?: boolean;
2117
+ /**
2118
+ * Precomputed ECDH shared secret (self-note discovery: the caller matched
2119
+ * the hint's ephPk against a selfEphWindow and already holds the secret).
2120
+ * Skips the ECDH and the view-tag gate entirely; the decrypt + commitment
2121
+ * check still validate the note as usual.
2122
+ */
2123
+ sharedSecret?: Uint8Array;
2124
+ }
2037
2125
  /**
2038
2126
  * Attempt to decrypt an on-chain commitment using the recipient's viewing secret key.
2039
2127
  *
@@ -2047,13 +2135,14 @@ declare function computeNullifier(commitment: bigint, spendingKey: bigint): bigi
2047
2135
  * @param spendingKey Spending key bigint (for nullifier computation).
2048
2136
  * @param ownOwnerPk The viewer's global BabyJubJub Ax (ownerPk). Required for stealth detection.
2049
2137
  * Pass 0n to disable stealth detection (legacy/own-note-only scanning).
2138
+ * @param opts See TryDecryptOptions (view-tag fast path).
2050
2139
  */
2051
- declare function tryDecryptNote(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint): ZkNote | null;
2140
+ declare function tryDecryptNote(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint, opts?: TryDecryptOptions): ZkNote | null;
2052
2141
  /**
2053
2142
  * Like tryDecryptNote but also returns a human-readable reason for failure.
2054
2143
  * Useful for debugging scan issues (wrong key, corrupted memo, commitment mismatch).
2055
2144
  */
2056
- declare function tryDecryptNoteVerbose(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint): {
2145
+ declare function tryDecryptNoteVerbose(commitment: ScanCommitment, viewingSecretKey: Uint8Array, spendingKey: bigint, ownOwnerPk?: bigint, opts?: TryDecryptOptions): {
2057
2146
  note: ZkNote | null;
2058
2147
  reason?: string;
2059
2148
  };
@@ -4172,4 +4261,4 @@ interface ExtrinsicFailedData {
4172
4261
  dispatch_info: DispatchInfo;
4173
4262
  }
4174
4263
 
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 };
4264
+ 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 SelfEphWindowEntry, 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, deriveSelfEphSk, 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, selfEphWindow, shortHash, substrateSs58ToAccountIdHex, substrateToEvm, toBase64, toHex, toTxResult, truncateMiddle, tryDecryptNote, tryDecryptNoteVerbose, vaultReplacer, vaultReviver };
package/dist/index.js CHANGED
@@ -73,12 +73,14 @@ __export(index_exports, {
73
73
  decryptNoteRecord: () => decryptNoteRecord,
74
74
  deriveMasterKeyBytes: () => deriveMasterKeyBytes,
75
75
  deriveOwnerPk: () => deriveOwnerPk,
76
+ deriveSelfEphSk: () => deriveSelfEphSk,
76
77
  deriveSpendingKeyFromSignature: () => deriveSpendingKeyFromSignature,
77
78
  deriveSpendingKeyMessage: () => deriveSpendingKeyMessage,
78
79
  deriveStealthOwnerPk: () => deriveStealthOwnerPk,
79
80
  deriveStealthSk: () => deriveStealthSk,
80
81
  deriveVaultBlindKey: () => deriveVaultBlindKey,
81
82
  deriveVaultKey: () => deriveVaultKey,
83
+ deriveViewTag: () => deriveViewTag,
82
84
  deriveViewingPublicKey: () => deriveViewingPublicKey,
83
85
  deriveViewingSecretKey: () => deriveViewingSecretKey,
84
86
  encryptJson: () => encryptJson,
@@ -116,6 +118,7 @@ __export(index_exports, {
116
118
  randomBlinding: () => randomBlinding,
117
119
  recoverOwnerPkPoint: () => recoverOwnerPkPoint,
118
120
  selectNotes: () => selectNotes,
121
+ selfEphWindow: () => selfEphWindow,
119
122
  shortHash: () => shortHash,
120
123
  substrateSs58ToAccountIdHex: () => substrateSs58ToAccountIdHex,
121
124
  substrateToEvm: () => substrateToEvm,
@@ -1207,6 +1210,32 @@ var import_chacha = require("@noble/ciphers/chacha.js");
1207
1210
  var import_utils = require("@noble/ciphers/utils.js");
1208
1211
  var import_baby_jubjub = require("@zk-kit/baby-jubjub");
1209
1212
 
1213
+ // src/utils/bjj-fast.ts
1214
+ var import_edwards = require("@noble/curves/abstract/edwards.js");
1215
+ var P = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
1216
+ var N = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
1217
+ var BjjPoint = (0, import_edwards.edwards)({
1218
+ p: P,
1219
+ n: N,
1220
+ h: 8n,
1221
+ a: 168700n,
1222
+ d: 168696n,
1223
+ Gx: 5299619240641551281634865583518297030282874472190772894086521144482721001553n,
1224
+ Gy: 16950150798460657717958625567821834550301663161624707787222815936182638968203n
1225
+ });
1226
+ function fastMulBase(scalar) {
1227
+ const s = scalar % N;
1228
+ if (s === 0n) return [0n, 1n];
1229
+ const { x, y } = BjjPoint.BASE.multiply(s).toAffine();
1230
+ return [x, y];
1231
+ }
1232
+ function fastMulPoint(point, scalar) {
1233
+ const s = scalar % N;
1234
+ if (s === 0n) return [0n, 1n];
1235
+ const { x, y } = BjjPoint.fromAffine({ x: point[0], y: point[1] }).multiplyUnsafe(s).toAffine();
1236
+ return [x, y];
1237
+ }
1238
+
1210
1239
  // src/utils/bytes.ts
1211
1240
  function bigintTo32Le(n) {
1212
1241
  const buf = new Uint8Array(32);
@@ -1262,6 +1291,7 @@ var BABYJUB_SUBORDER = 273603035897990940278080071815715938607681397215856725920
1262
1291
  // src/shielded-pool/protocol/memo.ts
1263
1292
  var import_sha2 = require("@noble/hashes/sha2.js");
1264
1293
  var KEY_DOMAIN = new TextEncoder().encode("orbinum-note-encryption-v1");
1294
+ var VIEW_TAG_DOMAIN = new TextEncoder().encode("orbinum-view-tag-v1");
1265
1295
  var MEMO_PLAINTEXT_SIZE = 120;
1266
1296
  function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk, circuitVersion) {
1267
1297
  const buf = new Uint8Array(MEMO_PLAINTEXT_SIZE);
@@ -1282,6 +1312,12 @@ function deriveEncryptionKey(sharedSecret, commitment) {
1282
1312
  h.update(KEY_DOMAIN);
1283
1313
  return h.digest();
1284
1314
  }
1315
+ function deriveViewTag(sharedSecret) {
1316
+ const h = import_sha2.sha256.create();
1317
+ h.update(VIEW_TAG_DOMAIN);
1318
+ h.update(sharedSecret);
1319
+ return h.digest()[0];
1320
+ }
1285
1321
 
1286
1322
  // src/shielded-pool/protocol/EncryptedMemo.ts
1287
1323
  var NONCE_SIZE = 12;
@@ -1349,15 +1385,16 @@ var EncryptedMemo = {
1349
1385
  if (ephSkBytes.length !== 32)
1350
1386
  throw new Error("EncryptedMemo.encrypt: ephSkOverride must be 32 bytes");
1351
1387
  const ephSkScalar = bytesToBjjScalar(ephSkBytes);
1352
- const ephPkPoint = (0, import_baby_jubjub.mulPointEscalar)(import_baby_jubjub.Base8, ephSkScalar);
1388
+ const ephPkPoint = fastMulBase(ephSkScalar);
1353
1389
  ephPkPackedBytes = bigintTo32Le((0, import_baby_jubjub.packPoint)(ephPkPoint));
1354
1390
  const ivkPackedBigint = bytesToBigintLE(recipientIvkPacked);
1355
1391
  const ivkPoint = (0, import_baby_jubjub.unpackPoint)(ivkPackedBigint);
1356
1392
  if (!ivkPoint)
1357
1393
  throw new Error("EncryptedMemo.encrypt: invalid recipient viewing public key");
1358
- const sharedPoint = (0, import_baby_jubjub.mulPointEscalar)(ivkPoint, ephSkScalar);
1394
+ const sharedPoint = fastMulPoint(ivkPoint, ephSkScalar);
1359
1395
  sharedSecret = bigintTo32Le(sharedPoint[0]);
1360
1396
  }
1397
+ nonce[0] = deriveViewTag(sharedSecret);
1361
1398
  const encKey = deriveEncryptionKey(sharedSecret, commitment);
1362
1399
  const cipher = (0, import_chacha.chacha20poly1305)(encKey, nonce);
1363
1400
  const ciphertext = cipher.encrypt(plaintext);
@@ -1444,27 +1481,38 @@ var EncryptedMemo = {
1444
1481
  const ephPkPoint = (0, import_baby_jubjub.unpackPoint)(ephPkPackedBigint);
1445
1482
  if (!ephPkPoint) return null;
1446
1483
  const ivskScalar = bytesToBjjScalar(viewingSecretKey);
1447
- const sharedPoint = (0, import_baby_jubjub.mulPointEscalar)(ephPkPoint, ivskScalar);
1484
+ const sharedPoint = fastMulPoint(ephPkPoint, ivskScalar);
1448
1485
  return bigintTo32Le(sharedPoint[0]);
1449
1486
  },
1450
- /** @internal */
1451
- _decrypt(memoBytes, commitment, viewingSecretKey) {
1487
+ /**
1488
+ * Cheap view-tag check: does memo nonce[0] match the tag derived from
1489
+ * `sharedSecret`? One SHA256 + one byte compare — no AEAD work.
1490
+ *
1491
+ * Only meaningful for memos built with view tags (commitments at/after
1492
+ * the wallet's tagActivationLeaf): a legacy memo carries a random byte
1493
+ * there and would false-negative 255/256 of the time.
1494
+ */
1495
+ checkViewTag(memoBytes, sharedSecret) {
1496
+ if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return false;
1497
+ return memoBytes[0] === deriveViewTag(sharedSecret);
1498
+ },
1499
+ /**
1500
+ * Decrypt with an already-computed shared secret (from
1501
+ * extractSharedSecret), skipping the ECDH. Pair with checkViewTag for the
1502
+ * fast scan path: ECDH once → tag check → decrypt only on match.
1503
+ */
1504
+ decryptWithSharedSecret(memoBytes, commitment, sharedSecret) {
1505
+ if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return null;
1452
1506
  const nonce = memoBytes.slice(0, NONCE_SIZE);
1453
1507
  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
1508
  const encKey = deriveEncryptionKey(sharedSecret, commitment);
1467
1509
  return parsePlaintext(nonce, ciphertextWithMac, encKey);
1510
+ },
1511
+ /** @internal */
1512
+ _decrypt(memoBytes, commitment, viewingSecretKey) {
1513
+ const sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
1514
+ if (!sharedSecret) return null;
1515
+ return EncryptedMemo.decryptWithSharedSecret(memoBytes, commitment, sharedSecret);
1468
1516
  }
1469
1517
  };
1470
1518
 
@@ -3814,7 +3862,8 @@ var NoteBuilder = class {
3814
3862
  commitmentBytes,
3815
3863
  input.viewingPublicKey,
3816
3864
  bigintTo32Le(counterpartyPk),
3817
- circuitVersion
3865
+ circuitVersion,
3866
+ input.ephSkOverride
3818
3867
  )
3819
3868
  ) : Array.from(EncryptedMemo.dummy());
3820
3869
  if (memo.length !== ENCRYPTED_MEMO_SIZE)
@@ -3864,15 +3913,45 @@ var NoteBuilder = class {
3864
3913
  }
3865
3914
  };
3866
3915
 
3916
+ // src/shielded-pool/protocol/selfEph.ts
3917
+ var import_sha23 = require("@noble/hashes/sha2.js");
3918
+ var import_baby_jubjub5 = require("@zk-kit/baby-jubjub");
3919
+ var SELF_EPH_DOMAIN = new TextEncoder().encode("orbinum-self-eph-v1");
3920
+ function deriveSelfEphSk(spendingKey, index) {
3921
+ const h = import_sha23.sha256.create();
3922
+ h.update(SELF_EPH_DOMAIN);
3923
+ h.update(bigintTo32Le(spendingKey));
3924
+ const idx = new Uint8Array(4);
3925
+ new DataView(idx.buffer).setUint32(0, index >>> 0, true);
3926
+ h.update(idx);
3927
+ return h.digest();
3928
+ }
3929
+ function selfEphWindow(spendingKey, ivkPacked, from, count) {
3930
+ const ivkPoint = (0, import_baby_jubjub5.unpackPoint)(bytesToBigintLE(ivkPacked));
3931
+ if (!ivkPoint) throw new Error("selfEphWindow: invalid viewing public key");
3932
+ const entries = [];
3933
+ for (let i = from; i < from + count; i++) {
3934
+ const scalar = bytesToBjjScalar(deriveSelfEphSk(spendingKey, i));
3935
+ const ephPk = fastMulBase(scalar);
3936
+ const sharedPoint = fastMulPoint(ivkPoint, scalar);
3937
+ entries.push({
3938
+ index: i,
3939
+ ephPkHex: toHex(bigintTo32Le((0, import_baby_jubjub5.packPoint)(ephPk))),
3940
+ sharedSecret: bigintTo32Le(sharedPoint[0])
3941
+ });
3942
+ }
3943
+ return entries;
3944
+ }
3945
+
3867
3946
  // src/shielded-pool/protocol/NoteDecryptor.ts
3868
3947
  var import_poseidon_lite2 = require("poseidon-lite");
3869
3948
  function computeNullifier(commitment, spendingKey) {
3870
3949
  return (0, import_poseidon_lite2.poseidon2)([commitment, spendingKey]);
3871
3950
  }
3872
- function tryDecryptNote(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
3873
- return tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk).note;
3951
+ function tryDecryptNote(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n, opts) {
3952
+ return tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk, opts).note;
3874
3953
  }
3875
- function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
3954
+ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n, opts) {
3876
3955
  if (!commitment.encryptedMemo) return { note: null, reason: "no_memo" };
3877
3956
  let commitmentBytes;
3878
3957
  let memoBytes;
@@ -3888,21 +3967,29 @@ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwn
3888
3967
  reason: `memo_size_mismatch:got_${memoBytes.length}_expected_${ENCRYPTED_MEMO_SIZE}`
3889
3968
  };
3890
3969
  }
3891
- const plaintext = EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingSecretKey);
3970
+ let sharedSecret = opts?.sharedSecret ?? null;
3971
+ if (!sharedSecret && opts?.viewTag) {
3972
+ sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
3973
+ if (!sharedSecret) return { note: null, reason: "stealth_shared_secret_failed" };
3974
+ if (!EncryptedMemo.checkViewTag(memoBytes, sharedSecret)) {
3975
+ return { note: null, reason: "view_tag_mismatch" };
3976
+ }
3977
+ }
3978
+ const plaintext = sharedSecret ? EncryptedMemo.decryptWithSharedSecret(memoBytes, commitmentBytes, sharedSecret) : EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingSecretKey);
3892
3979
  if (!plaintext) return { note: null, reason: "decrypt_failed:wrong_key_or_corrupt_mac" };
3893
3980
  let effectiveOwnerPk = plaintext.ownerPk;
3894
3981
  let effectiveSpendingKey = spendingKey;
3895
3982
  if (ownOwnerPk !== 0n && plaintext.ownerPk !== ownOwnerPk) {
3896
- const sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
3897
- if (!sharedSecret) return { note: null, reason: "stealth_shared_secret_failed" };
3983
+ const ss = sharedSecret ?? EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
3984
+ if (!ss) return { note: null, reason: "stealth_shared_secret_failed" };
3898
3985
  const ownPkPoint = recoverOwnerPkPoint(ownOwnerPk);
3899
3986
  if (!ownPkPoint) return { note: null, reason: "stealth_invalid_own_owner_pk" };
3900
- const stealthOwnerPk = deriveStealthOwnerPk(sharedSecret, ownOwnerPk, ownPkPoint);
3987
+ const stealthOwnerPk = deriveStealthOwnerPk(ss, ownOwnerPk, ownPkPoint);
3901
3988
  if (stealthOwnerPk !== plaintext.ownerPk) {
3902
3989
  return { note: null, reason: "commitment_mismatch" };
3903
3990
  }
3904
3991
  effectiveOwnerPk = stealthOwnerPk;
3905
- effectiveSpendingKey = deriveStealthSk(sharedSecret, ownOwnerPk, spendingKey);
3992
+ effectiveSpendingKey = deriveStealthSk(ss, ownOwnerPk, spendingKey);
3906
3993
  }
3907
3994
  const recomputed = (0, import_poseidon_lite2.poseidon4)([
3908
3995
  plaintext.value,
@@ -4030,8 +4117,8 @@ function randomBlinding() {
4030
4117
 
4031
4118
  // src/privacy-keys/PrivacyKeys.ts
4032
4119
  var import_hkdf2 = require("@noble/hashes/hkdf.js");
4033
- var import_sha23 = require("@noble/hashes/sha2.js");
4034
- var import_baby_jubjub5 = require("@zk-kit/baby-jubjub");
4120
+ var import_sha24 = require("@noble/hashes/sha2.js");
4121
+ var import_baby_jubjub6 = require("@zk-kit/baby-jubjub");
4035
4122
  var IVK_DOMAIN = new TextEncoder().encode("orbinum-ivk-v1");
4036
4123
  function deriveSpendingKeyMessage(chainId, address) {
4037
4124
  return `orbinum-spending-key-v1
@@ -4041,7 +4128,7 @@ ${address.toLowerCase()}`;
4041
4128
  async function deriveMasterKeyBytes(signatureHex, chainId, address) {
4042
4129
  const sigBytes = fromHex(signatureHex);
4043
4130
  const info = new TextEncoder().encode(`orbinum-sk-v1:${chainId}:${address.toLowerCase()}`);
4044
- return (0, import_hkdf2.hkdf)(import_sha23.sha256, sigBytes, new Uint8Array(0), info, 32);
4131
+ return (0, import_hkdf2.hkdf)(import_sha24.sha256, sigBytes, new Uint8Array(0), info, 32);
4045
4132
  }
4046
4133
  async function deriveSpendingKeyFromSignature(signatureHex, chainId, address) {
4047
4134
  const masterBytes = await deriveMasterKeyBytes(signatureHex, chainId, address);
@@ -4050,17 +4137,17 @@ async function deriveSpendingKeyFromSignature(signatureHex, chainId, address) {
4050
4137
  }
4051
4138
  function deriveViewingSecretKey(spendingKey) {
4052
4139
  const ikm = bigintTo32Le(spendingKey);
4053
- return (0, import_hkdf2.hkdf)(import_sha23.sha256, ikm, void 0, IVK_DOMAIN, 32);
4140
+ return (0, import_hkdf2.hkdf)(import_sha24.sha256, ikm, void 0, IVK_DOMAIN, 32);
4054
4141
  }
4055
4142
  function deriveViewingPublicKey(ivsk) {
4056
4143
  const ivskScalar = BigInt(toHex(ivsk)) % BABYJUB_SUBORDER || 1n;
4057
- const ivkPoint = (0, import_baby_jubjub5.mulPointEscalar)(import_baby_jubjub5.Base8, ivskScalar);
4058
- const packed = (0, import_baby_jubjub5.packPoint)(ivkPoint);
4144
+ const ivkPoint = (0, import_baby_jubjub6.mulPointEscalar)(import_baby_jubjub6.Base8, ivskScalar);
4145
+ const packed = (0, import_baby_jubjub6.packPoint)(ivkPoint);
4059
4146
  return bigintTo32Le(packed);
4060
4147
  }
4061
4148
  function deriveOwnerPk(spendingKey) {
4062
4149
  try {
4063
- const pubPoint = (0, import_baby_jubjub5.mulPointEscalar)(import_baby_jubjub5.Base8, spendingKey);
4150
+ const pubPoint = (0, import_baby_jubjub6.mulPointEscalar)(import_baby_jubjub6.Base8, spendingKey);
4064
4151
  return pubPoint[0];
4065
4152
  } catch {
4066
4153
  return 0n;
@@ -4369,7 +4456,7 @@ async function noteBlindTag(blindKey, hex) {
4369
4456
  // src/proof-generator/unshield.ts
4370
4457
  var import_proof_generator2 = require("@orbinum/proof-generator");
4371
4458
  var import_utils3 = require("@noble/ciphers/utils.js");
4372
- var import_baby_jubjub6 = require("@zk-kit/baby-jubjub");
4459
+ var import_baby_jubjub7 = require("@zk-kit/baby-jubjub");
4373
4460
  var import_poseidon_lite4 = require("poseidon-lite");
4374
4461
 
4375
4462
  // src/proof-generator/merkle.ts
@@ -4392,7 +4479,7 @@ async function generateUnshieldProof(inputs, options = {}) {
4392
4479
  if (changeValue < 0n) {
4393
4480
  throw new Error("changeValue must be >= 0.");
4394
4481
  }
4395
- const changeOwnerPubkey = inputs.changeOwnerPubkey ?? (0, import_baby_jubjub6.mulPointEscalar)(import_baby_jubjub6.Base8, inputs.spendingKey)[0];
4482
+ const changeOwnerPubkey = inputs.changeOwnerPubkey ?? (0, import_baby_jubjub7.mulPointEscalar)(import_baby_jubjub7.Base8, inputs.spendingKey)[0];
4396
4483
  const changeBlinding = inputs.changeBlinding ?? (changeValue > 0n ? bytesToBigintLE((0, import_utils3.randomBytes)(32)) : 0n);
4397
4484
  const changeCommitment = changeValue > 0n ? (0, import_poseidon_lite4.poseidon4)([changeValue, inputs.assetId, changeOwnerPubkey, changeBlinding]) : 0n;
4398
4485
  const circuitInputs = {
@@ -5297,12 +5384,14 @@ var import_polkadot_api4 = require("polkadot-api");
5297
5384
  decryptNoteRecord,
5298
5385
  deriveMasterKeyBytes,
5299
5386
  deriveOwnerPk,
5387
+ deriveSelfEphSk,
5300
5388
  deriveSpendingKeyFromSignature,
5301
5389
  deriveSpendingKeyMessage,
5302
5390
  deriveStealthOwnerPk,
5303
5391
  deriveStealthSk,
5304
5392
  deriveVaultBlindKey,
5305
5393
  deriveVaultKey,
5394
+ deriveViewTag,
5306
5395
  deriveViewingPublicKey,
5307
5396
  deriveViewingSecretKey,
5308
5397
  encryptJson,
@@ -5340,6 +5429,7 @@ var import_polkadot_api4 = require("polkadot-api");
5340
5429
  randomBlinding,
5341
5430
  recoverOwnerPkPoint,
5342
5431
  selectNotes,
5432
+ selfEphWindow,
5343
5433
  shortHash,
5344
5434
  substrateSs58ToAccountIdHex,
5345
5435
  substrateToEvm,
package/dist/index.mjs CHANGED
@@ -1074,7 +1074,33 @@ function resolveTx(unsafe, pallet, call) {
1074
1074
  // src/shielded-pool/protocol/EncryptedMemo.ts
1075
1075
  import { chacha20poly1305 } from "@noble/ciphers/chacha.js";
1076
1076
  import { randomBytes } from "@noble/ciphers/utils.js";
1077
- import { mulPointEscalar, Base8, packPoint, unpackPoint } from "@zk-kit/baby-jubjub";
1077
+ import { packPoint, unpackPoint } from "@zk-kit/baby-jubjub";
1078
+
1079
+ // src/utils/bjj-fast.ts
1080
+ import { edwards } from "@noble/curves/abstract/edwards.js";
1081
+ var P = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
1082
+ var N = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
1083
+ var BjjPoint = edwards({
1084
+ p: P,
1085
+ n: N,
1086
+ h: 8n,
1087
+ a: 168700n,
1088
+ d: 168696n,
1089
+ Gx: 5299619240641551281634865583518297030282874472190772894086521144482721001553n,
1090
+ Gy: 16950150798460657717958625567821834550301663161624707787222815936182638968203n
1091
+ });
1092
+ function fastMulBase(scalar) {
1093
+ const s = scalar % N;
1094
+ if (s === 0n) return [0n, 1n];
1095
+ const { x, y } = BjjPoint.BASE.multiply(s).toAffine();
1096
+ return [x, y];
1097
+ }
1098
+ function fastMulPoint(point, scalar) {
1099
+ const s = scalar % N;
1100
+ if (s === 0n) return [0n, 1n];
1101
+ const { x, y } = BjjPoint.fromAffine({ x: point[0], y: point[1] }).multiplyUnsafe(s).toAffine();
1102
+ return [x, y];
1103
+ }
1078
1104
 
1079
1105
  // src/utils/bytes.ts
1080
1106
  function bigintTo32Le(n) {
@@ -1131,6 +1157,7 @@ var BABYJUB_SUBORDER = 273603035897990940278080071815715938607681397215856725920
1131
1157
  // src/shielded-pool/protocol/memo.ts
1132
1158
  import { sha256 } from "@noble/hashes/sha2.js";
1133
1159
  var KEY_DOMAIN = new TextEncoder().encode("orbinum-note-encryption-v1");
1160
+ var VIEW_TAG_DOMAIN = new TextEncoder().encode("orbinum-view-tag-v1");
1134
1161
  var MEMO_PLAINTEXT_SIZE = 120;
1135
1162
  function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk, circuitVersion) {
1136
1163
  const buf = new Uint8Array(MEMO_PLAINTEXT_SIZE);
@@ -1151,6 +1178,12 @@ function deriveEncryptionKey(sharedSecret, commitment) {
1151
1178
  h.update(KEY_DOMAIN);
1152
1179
  return h.digest();
1153
1180
  }
1181
+ function deriveViewTag(sharedSecret) {
1182
+ const h = sha256.create();
1183
+ h.update(VIEW_TAG_DOMAIN);
1184
+ h.update(sharedSecret);
1185
+ return h.digest()[0];
1186
+ }
1154
1187
 
1155
1188
  // src/shielded-pool/protocol/EncryptedMemo.ts
1156
1189
  var NONCE_SIZE = 12;
@@ -1218,15 +1251,16 @@ var EncryptedMemo = {
1218
1251
  if (ephSkBytes.length !== 32)
1219
1252
  throw new Error("EncryptedMemo.encrypt: ephSkOverride must be 32 bytes");
1220
1253
  const ephSkScalar = bytesToBjjScalar(ephSkBytes);
1221
- const ephPkPoint = mulPointEscalar(Base8, ephSkScalar);
1254
+ const ephPkPoint = fastMulBase(ephSkScalar);
1222
1255
  ephPkPackedBytes = bigintTo32Le(packPoint(ephPkPoint));
1223
1256
  const ivkPackedBigint = bytesToBigintLE(recipientIvkPacked);
1224
1257
  const ivkPoint = unpackPoint(ivkPackedBigint);
1225
1258
  if (!ivkPoint)
1226
1259
  throw new Error("EncryptedMemo.encrypt: invalid recipient viewing public key");
1227
- const sharedPoint = mulPointEscalar(ivkPoint, ephSkScalar);
1260
+ const sharedPoint = fastMulPoint(ivkPoint, ephSkScalar);
1228
1261
  sharedSecret = bigintTo32Le(sharedPoint[0]);
1229
1262
  }
1263
+ nonce[0] = deriveViewTag(sharedSecret);
1230
1264
  const encKey = deriveEncryptionKey(sharedSecret, commitment);
1231
1265
  const cipher = chacha20poly1305(encKey, nonce);
1232
1266
  const ciphertext = cipher.encrypt(plaintext);
@@ -1313,27 +1347,38 @@ var EncryptedMemo = {
1313
1347
  const ephPkPoint = unpackPoint(ephPkPackedBigint);
1314
1348
  if (!ephPkPoint) return null;
1315
1349
  const ivskScalar = bytesToBjjScalar(viewingSecretKey);
1316
- const sharedPoint = mulPointEscalar(ephPkPoint, ivskScalar);
1350
+ const sharedPoint = fastMulPoint(ephPkPoint, ivskScalar);
1317
1351
  return bigintTo32Le(sharedPoint[0]);
1318
1352
  },
1319
- /** @internal */
1320
- _decrypt(memoBytes, commitment, viewingSecretKey) {
1353
+ /**
1354
+ * Cheap view-tag check: does memo nonce[0] match the tag derived from
1355
+ * `sharedSecret`? One SHA256 + one byte compare — no AEAD work.
1356
+ *
1357
+ * Only meaningful for memos built with view tags (commitments at/after
1358
+ * the wallet's tagActivationLeaf): a legacy memo carries a random byte
1359
+ * there and would false-negative 255/256 of the time.
1360
+ */
1361
+ checkViewTag(memoBytes, sharedSecret) {
1362
+ if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return false;
1363
+ return memoBytes[0] === deriveViewTag(sharedSecret);
1364
+ },
1365
+ /**
1366
+ * Decrypt with an already-computed shared secret (from
1367
+ * extractSharedSecret), skipping the ECDH. Pair with checkViewTag for the
1368
+ * fast scan path: ECDH once → tag check → decrypt only on match.
1369
+ */
1370
+ decryptWithSharedSecret(memoBytes, commitment, sharedSecret) {
1371
+ if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return null;
1321
1372
  const nonce = memoBytes.slice(0, NONCE_SIZE);
1322
1373
  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
1374
  const encKey = deriveEncryptionKey(sharedSecret, commitment);
1336
1375
  return parsePlaintext(nonce, ciphertextWithMac, encKey);
1376
+ },
1377
+ /** @internal */
1378
+ _decrypt(memoBytes, commitment, viewingSecretKey) {
1379
+ const sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
1380
+ if (!sharedSecret) return null;
1381
+ return EncryptedMemo.decryptWithSharedSecret(memoBytes, commitment, sharedSecret);
1337
1382
  }
1338
1383
  };
1339
1384
 
@@ -3504,7 +3549,7 @@ var CURRENT_CIRCUIT_VERSION = 1;
3504
3549
  // src/utils/stealth.ts
3505
3550
  import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
3506
3551
  import { hkdf } from "@noble/hashes/hkdf.js";
3507
- import { mulPointEscalar as mulPointEscalar2, Base8 as Base82, addPoint } from "@zk-kit/baby-jubjub";
3552
+ import { mulPointEscalar, Base8, addPoint } from "@zk-kit/baby-jubjub";
3508
3553
  var STEALTH_INFO = new TextEncoder().encode("orbinum-stealth-v1");
3509
3554
  function deriveStealthScalar(sharedSecret, ownerPkBigint) {
3510
3555
  const salt = bigintTo32Le(ownerPkBigint);
@@ -3513,7 +3558,7 @@ function deriveStealthScalar(sharedSecret, ownerPkBigint) {
3513
3558
  }
3514
3559
  function deriveStealthOwnerPk(sharedSecret, ownerPkBigint, ownerPkPoint) {
3515
3560
  const stealthScalar = deriveStealthScalar(sharedSecret, ownerPkBigint);
3516
- const stealthPt = addPoint(mulPointEscalar2(Base82, stealthScalar), ownerPkPoint);
3561
+ const stealthPt = addPoint(mulPointEscalar(Base8, stealthScalar), ownerPkPoint);
3517
3562
  return stealthPt[0];
3518
3563
  }
3519
3564
  function deriveStealthSk(sharedSecret, ownerPkBigint, spendingKey) {
@@ -3522,7 +3567,7 @@ function deriveStealthSk(sharedSecret, ownerPkBigint, spendingKey) {
3522
3567
  }
3523
3568
 
3524
3569
  // src/utils/bjj.ts
3525
- import { mulPointEscalar as mulPointEscalar3 } from "@zk-kit/baby-jubjub";
3570
+ import { mulPointEscalar as mulPointEscalar2 } from "@zk-kit/baby-jubjub";
3526
3571
  var BJJ_A = 168700n;
3527
3572
  var BJJ_D = 168696n;
3528
3573
  function _modpow(base, exp, mod) {
@@ -3577,7 +3622,7 @@ function recoverOwnerPkPoint(ax) {
3577
3622
  if (y === null) return null;
3578
3623
  const yAlt = BN254_R - y;
3579
3624
  try {
3580
- const check = mulPointEscalar3([ax, y], BABYJUB_SUBORDER);
3625
+ const check = mulPointEscalar2([ax, y], BABYJUB_SUBORDER);
3581
3626
  return check[0] === 0n && check[1] === 1n ? [ax, y] : [ax, yAlt];
3582
3627
  } catch {
3583
3628
  return [ax, yAlt];
@@ -3585,7 +3630,7 @@ function recoverOwnerPkPoint(ax) {
3585
3630
  }
3586
3631
 
3587
3632
  // src/shielded-pool/protocol/NoteBuilder.ts
3588
- import { mulPointEscalar as mulPointEscalar4, unpackPoint as unpackPoint2 } from "@zk-kit/baby-jubjub";
3633
+ import { mulPointEscalar as mulPointEscalar3, unpackPoint as unpackPoint2 } from "@zk-kit/baby-jubjub";
3589
3634
  import { randomBytes as randomBytes2 } from "@noble/ciphers/utils.js";
3590
3635
  import { poseidon2, poseidon4 } from "poseidon-lite";
3591
3636
  var NoteBuilder = class {
@@ -3621,7 +3666,7 @@ var NoteBuilder = class {
3621
3666
  if (!ivkPoint)
3622
3667
  throw new Error("NoteBuilder.build: invalid recipient viewing public key");
3623
3668
  const ephSkScalar = BigInt(toHex(ephSk)) % BABYJUB_SUBORDER || 1n;
3624
- const sharedPoint = mulPointEscalar4(ivkPoint, ephSkScalar);
3669
+ const sharedPoint = mulPointEscalar3(ivkPoint, ephSkScalar);
3625
3670
  const sharedSecret = bigintTo32Le(sharedPoint[0]);
3626
3671
  const recipientPkPoint = recoverOwnerPkPoint(recipientOwnerPk);
3627
3672
  if (!recipientPkPoint)
@@ -3686,7 +3731,8 @@ var NoteBuilder = class {
3686
3731
  commitmentBytes,
3687
3732
  input.viewingPublicKey,
3688
3733
  bigintTo32Le(counterpartyPk),
3689
- circuitVersion
3734
+ circuitVersion,
3735
+ input.ephSkOverride
3690
3736
  )
3691
3737
  ) : Array.from(EncryptedMemo.dummy());
3692
3738
  if (memo.length !== ENCRYPTED_MEMO_SIZE)
@@ -3736,15 +3782,45 @@ var NoteBuilder = class {
3736
3782
  }
3737
3783
  };
3738
3784
 
3785
+ // src/shielded-pool/protocol/selfEph.ts
3786
+ import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
3787
+ import { packPoint as packPoint2, unpackPoint as unpackPoint3 } from "@zk-kit/baby-jubjub";
3788
+ var SELF_EPH_DOMAIN = new TextEncoder().encode("orbinum-self-eph-v1");
3789
+ function deriveSelfEphSk(spendingKey, index) {
3790
+ const h = sha2563.create();
3791
+ h.update(SELF_EPH_DOMAIN);
3792
+ h.update(bigintTo32Le(spendingKey));
3793
+ const idx = new Uint8Array(4);
3794
+ new DataView(idx.buffer).setUint32(0, index >>> 0, true);
3795
+ h.update(idx);
3796
+ return h.digest();
3797
+ }
3798
+ function selfEphWindow(spendingKey, ivkPacked, from, count) {
3799
+ const ivkPoint = unpackPoint3(bytesToBigintLE(ivkPacked));
3800
+ if (!ivkPoint) throw new Error("selfEphWindow: invalid viewing public key");
3801
+ const entries = [];
3802
+ for (let i = from; i < from + count; i++) {
3803
+ const scalar = bytesToBjjScalar(deriveSelfEphSk(spendingKey, i));
3804
+ const ephPk = fastMulBase(scalar);
3805
+ const sharedPoint = fastMulPoint(ivkPoint, scalar);
3806
+ entries.push({
3807
+ index: i,
3808
+ ephPkHex: toHex(bigintTo32Le(packPoint2(ephPk))),
3809
+ sharedSecret: bigintTo32Le(sharedPoint[0])
3810
+ });
3811
+ }
3812
+ return entries;
3813
+ }
3814
+
3739
3815
  // src/shielded-pool/protocol/NoteDecryptor.ts
3740
3816
  import { poseidon2 as poseidon22, poseidon4 as poseidon42 } from "poseidon-lite";
3741
3817
  function computeNullifier(commitment, spendingKey) {
3742
3818
  return poseidon22([commitment, spendingKey]);
3743
3819
  }
3744
- function tryDecryptNote(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
3745
- return tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk).note;
3820
+ function tryDecryptNote(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n, opts) {
3821
+ return tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk, opts).note;
3746
3822
  }
3747
- function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
3823
+ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n, opts) {
3748
3824
  if (!commitment.encryptedMemo) return { note: null, reason: "no_memo" };
3749
3825
  let commitmentBytes;
3750
3826
  let memoBytes;
@@ -3760,21 +3836,29 @@ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwn
3760
3836
  reason: `memo_size_mismatch:got_${memoBytes.length}_expected_${ENCRYPTED_MEMO_SIZE}`
3761
3837
  };
3762
3838
  }
3763
- const plaintext = EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingSecretKey);
3839
+ let sharedSecret = opts?.sharedSecret ?? null;
3840
+ if (!sharedSecret && opts?.viewTag) {
3841
+ sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
3842
+ if (!sharedSecret) return { note: null, reason: "stealth_shared_secret_failed" };
3843
+ if (!EncryptedMemo.checkViewTag(memoBytes, sharedSecret)) {
3844
+ return { note: null, reason: "view_tag_mismatch" };
3845
+ }
3846
+ }
3847
+ const plaintext = sharedSecret ? EncryptedMemo.decryptWithSharedSecret(memoBytes, commitmentBytes, sharedSecret) : EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingSecretKey);
3764
3848
  if (!plaintext) return { note: null, reason: "decrypt_failed:wrong_key_or_corrupt_mac" };
3765
3849
  let effectiveOwnerPk = plaintext.ownerPk;
3766
3850
  let effectiveSpendingKey = spendingKey;
3767
3851
  if (ownOwnerPk !== 0n && plaintext.ownerPk !== ownOwnerPk) {
3768
- const sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
3769
- if (!sharedSecret) return { note: null, reason: "stealth_shared_secret_failed" };
3852
+ const ss = sharedSecret ?? EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
3853
+ if (!ss) return { note: null, reason: "stealth_shared_secret_failed" };
3770
3854
  const ownPkPoint = recoverOwnerPkPoint(ownOwnerPk);
3771
3855
  if (!ownPkPoint) return { note: null, reason: "stealth_invalid_own_owner_pk" };
3772
- const stealthOwnerPk = deriveStealthOwnerPk(sharedSecret, ownOwnerPk, ownPkPoint);
3856
+ const stealthOwnerPk = deriveStealthOwnerPk(ss, ownOwnerPk, ownPkPoint);
3773
3857
  if (stealthOwnerPk !== plaintext.ownerPk) {
3774
3858
  return { note: null, reason: "commitment_mismatch" };
3775
3859
  }
3776
3860
  effectiveOwnerPk = stealthOwnerPk;
3777
- effectiveSpendingKey = deriveStealthSk(sharedSecret, ownOwnerPk, spendingKey);
3861
+ effectiveSpendingKey = deriveStealthSk(ss, ownOwnerPk, spendingKey);
3778
3862
  }
3779
3863
  const recomputed = poseidon42([
3780
3864
  plaintext.value,
@@ -3902,8 +3986,8 @@ function randomBlinding() {
3902
3986
 
3903
3987
  // src/privacy-keys/PrivacyKeys.ts
3904
3988
  import { hkdf as hkdf2 } from "@noble/hashes/hkdf.js";
3905
- import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
3906
- import { mulPointEscalar as mulPointEscalar5, Base8 as Base83, packPoint as packPoint2 } from "@zk-kit/baby-jubjub";
3989
+ import { sha256 as sha2564 } from "@noble/hashes/sha2.js";
3990
+ import { mulPointEscalar as mulPointEscalar4, Base8 as Base82, packPoint as packPoint3 } from "@zk-kit/baby-jubjub";
3907
3991
  var IVK_DOMAIN = new TextEncoder().encode("orbinum-ivk-v1");
3908
3992
  function deriveSpendingKeyMessage(chainId, address) {
3909
3993
  return `orbinum-spending-key-v1
@@ -3913,7 +3997,7 @@ ${address.toLowerCase()}`;
3913
3997
  async function deriveMasterKeyBytes(signatureHex, chainId, address) {
3914
3998
  const sigBytes = fromHex(signatureHex);
3915
3999
  const info = new TextEncoder().encode(`orbinum-sk-v1:${chainId}:${address.toLowerCase()}`);
3916
- return hkdf2(sha2563, sigBytes, new Uint8Array(0), info, 32);
4000
+ return hkdf2(sha2564, sigBytes, new Uint8Array(0), info, 32);
3917
4001
  }
3918
4002
  async function deriveSpendingKeyFromSignature(signatureHex, chainId, address) {
3919
4003
  const masterBytes = await deriveMasterKeyBytes(signatureHex, chainId, address);
@@ -3922,17 +4006,17 @@ async function deriveSpendingKeyFromSignature(signatureHex, chainId, address) {
3922
4006
  }
3923
4007
  function deriveViewingSecretKey(spendingKey) {
3924
4008
  const ikm = bigintTo32Le(spendingKey);
3925
- return hkdf2(sha2563, ikm, void 0, IVK_DOMAIN, 32);
4009
+ return hkdf2(sha2564, ikm, void 0, IVK_DOMAIN, 32);
3926
4010
  }
3927
4011
  function deriveViewingPublicKey(ivsk) {
3928
4012
  const ivskScalar = BigInt(toHex(ivsk)) % BABYJUB_SUBORDER || 1n;
3929
- const ivkPoint = mulPointEscalar5(Base83, ivskScalar);
3930
- const packed = packPoint2(ivkPoint);
4013
+ const ivkPoint = mulPointEscalar4(Base82, ivskScalar);
4014
+ const packed = packPoint3(ivkPoint);
3931
4015
  return bigintTo32Le(packed);
3932
4016
  }
3933
4017
  function deriveOwnerPk(spendingKey) {
3934
4018
  try {
3935
- const pubPoint = mulPointEscalar5(Base83, spendingKey);
4019
+ const pubPoint = mulPointEscalar4(Base82, spendingKey);
3936
4020
  return pubPoint[0];
3937
4021
  } catch {
3938
4022
  return 0n;
@@ -4245,7 +4329,7 @@ import {
4245
4329
  WebArtifactProvider as WebArtifactProvider2
4246
4330
  } from "@orbinum/proof-generator";
4247
4331
  import { randomBytes as randomBytes3 } from "@noble/ciphers/utils.js";
4248
- import { mulPointEscalar as mulPointEscalar6, Base8 as Base84 } from "@zk-kit/baby-jubjub";
4332
+ import { mulPointEscalar as mulPointEscalar5, Base8 as Base83 } from "@zk-kit/baby-jubjub";
4249
4333
  import { poseidon4 as poseidon44 } from "poseidon-lite";
4250
4334
 
4251
4335
  // src/proof-generator/merkle.ts
@@ -4268,7 +4352,7 @@ async function generateUnshieldProof(inputs, options = {}) {
4268
4352
  if (changeValue < 0n) {
4269
4353
  throw new Error("changeValue must be >= 0.");
4270
4354
  }
4271
- const changeOwnerPubkey = inputs.changeOwnerPubkey ?? mulPointEscalar6(Base84, inputs.spendingKey)[0];
4355
+ const changeOwnerPubkey = inputs.changeOwnerPubkey ?? mulPointEscalar5(Base83, inputs.spendingKey)[0];
4272
4356
  const changeBlinding = inputs.changeBlinding ?? (changeValue > 0n ? bytesToBigintLE(randomBytes3(32)) : 0n);
4273
4357
  const changeCommitment = changeValue > 0n ? poseidon44([changeValue, inputs.assetId, changeOwnerPubkey, changeBlinding]) : 0n;
4274
4358
  const circuitInputs = {
@@ -5191,12 +5275,14 @@ export {
5191
5275
  decryptNoteRecord,
5192
5276
  deriveMasterKeyBytes,
5193
5277
  deriveOwnerPk,
5278
+ deriveSelfEphSk,
5194
5279
  deriveSpendingKeyFromSignature,
5195
5280
  deriveSpendingKeyMessage,
5196
5281
  deriveStealthOwnerPk,
5197
5282
  deriveStealthSk,
5198
5283
  deriveVaultBlindKey,
5199
5284
  deriveVaultKey,
5285
+ deriveViewTag,
5200
5286
  deriveViewingPublicKey,
5201
5287
  deriveViewingSecretKey,
5202
5288
  encryptJson,
@@ -5234,6 +5320,7 @@ export {
5234
5320
  randomBlinding,
5235
5321
  recoverOwnerPkPoint,
5236
5322
  selectNotes,
5323
+ selfEphWindow,
5237
5324
  shortHash,
5238
5325
  substrateSs58ToAccountIdHex,
5239
5326
  substrateToEvm,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbinum/sdk",
3
- "version": "0.14.1",
3
+ "version": "0.16.0",
4
4
  "description": "Official TypeScript SDK for Orbinum.",
5
5
  "author": "Orbinum",
6
6
  "license": "MIT",
@@ -46,6 +46,7 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@noble/ciphers": "2.2.0",
49
+ "@noble/curves": "^2.2.0",
49
50
  "@noble/hashes": "2.2.0",
50
51
  "@orbinum/proof-generator": "4.0.0",
51
52
  "@polkadot-api/metadata-builders": "0.14.2",