@sip-protocol/sdk 0.7.3 → 0.8.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/README.md +267 -0
- package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
- package/dist/browser.d.mts +10 -4
- package/dist/browser.d.ts +10 -4
- package/dist/browser.js +47556 -19603
- package/dist/browser.mjs +628 -48
- package/dist/chunk-4GRJ5MAW.mjs +152 -0
- package/dist/chunk-5D7A3L3W.mjs +717 -0
- package/dist/chunk-64AYA5F5.mjs +7834 -0
- package/dist/chunk-GMDGB22A.mjs +379 -0
- package/dist/chunk-I534WKN7.mjs +328 -0
- package/dist/chunk-IBZVA5Y7.mjs +1003 -0
- package/dist/chunk-PRRZAWJE.mjs +223 -0
- package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
- package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
- package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
- package/dist/constants-LHAAUC2T.mjs +51 -0
- package/dist/dist-2OGQ7FED.mjs +3957 -0
- package/dist/dist-IFHPYLDX.mjs +254 -0
- package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
- package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
- package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
- package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +48396 -19623
- package/dist/index.mjs +537 -19
- package/dist/interface-Bf7w1PLW.d.mts +679 -0
- package/dist/interface-Bf7w1PLW.d.ts +679 -0
- package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
- package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
- package/dist/proofs/halo2.d.mts +151 -0
- package/dist/proofs/halo2.d.ts +151 -0
- package/dist/proofs/halo2.js +350 -0
- package/dist/proofs/halo2.mjs +11 -0
- package/dist/proofs/kimchi.d.mts +160 -0
- package/dist/proofs/kimchi.d.ts +160 -0
- package/dist/proofs/kimchi.js +431 -0
- package/dist/proofs/kimchi.mjs +13 -0
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/proofs/noir.js +74 -18
- package/dist/proofs/noir.mjs +84 -24
- package/dist/solana-U3MEGU7W.mjs +280 -0
- package/dist/validity_proof-3POXLPNY.mjs +21 -0
- package/package.json +44 -11
- package/src/adapters/index.ts +41 -0
- package/src/adapters/jupiter.ts +571 -0
- package/src/adapters/near-intents.ts +135 -0
- package/src/advisor/advisor.ts +653 -0
- package/src/advisor/index.ts +54 -0
- package/src/advisor/tools.ts +303 -0
- package/src/advisor/types.ts +164 -0
- package/src/chains/ethereum/announcement.ts +536 -0
- package/src/chains/ethereum/bnb-optimizations.ts +474 -0
- package/src/chains/ethereum/commitment.ts +522 -0
- package/src/chains/ethereum/constants.ts +462 -0
- package/src/chains/ethereum/deployment.ts +596 -0
- package/src/chains/ethereum/gas-estimation.ts +538 -0
- package/src/chains/ethereum/index.ts +268 -0
- package/src/chains/ethereum/optimizations.ts +614 -0
- package/src/chains/ethereum/privacy-adapter.ts +855 -0
- package/src/chains/ethereum/registry.ts +584 -0
- package/src/chains/ethereum/rpc.ts +905 -0
- package/src/chains/ethereum/stealth.ts +491 -0
- package/src/chains/ethereum/token.ts +790 -0
- package/src/chains/ethereum/transfer.ts +637 -0
- package/src/chains/ethereum/types.ts +456 -0
- package/src/chains/ethereum/viewing-key.ts +455 -0
- package/src/chains/near/commitment.ts +608 -0
- package/src/chains/near/constants.ts +284 -0
- package/src/chains/near/function-call.ts +871 -0
- package/src/chains/near/history.ts +654 -0
- package/src/chains/near/implicit-account.ts +840 -0
- package/src/chains/near/index.ts +393 -0
- package/src/chains/near/native-transfer.ts +658 -0
- package/src/chains/near/nep141.ts +775 -0
- package/src/chains/near/privacy-adapter.ts +889 -0
- package/src/chains/near/resolver.ts +971 -0
- package/src/chains/near/rpc.ts +1016 -0
- package/src/chains/near/stealth.ts +419 -0
- package/src/chains/near/types.ts +317 -0
- package/src/chains/near/viewing-key.ts +876 -0
- package/src/chains/solana/anchor-transfer.ts +386 -0
- package/src/chains/solana/commitment.ts +577 -0
- package/src/chains/solana/constants.ts +126 -12
- package/src/chains/solana/ephemeral-keys.ts +543 -0
- package/src/chains/solana/index.ts +252 -1
- package/src/chains/solana/key-derivation.ts +418 -0
- package/src/chains/solana/kit-compat.ts +334 -0
- package/src/chains/solana/optimizations.ts +560 -0
- package/src/chains/solana/privacy-adapter.ts +605 -0
- package/src/chains/solana/providers/generic.ts +47 -6
- package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
- package/src/chains/solana/providers/helius-enhanced.ts +623 -0
- package/src/chains/solana/providers/helius.ts +186 -33
- package/src/chains/solana/providers/index.ts +31 -0
- package/src/chains/solana/providers/interface.ts +61 -18
- package/src/chains/solana/providers/quicknode.ts +409 -0
- package/src/chains/solana/providers/triton.ts +426 -0
- package/src/chains/solana/providers/webhook.ts +338 -67
- package/src/chains/solana/rpc-client.ts +1150 -0
- package/src/chains/solana/scan.ts +83 -66
- package/src/chains/solana/sol-transfer.ts +732 -0
- package/src/chains/solana/spl-transfer.ts +886 -0
- package/src/chains/solana/stealth-scanner.ts +703 -0
- package/src/chains/solana/sunspot-verifier.ts +453 -0
- package/src/chains/solana/transaction-builder.ts +755 -0
- package/src/chains/solana/transfer.ts +74 -5
- package/src/chains/solana/types.ts +57 -6
- package/src/chains/solana/utils.ts +110 -0
- package/src/chains/solana/viewing-key.ts +807 -0
- package/src/compliance/fireblocks.ts +921 -0
- package/src/compliance/index.ts +23 -0
- package/src/compliance/range-sas.ts +398 -33
- package/src/config/endpoints.ts +100 -0
- package/src/crypto.ts +11 -8
- package/src/errors.ts +82 -0
- package/src/evm/erc4337-relayer.ts +830 -0
- package/src/evm/index.ts +47 -0
- package/src/fees/calculator.ts +396 -0
- package/src/fees/index.ts +87 -0
- package/src/fees/near-contract.ts +429 -0
- package/src/fees/types.ts +268 -0
- package/src/index.ts +686 -1
- package/src/intent.ts +6 -3
- package/src/logger.ts +324 -0
- package/src/network/index.ts +80 -0
- package/src/network/proxy.ts +691 -0
- package/src/optimizations/index.ts +541 -0
- package/src/oracle/types.ts +1 -0
- package/src/privacy-backends/arcium-types.ts +727 -0
- package/src/privacy-backends/arcium.ts +719 -0
- package/src/privacy-backends/combined-privacy.ts +866 -0
- package/src/privacy-backends/cspl-token.ts +595 -0
- package/src/privacy-backends/cspl-types.ts +512 -0
- package/src/privacy-backends/cspl.ts +907 -0
- package/src/privacy-backends/health.ts +488 -0
- package/src/privacy-backends/inco-types.ts +323 -0
- package/src/privacy-backends/inco.ts +616 -0
- package/src/privacy-backends/index.ts +254 -4
- package/src/privacy-backends/interface.ts +649 -6
- package/src/privacy-backends/lru-cache.ts +343 -0
- package/src/privacy-backends/magicblock.ts +458 -0
- package/src/privacy-backends/mock.ts +258 -0
- package/src/privacy-backends/privacycash.ts +13 -17
- package/src/privacy-backends/private-swap.ts +570 -0
- package/src/privacy-backends/rate-limiter.ts +683 -0
- package/src/privacy-backends/registry.ts +414 -2
- package/src/privacy-backends/router.ts +283 -3
- package/src/privacy-backends/shadowwire.ts +449 -0
- package/src/privacy-backends/sip-native.ts +3 -0
- package/src/privacy-logger.ts +191 -0
- package/src/production-safety.ts +373 -0
- package/src/proofs/aggregator.ts +1029 -0
- package/src/proofs/browser-composer.ts +1150 -0
- package/src/proofs/browser.ts +113 -25
- package/src/proofs/cache/index.ts +127 -0
- package/src/proofs/cache/interface.ts +545 -0
- package/src/proofs/cache/key-generator.ts +188 -0
- package/src/proofs/cache/lru-cache.ts +481 -0
- package/src/proofs/cache/multi-tier-cache.ts +575 -0
- package/src/proofs/cache/persistent-cache.ts +788 -0
- package/src/proofs/compliance-proof.ts +872 -0
- package/src/proofs/composer/base.ts +923 -0
- package/src/proofs/composer/index.ts +25 -0
- package/src/proofs/composer/interface.ts +518 -0
- package/src/proofs/composer/types.ts +383 -0
- package/src/proofs/converters/halo2.ts +452 -0
- package/src/proofs/converters/index.ts +208 -0
- package/src/proofs/converters/interface.ts +363 -0
- package/src/proofs/converters/kimchi.ts +462 -0
- package/src/proofs/converters/noir.ts +451 -0
- package/src/proofs/fallback.ts +888 -0
- package/src/proofs/halo2.ts +42 -0
- package/src/proofs/index.ts +471 -0
- package/src/proofs/interface.ts +13 -0
- package/src/proofs/kimchi.ts +42 -0
- package/src/proofs/lazy.ts +1004 -0
- package/src/proofs/mock.ts +25 -1
- package/src/proofs/noir.ts +110 -29
- package/src/proofs/orchestrator.ts +960 -0
- package/src/proofs/parallel/concurrency.ts +297 -0
- package/src/proofs/parallel/dependency-graph.ts +602 -0
- package/src/proofs/parallel/executor.ts +420 -0
- package/src/proofs/parallel/index.ts +131 -0
- package/src/proofs/parallel/interface.ts +685 -0
- package/src/proofs/parallel/worker-pool.ts +644 -0
- package/src/proofs/providers/halo2.ts +560 -0
- package/src/proofs/providers/index.ts +34 -0
- package/src/proofs/providers/kimchi.ts +641 -0
- package/src/proofs/validator.ts +881 -0
- package/src/proofs/verifier.ts +867 -0
- package/src/quantum/index.ts +112 -0
- package/src/quantum/winternitz-vault.ts +639 -0
- package/src/quantum/wots.ts +611 -0
- package/src/settlement/backends/direct-chain.ts +1 -0
- package/src/settlement/index.ts +9 -0
- package/src/settlement/router.ts +732 -46
- package/src/solana/index.ts +72 -0
- package/src/solana/jito-relayer.ts +687 -0
- package/src/solana/noir-verifier-types.ts +430 -0
- package/src/solana/noir-verifier.ts +816 -0
- package/src/stealth/address-derivation.ts +193 -0
- package/src/stealth/ed25519.ts +431 -0
- package/src/stealth/index.ts +233 -0
- package/src/stealth/meta-address.ts +221 -0
- package/src/stealth/secp256k1.ts +368 -0
- package/src/stealth/utils.ts +194 -0
- package/src/stealth.ts +50 -1504
- package/src/sync/index.ts +106 -0
- package/src/sync/manager.ts +504 -0
- package/src/sync/mock-provider.ts +318 -0
- package/src/sync/oblivious.ts +625 -0
- package/src/tokens/index.ts +15 -0
- package/src/tokens/registry.ts +301 -0
- package/src/utils/deprecation.ts +94 -0
- package/src/utils/index.ts +9 -0
- package/src/wallet/ethereum/index.ts +68 -0
- package/src/wallet/ethereum/metamask-privacy.ts +420 -0
- package/src/wallet/ethereum/multi-wallet.ts +646 -0
- package/src/wallet/ethereum/privacy-adapter.ts +700 -0
- package/src/wallet/ethereum/types.ts +3 -1
- package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
- package/src/wallet/hardware/index.ts +10 -0
- package/src/wallet/hardware/ledger-privacy.ts +414 -0
- package/src/wallet/index.ts +71 -0
- package/src/wallet/near/adapter.ts +626 -0
- package/src/wallet/near/index.ts +86 -0
- package/src/wallet/near/meteor-wallet.ts +1153 -0
- package/src/wallet/near/my-near-wallet.ts +790 -0
- package/src/wallet/near/wallet-selector.ts +702 -0
- package/src/wallet/solana/adapter.ts +6 -4
- package/src/wallet/solana/index.ts +13 -0
- package/src/wallet/solana/privacy-adapter.ts +567 -0
- package/src/wallet/sui/types.ts +6 -4
- package/src/zcash/rpc-client.ts +13 -6
- package/dist/chunk-2XIVXWHA.mjs +0 -1930
- package/dist/chunk-3INS3PR5.mjs +0 -884
- package/dist/chunk-3OVABDRH.mjs +0 -17096
- package/dist/chunk-7RFRWDCW.mjs +0 -1504
- package/dist/chunk-DLDWZFYC.mjs +0 -1495
- package/dist/chunk-E6SZWREQ.mjs +0 -57
- package/dist/chunk-F6F73W35.mjs +0 -16166
- package/dist/chunk-G33LB27A.mjs +0 -16166
- package/dist/chunk-HGU6HZRC.mjs +0 -231
- package/dist/chunk-L2K34JCU.mjs +0 -1496
- package/dist/chunk-OFDBEIEK.mjs +0 -16166
- package/dist/chunk-SF7YSLF5.mjs +0 -1515
- package/dist/chunk-SN4ZDTVW.mjs +0 -16166
- package/dist/chunk-WWUSGOXE.mjs +0 -17129
- package/dist/constants-VOI7BSLK.mjs +0 -27
- package/dist/index-B71aXVzk.d.ts +0 -13264
- package/dist/index-BYZbDjal.d.ts +0 -11390
- package/dist/index-CHB3KuOB.d.mts +0 -11859
- package/dist/index-CzWPI6Le.d.ts +0 -11859
- package/dist/index-pOIIuwfV.d.mts +0 -13264
- package/dist/index-xbWjohNq.d.mts +0 -11390
- package/dist/solana-4O4K45VU.mjs +0 -46
- package/dist/solana-5EMCTPTS.mjs +0 -46
- package/dist/solana-NDABAZ6P.mjs +0 -56
- package/dist/solana-Q4NAVBTS.mjs +0 -46
- package/dist/solana-ZYO63LY5.mjs +0 -46
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oblivious Sync Service Interface
|
|
3
|
+
*
|
|
4
|
+
* Implements oblivious synchronization where sync services learn NOTHING
|
|
5
|
+
* about user transactions. Inspired by Project Tachyon.
|
|
6
|
+
*
|
|
7
|
+
* @see https://seanbowe.com/blog/tachyon-scaling-zcash-oblivious-synchronization/
|
|
8
|
+
* @see https://github.com/sip-protocol/sip-protocol/issues/433
|
|
9
|
+
*
|
|
10
|
+
* ## How It Works
|
|
11
|
+
*
|
|
12
|
+
* Traditional sync services leak information:
|
|
13
|
+
* - Which nullifiers you're checking (reveals spend timing)
|
|
14
|
+
* - Block access patterns (correlates with activity)
|
|
15
|
+
*
|
|
16
|
+
* Oblivious sync prevents this by:
|
|
17
|
+
* 1. Using sync randomness in nullifier derivation
|
|
18
|
+
* 2. Encrypting queries so service learns nothing
|
|
19
|
+
* 3. Supporting time-windowed viewing key disclosure
|
|
20
|
+
*
|
|
21
|
+
* @module sync/oblivious
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { HexString, Hash } from '@sip-protocol/types'
|
|
25
|
+
import { sha256 } from '@noble/hashes/sha2'
|
|
26
|
+
import { hmac } from '@noble/hashes/hmac'
|
|
27
|
+
import { bytesToHex, hexToBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils'
|
|
28
|
+
import { secureWipe } from '../secure-memory'
|
|
29
|
+
|
|
30
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Chain identifier for multi-chain sync
|
|
34
|
+
*/
|
|
35
|
+
export type ChainId = string
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Block range for synchronization
|
|
39
|
+
*/
|
|
40
|
+
export interface BlockRange {
|
|
41
|
+
/** Starting block (inclusive) */
|
|
42
|
+
startBlock: bigint
|
|
43
|
+
/** Ending block (inclusive) */
|
|
44
|
+
endBlock: bigint
|
|
45
|
+
/** Chain ID */
|
|
46
|
+
chainId: ChainId
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Encrypted note that sync service returns without being able to decrypt
|
|
51
|
+
*/
|
|
52
|
+
export interface EncryptedNote {
|
|
53
|
+
/** Note commitment (public) */
|
|
54
|
+
commitment: HexString
|
|
55
|
+
/** Encrypted note data (only owner can decrypt) */
|
|
56
|
+
encryptedData: HexString
|
|
57
|
+
/** Block number where note was created */
|
|
58
|
+
blockNumber: bigint
|
|
59
|
+
/** Transaction hash */
|
|
60
|
+
txHash: HexString
|
|
61
|
+
/** Chain ID */
|
|
62
|
+
chainId: ChainId
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Merkle proof for note inclusion
|
|
67
|
+
*/
|
|
68
|
+
export interface MerkleProof {
|
|
69
|
+
/** Leaf being proven */
|
|
70
|
+
leaf: HexString
|
|
71
|
+
/** Sibling hashes along path */
|
|
72
|
+
siblings: HexString[]
|
|
73
|
+
/** Index of leaf in tree */
|
|
74
|
+
index: bigint
|
|
75
|
+
/** Root of the tree */
|
|
76
|
+
root: HexString
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Sync randomness used in oblivious nullifier derivation
|
|
81
|
+
*
|
|
82
|
+
* This is the key innovation from Tachyon: by including sync randomness
|
|
83
|
+
* in nullifier computation, the sync service cannot correlate nullifiers
|
|
84
|
+
* to specific notes.
|
|
85
|
+
*/
|
|
86
|
+
export interface SyncRandomness {
|
|
87
|
+
/** Random bytes (32 bytes) */
|
|
88
|
+
value: Uint8Array
|
|
89
|
+
/** Epoch/period this randomness is valid for */
|
|
90
|
+
epoch: number
|
|
91
|
+
/** Expiration timestamp */
|
|
92
|
+
expiresAt: number
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Oblivious nullifier - cannot be correlated by sync service
|
|
97
|
+
*/
|
|
98
|
+
export interface ObliviousNullifier {
|
|
99
|
+
/** The nullifier hash */
|
|
100
|
+
nullifier: HexString
|
|
101
|
+
/** Epoch it was derived in */
|
|
102
|
+
epoch: number
|
|
103
|
+
/** Chain ID */
|
|
104
|
+
chainId: ChainId
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Query for oblivious sync - encrypted so service learns nothing
|
|
109
|
+
*/
|
|
110
|
+
export interface ObliviousSyncQuery {
|
|
111
|
+
/** Encrypted query (only service can process, learns nothing) */
|
|
112
|
+
encryptedQuery: HexString
|
|
113
|
+
/** Public nonce for query */
|
|
114
|
+
nonce: HexString
|
|
115
|
+
/** Block range being queried */
|
|
116
|
+
blockRange: BlockRange
|
|
117
|
+
/** Query timestamp */
|
|
118
|
+
timestamp: number
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Response from oblivious sync service
|
|
123
|
+
*/
|
|
124
|
+
export interface ObliviousSyncResponse {
|
|
125
|
+
/** Encrypted notes found (only querier can decrypt) */
|
|
126
|
+
encryptedNotes: EncryptedNote[]
|
|
127
|
+
/** Merkle proofs for note inclusion */
|
|
128
|
+
merkleProofs: MerkleProof[]
|
|
129
|
+
/** Current sync height */
|
|
130
|
+
syncHeight: bigint
|
|
131
|
+
/** Response timestamp */
|
|
132
|
+
timestamp: number
|
|
133
|
+
/** Query hash for verification */
|
|
134
|
+
queryHash: HexString
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Sync service health information
|
|
139
|
+
*/
|
|
140
|
+
export interface SyncServiceHealth {
|
|
141
|
+
/** Is service available */
|
|
142
|
+
available: boolean
|
|
143
|
+
/** Current block height */
|
|
144
|
+
currentHeight: bigint
|
|
145
|
+
/** Chain ID */
|
|
146
|
+
chainId: ChainId
|
|
147
|
+
/** Latency in milliseconds */
|
|
148
|
+
latencyMs: number
|
|
149
|
+
/** Last successful sync timestamp */
|
|
150
|
+
lastSuccessfulSync?: number
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Configuration for oblivious sync
|
|
155
|
+
*/
|
|
156
|
+
export interface ObliviousSyncConfig {
|
|
157
|
+
/** Maximum blocks per sync batch */
|
|
158
|
+
maxBatchSize: number
|
|
159
|
+
/** Timeout for sync requests (ms) */
|
|
160
|
+
timeoutMs: number
|
|
161
|
+
/** Number of retries on failure */
|
|
162
|
+
retries: number
|
|
163
|
+
/** Epoch duration for sync randomness (seconds) */
|
|
164
|
+
epochDurationSeconds: number
|
|
165
|
+
/** Enable parallel sync across chains */
|
|
166
|
+
parallelSync: boolean
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Default configuration
|
|
171
|
+
*/
|
|
172
|
+
export const DEFAULT_SYNC_CONFIG: ObliviousSyncConfig = {
|
|
173
|
+
maxBatchSize: 1000,
|
|
174
|
+
timeoutMs: 30_000,
|
|
175
|
+
retries: 3,
|
|
176
|
+
epochDurationSeconds: 3600, // 1 hour epochs
|
|
177
|
+
parallelSync: true,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ─── Sync Randomness Management ───────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Generate new sync randomness for an epoch
|
|
184
|
+
*
|
|
185
|
+
* @param epoch - Epoch number
|
|
186
|
+
* @param durationSeconds - Duration of epoch in seconds
|
|
187
|
+
* @returns Sync randomness for the epoch
|
|
188
|
+
*/
|
|
189
|
+
export function generateSyncRandomness(
|
|
190
|
+
epoch: number,
|
|
191
|
+
durationSeconds: number = DEFAULT_SYNC_CONFIG.epochDurationSeconds
|
|
192
|
+
): SyncRandomness {
|
|
193
|
+
const value = randomBytes(32)
|
|
194
|
+
const expiresAt = Date.now() + durationSeconds * 1000
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
value,
|
|
198
|
+
epoch,
|
|
199
|
+
expiresAt,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Check if sync randomness is still valid
|
|
205
|
+
*
|
|
206
|
+
* @param randomness - Sync randomness to check
|
|
207
|
+
* @returns True if still valid
|
|
208
|
+
*/
|
|
209
|
+
export function isSyncRandomnessValid(randomness: SyncRandomness): boolean {
|
|
210
|
+
return Date.now() < randomness.expiresAt
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get current epoch number
|
|
215
|
+
*
|
|
216
|
+
* @param epochDurationSeconds - Duration of each epoch
|
|
217
|
+
* @returns Current epoch number
|
|
218
|
+
*/
|
|
219
|
+
export function getCurrentEpoch(
|
|
220
|
+
epochDurationSeconds: number = DEFAULT_SYNC_CONFIG.epochDurationSeconds
|
|
221
|
+
): number {
|
|
222
|
+
return Math.floor(Date.now() / (epochDurationSeconds * 1000))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ─── Oblivious Nullifier Derivation ───────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Domain separator for oblivious nullifier derivation
|
|
229
|
+
*/
|
|
230
|
+
const OBLIVIOUS_NULLIFIER_DOMAIN = 'SIP-OBLIVIOUS-NULLIFIER-V1'
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Derive oblivious nullifier from note commitment and sync randomness
|
|
234
|
+
*
|
|
235
|
+
* This is the key primitive that enables oblivious sync:
|
|
236
|
+
*
|
|
237
|
+
* Traditional: nullifier = f(note_commitment, spending_key)
|
|
238
|
+
* → Sync service sees nullifiers you check, can correlate
|
|
239
|
+
*
|
|
240
|
+
* Oblivious: nullifier = f(note_commitment, spending_key, sync_randomness)
|
|
241
|
+
* → Service cannot correlate without sync_randomness (user holds)
|
|
242
|
+
*
|
|
243
|
+
* @param noteCommitment - The note's commitment
|
|
244
|
+
* @param spendingKey - User's spending key
|
|
245
|
+
* @param syncRandomness - Per-epoch sync randomness
|
|
246
|
+
* @returns Oblivious nullifier
|
|
247
|
+
*/
|
|
248
|
+
export function deriveObliviousNullifier(
|
|
249
|
+
noteCommitment: HexString,
|
|
250
|
+
spendingKey: HexString,
|
|
251
|
+
syncRandomness: SyncRandomness
|
|
252
|
+
): ObliviousNullifier {
|
|
253
|
+
// Parse inputs
|
|
254
|
+
const commitmentBytes = hexToBytes(
|
|
255
|
+
noteCommitment.startsWith('0x') ? noteCommitment.slice(2) : noteCommitment
|
|
256
|
+
)
|
|
257
|
+
const keyBytes = hexToBytes(
|
|
258
|
+
spendingKey.startsWith('0x') ? spendingKey.slice(2) : spendingKey
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
// Build message: domain || commitment || randomness
|
|
263
|
+
const domain = utf8ToBytes(OBLIVIOUS_NULLIFIER_DOMAIN)
|
|
264
|
+
const message = new Uint8Array(domain.length + commitmentBytes.length + syncRandomness.value.length)
|
|
265
|
+
message.set(domain, 0)
|
|
266
|
+
message.set(commitmentBytes, domain.length)
|
|
267
|
+
message.set(syncRandomness.value, domain.length + commitmentBytes.length)
|
|
268
|
+
|
|
269
|
+
// HMAC-SHA256(spendingKey, message)
|
|
270
|
+
const nullifierBytes = hmac(sha256, keyBytes, message)
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
nullifier: `0x${bytesToHex(nullifierBytes)}` as HexString,
|
|
274
|
+
epoch: syncRandomness.epoch,
|
|
275
|
+
chainId: 'default', // Will be set by caller
|
|
276
|
+
}
|
|
277
|
+
} finally {
|
|
278
|
+
// Wipe sensitive data
|
|
279
|
+
secureWipe(keyBytes)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Derive traditional nullifier (non-oblivious, for comparison/fallback)
|
|
285
|
+
*
|
|
286
|
+
* @param noteCommitment - The note's commitment
|
|
287
|
+
* @param spendingKey - User's spending key
|
|
288
|
+
* @returns Traditional nullifier
|
|
289
|
+
*/
|
|
290
|
+
export function deriveTraditionalNullifier(
|
|
291
|
+
noteCommitment: HexString,
|
|
292
|
+
spendingKey: HexString
|
|
293
|
+
): HexString {
|
|
294
|
+
const commitmentBytes = hexToBytes(
|
|
295
|
+
noteCommitment.startsWith('0x') ? noteCommitment.slice(2) : noteCommitment
|
|
296
|
+
)
|
|
297
|
+
const keyBytes = hexToBytes(
|
|
298
|
+
spendingKey.startsWith('0x') ? spendingKey.slice(2) : spendingKey
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
// Simple HMAC-SHA256(spendingKey, commitment)
|
|
303
|
+
const nullifierBytes = hmac(sha256, keyBytes, commitmentBytes)
|
|
304
|
+
return `0x${bytesToHex(nullifierBytes)}` as HexString
|
|
305
|
+
} finally {
|
|
306
|
+
secureWipe(keyBytes)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ─── Oblivious Sync Provider Interface ────────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Oblivious Sync Provider Interface
|
|
314
|
+
*
|
|
315
|
+
* This is the core interface that sync services must implement.
|
|
316
|
+
* The service processes queries but learns NOTHING about:
|
|
317
|
+
* - Which notes belong to the user
|
|
318
|
+
* - When the user spends
|
|
319
|
+
* - Transaction patterns or amounts
|
|
320
|
+
*
|
|
321
|
+
* ## Implementation Requirements
|
|
322
|
+
*
|
|
323
|
+
* 1. **Query Processing**: Must process encrypted queries without decryption
|
|
324
|
+
* 2. **Note Scanning**: Return all potentially matching notes (over-approximate)
|
|
325
|
+
* 3. **No Logging**: Must not log query content or patterns
|
|
326
|
+
* 4. **Stateless**: Should not maintain per-user state
|
|
327
|
+
*/
|
|
328
|
+
export interface ObliviousSyncProvider {
|
|
329
|
+
/**
|
|
330
|
+
* Get provider name/identifier
|
|
331
|
+
*/
|
|
332
|
+
readonly name: string
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Supported chains
|
|
336
|
+
*/
|
|
337
|
+
readonly supportedChains: ChainId[]
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Initialize the provider
|
|
341
|
+
*/
|
|
342
|
+
initialize(): Promise<void>
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Check health of sync service for a chain
|
|
346
|
+
*
|
|
347
|
+
* @param chainId - Chain to check
|
|
348
|
+
* @returns Health information
|
|
349
|
+
*/
|
|
350
|
+
getHealth(chainId: ChainId): Promise<SyncServiceHealth>
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Scan for notes belonging to a viewing key (oblivious)
|
|
354
|
+
*
|
|
355
|
+
* The service returns ALL notes that COULD belong to the viewing key,
|
|
356
|
+
* without knowing which ones actually do. The user filters locally.
|
|
357
|
+
*
|
|
358
|
+
* @param viewingKeyPublic - Public viewing key (service sees this)
|
|
359
|
+
* @param blockRange - Blocks to scan
|
|
360
|
+
* @returns Encrypted notes that might belong to user
|
|
361
|
+
*/
|
|
362
|
+
scanForNotes(
|
|
363
|
+
viewingKeyPublic: HexString,
|
|
364
|
+
blockRange: BlockRange
|
|
365
|
+
): Promise<EncryptedNote[]>
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Check if nullifiers have been spent (oblivious)
|
|
369
|
+
*
|
|
370
|
+
* The service checks nullifiers but cannot correlate them to specific notes
|
|
371
|
+
* due to the sync randomness included in derivation.
|
|
372
|
+
*
|
|
373
|
+
* @param nullifiers - Oblivious nullifiers to check
|
|
374
|
+
* @returns Map of nullifier → spent status
|
|
375
|
+
*/
|
|
376
|
+
checkNullifiers(
|
|
377
|
+
nullifiers: ObliviousNullifier[]
|
|
378
|
+
): Promise<Map<HexString, boolean>>
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Get Merkle proofs for note inclusion
|
|
382
|
+
*
|
|
383
|
+
* @param commitments - Note commitments to get proofs for
|
|
384
|
+
* @param chainId - Chain ID
|
|
385
|
+
* @returns Merkle proofs for each commitment
|
|
386
|
+
*/
|
|
387
|
+
getMerkleProofs(
|
|
388
|
+
commitments: HexString[],
|
|
389
|
+
chainId: ChainId
|
|
390
|
+
): Promise<Map<HexString, MerkleProof>>
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Execute oblivious sync query
|
|
394
|
+
*
|
|
395
|
+
* This is the fully oblivious query method where the query itself
|
|
396
|
+
* is encrypted and the service learns nothing.
|
|
397
|
+
*
|
|
398
|
+
* @param query - Encrypted oblivious query
|
|
399
|
+
* @returns Sync response with encrypted notes
|
|
400
|
+
*/
|
|
401
|
+
executeObliviousQuery(
|
|
402
|
+
query: ObliviousSyncQuery
|
|
403
|
+
): Promise<ObliviousSyncResponse>
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Get current block height for a chain
|
|
407
|
+
*
|
|
408
|
+
* @param chainId - Chain to query
|
|
409
|
+
* @returns Current block height
|
|
410
|
+
*/
|
|
411
|
+
getCurrentHeight(chainId: ChainId): Promise<bigint>
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Subscribe to new notes (streaming)
|
|
415
|
+
*
|
|
416
|
+
* @param viewingKeyPublic - Public viewing key
|
|
417
|
+
* @param chainId - Chain to watch
|
|
418
|
+
* @param callback - Called when new notes found
|
|
419
|
+
* @returns Unsubscribe function
|
|
420
|
+
*/
|
|
421
|
+
subscribeToNotes(
|
|
422
|
+
viewingKeyPublic: HexString,
|
|
423
|
+
chainId: ChainId,
|
|
424
|
+
callback: (notes: EncryptedNote[]) => void
|
|
425
|
+
): () => void
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Shutdown the provider
|
|
429
|
+
*/
|
|
430
|
+
shutdown(): Promise<void>
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ─── Sync State Management ────────────────────────────────────────────────────
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Sync state for a wallet
|
|
437
|
+
*/
|
|
438
|
+
export interface WalletSyncState {
|
|
439
|
+
/** Sync height per chain */
|
|
440
|
+
syncHeights: Map<ChainId, bigint>
|
|
441
|
+
/** Current sync randomness per chain */
|
|
442
|
+
syncRandomness: Map<ChainId, SyncRandomness>
|
|
443
|
+
/** Nullifiers we've checked (for deduplication) */
|
|
444
|
+
checkedNullifiers: Set<HexString>
|
|
445
|
+
/** Pending notes to process */
|
|
446
|
+
pendingNotes: EncryptedNote[]
|
|
447
|
+
/** Last sync timestamp */
|
|
448
|
+
lastSyncTimestamp: number
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Create initial sync state
|
|
453
|
+
*
|
|
454
|
+
* @param chains - Chains to initialize
|
|
455
|
+
* @returns Initial sync state
|
|
456
|
+
*/
|
|
457
|
+
export function createSyncState(chains: ChainId[]): WalletSyncState {
|
|
458
|
+
const syncHeights = new Map<ChainId, bigint>()
|
|
459
|
+
const syncRandomness = new Map<ChainId, SyncRandomness>()
|
|
460
|
+
const epoch = getCurrentEpoch()
|
|
461
|
+
|
|
462
|
+
for (const chain of chains) {
|
|
463
|
+
syncHeights.set(chain, 0n)
|
|
464
|
+
syncRandomness.set(chain, generateSyncRandomness(epoch))
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
syncHeights,
|
|
469
|
+
syncRandomness,
|
|
470
|
+
checkedNullifiers: new Set(),
|
|
471
|
+
pendingNotes: [],
|
|
472
|
+
lastSyncTimestamp: 0,
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Update sync state after successful sync
|
|
478
|
+
*
|
|
479
|
+
* @param state - Current state
|
|
480
|
+
* @param chainId - Chain that was synced
|
|
481
|
+
* @param newHeight - New sync height
|
|
482
|
+
* @param notes - Notes received
|
|
483
|
+
* @returns Updated state
|
|
484
|
+
*/
|
|
485
|
+
export function updateSyncState(
|
|
486
|
+
state: WalletSyncState,
|
|
487
|
+
chainId: ChainId,
|
|
488
|
+
newHeight: bigint,
|
|
489
|
+
notes: EncryptedNote[]
|
|
490
|
+
): WalletSyncState {
|
|
491
|
+
const newSyncHeights = new Map(state.syncHeights)
|
|
492
|
+
newSyncHeights.set(chainId, newHeight)
|
|
493
|
+
|
|
494
|
+
// Rotate randomness if epoch changed
|
|
495
|
+
const currentEpoch = getCurrentEpoch()
|
|
496
|
+
const newRandomness = new Map(state.syncRandomness)
|
|
497
|
+
const existingRandomness = state.syncRandomness.get(chainId)
|
|
498
|
+
|
|
499
|
+
if (!existingRandomness || existingRandomness.epoch !== currentEpoch) {
|
|
500
|
+
newRandomness.set(chainId, generateSyncRandomness(currentEpoch))
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
...state,
|
|
505
|
+
syncHeights: newSyncHeights,
|
|
506
|
+
syncRandomness: newRandomness,
|
|
507
|
+
pendingNotes: [...state.pendingNotes, ...notes],
|
|
508
|
+
lastSyncTimestamp: Date.now(),
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ─── Viewing Key Integration ──────────────────────────────────────────────────
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Time-windowed viewing key for oblivious sync
|
|
516
|
+
*
|
|
517
|
+
* Enables auditors to see SPECIFIC windows, not ALL history.
|
|
518
|
+
*/
|
|
519
|
+
export interface TimeWindowedViewingKey {
|
|
520
|
+
/** The viewing key */
|
|
521
|
+
viewingKey: HexString
|
|
522
|
+
/** Start of valid window (timestamp) */
|
|
523
|
+
windowStart: number
|
|
524
|
+
/** End of valid window (timestamp) */
|
|
525
|
+
windowEnd: number
|
|
526
|
+
/** Epochs covered */
|
|
527
|
+
epochs: number[]
|
|
528
|
+
/** Hash for identification */
|
|
529
|
+
hash: Hash
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Create time-windowed viewing key for selective disclosure
|
|
534
|
+
*
|
|
535
|
+
* @param masterViewingKey - Master viewing key
|
|
536
|
+
* @param startTime - Start of window (timestamp)
|
|
537
|
+
* @param endTime - End of window (timestamp)
|
|
538
|
+
* @param epochDuration - Duration of each epoch
|
|
539
|
+
* @returns Time-windowed viewing key
|
|
540
|
+
*/
|
|
541
|
+
export function createTimeWindowedKey(
|
|
542
|
+
masterViewingKey: HexString,
|
|
543
|
+
startTime: number,
|
|
544
|
+
endTime: number,
|
|
545
|
+
epochDuration: number = DEFAULT_SYNC_CONFIG.epochDurationSeconds
|
|
546
|
+
): TimeWindowedViewingKey {
|
|
547
|
+
// Calculate epochs covered by window
|
|
548
|
+
const startEpoch = Math.floor(startTime / (epochDuration * 1000))
|
|
549
|
+
const endEpoch = Math.floor(endTime / (epochDuration * 1000))
|
|
550
|
+
const epochs: number[] = []
|
|
551
|
+
|
|
552
|
+
for (let e = startEpoch; e <= endEpoch; e++) {
|
|
553
|
+
epochs.push(e)
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Derive window-specific key: HMAC(masterKey, startTime || endTime)
|
|
557
|
+
const keyBytes = hexToBytes(
|
|
558
|
+
masterViewingKey.startsWith('0x') ? masterViewingKey.slice(2) : masterViewingKey
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
const windowData = new Uint8Array(16)
|
|
562
|
+
const view = new DataView(windowData.buffer)
|
|
563
|
+
view.setBigUint64(0, BigInt(startTime), false)
|
|
564
|
+
view.setBigUint64(8, BigInt(endTime), false)
|
|
565
|
+
|
|
566
|
+
const derivedKey = hmac(sha256, keyBytes, windowData)
|
|
567
|
+
const keyHash = sha256(derivedKey)
|
|
568
|
+
|
|
569
|
+
// Wipe master key bytes
|
|
570
|
+
secureWipe(keyBytes)
|
|
571
|
+
|
|
572
|
+
return {
|
|
573
|
+
viewingKey: `0x${bytesToHex(derivedKey)}` as HexString,
|
|
574
|
+
windowStart: startTime,
|
|
575
|
+
windowEnd: endTime,
|
|
576
|
+
epochs,
|
|
577
|
+
hash: `0x${bytesToHex(keyHash)}` as Hash,
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Validate that a note falls within viewing key window
|
|
583
|
+
*
|
|
584
|
+
* @param note - Note to check
|
|
585
|
+
* @param windowedKey - Time-windowed viewing key
|
|
586
|
+
* @returns True if note is within window
|
|
587
|
+
*/
|
|
588
|
+
export function isNoteInWindow(
|
|
589
|
+
_note: EncryptedNote,
|
|
590
|
+
_windowedKey: TimeWindowedViewingKey
|
|
591
|
+
): boolean {
|
|
592
|
+
// Check by block number would require block→timestamp mapping
|
|
593
|
+
// For now, we rely on epoch matching during sync
|
|
594
|
+
// Implementation note: Caller should filter by timestamp from note metadata
|
|
595
|
+
return true
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// ─── Error Types ──────────────────────────────────────────────────────────────
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Oblivious sync error codes
|
|
602
|
+
*/
|
|
603
|
+
export enum ObliviousSyncErrorCode {
|
|
604
|
+
PROVIDER_UNAVAILABLE = 'PROVIDER_UNAVAILABLE',
|
|
605
|
+
CHAIN_NOT_SUPPORTED = 'CHAIN_NOT_SUPPORTED',
|
|
606
|
+
SYNC_TIMEOUT = 'SYNC_TIMEOUT',
|
|
607
|
+
INVALID_RESPONSE = 'INVALID_RESPONSE',
|
|
608
|
+
NULLIFIER_CHECK_FAILED = 'NULLIFIER_CHECK_FAILED',
|
|
609
|
+
MERKLE_PROOF_INVALID = 'MERKLE_PROOF_INVALID',
|
|
610
|
+
RANDOMNESS_EXPIRED = 'RANDOMNESS_EXPIRED',
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Error thrown by oblivious sync operations
|
|
615
|
+
*/
|
|
616
|
+
export class ObliviousSyncError extends Error {
|
|
617
|
+
constructor(
|
|
618
|
+
message: string,
|
|
619
|
+
public readonly code: ObliviousSyncErrorCode,
|
|
620
|
+
public readonly context?: Record<string, unknown>
|
|
621
|
+
) {
|
|
622
|
+
super(message)
|
|
623
|
+
this.name = 'ObliviousSyncError'
|
|
624
|
+
}
|
|
625
|
+
}
|