@sip-protocol/sdk 0.7.3 → 0.7.4
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/LICENSE +21 -0
- 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 +54 -21
- 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,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain-Specific Address Derivation
|
|
3
|
+
*
|
|
4
|
+
* Functions for converting stealth addresses to chain-specific formats.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { HexString } from '@sip-protocol/types'
|
|
8
|
+
import { ValidationError } from '../errors'
|
|
9
|
+
import { isValidHex, isValidEd25519PublicKey } from '../validation'
|
|
10
|
+
import { bytesToBase58, base58ToBytes, bytesToHex, hexToBytes } from './utils'
|
|
11
|
+
|
|
12
|
+
// ─── Solana Address Derivation ──────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert an ed25519 public key (hex) to a Solana address (base58)
|
|
16
|
+
*
|
|
17
|
+
* Solana addresses are base58-encoded 32-byte ed25519 public keys.
|
|
18
|
+
*
|
|
19
|
+
* @param publicKey - 32-byte ed25519 public key as hex string (with 0x prefix)
|
|
20
|
+
* @returns Base58-encoded Solana address
|
|
21
|
+
* @throws {ValidationError} If public key is invalid
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const { stealthAddress } = generateEd25519StealthAddress(metaAddress)
|
|
26
|
+
* const solanaAddress = ed25519PublicKeyToSolanaAddress(stealthAddress.address)
|
|
27
|
+
* // Returns: "7Vbmv1jt4vyuqBZcpYPpnVhrqVe5e6ZPBJCyqLqzQPvN" (example)
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function ed25519PublicKeyToSolanaAddress(publicKey: HexString): string {
|
|
31
|
+
if (!isValidHex(publicKey)) {
|
|
32
|
+
throw new ValidationError(
|
|
33
|
+
'publicKey must be a valid hex string with 0x prefix',
|
|
34
|
+
'publicKey'
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!isValidEd25519PublicKey(publicKey)) {
|
|
39
|
+
throw new ValidationError(
|
|
40
|
+
'publicKey must be 32 bytes (64 hex characters)',
|
|
41
|
+
'publicKey'
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Convert hex to bytes (remove 0x prefix)
|
|
46
|
+
const publicKeyBytes = hexToBytes(publicKey.slice(2))
|
|
47
|
+
|
|
48
|
+
// Encode as base58
|
|
49
|
+
return bytesToBase58(publicKeyBytes)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate a Solana address format
|
|
54
|
+
*
|
|
55
|
+
* Checks that the address:
|
|
56
|
+
* - Is a valid base58 string
|
|
57
|
+
* - Decodes to exactly 32 bytes (ed25519 public key size)
|
|
58
|
+
*/
|
|
59
|
+
export function isValidSolanaAddress(address: string): boolean {
|
|
60
|
+
if (typeof address !== 'string' || address.length === 0) {
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Solana addresses are typically 32-44 characters
|
|
65
|
+
if (address.length < 32 || address.length > 44) {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const decoded = base58ToBytes(address)
|
|
71
|
+
// Valid Solana address is exactly 32 bytes
|
|
72
|
+
return decoded.length === 32
|
|
73
|
+
} catch {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Convert a Solana address (base58) back to ed25519 public key (hex)
|
|
80
|
+
*/
|
|
81
|
+
export function solanaAddressToEd25519PublicKey(address: string): HexString {
|
|
82
|
+
if (!isValidSolanaAddress(address)) {
|
|
83
|
+
throw new ValidationError(
|
|
84
|
+
'Invalid Solana address format',
|
|
85
|
+
'address'
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const decoded = base58ToBytes(address)
|
|
90
|
+
return `0x${bytesToHex(decoded)}` as HexString
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── NEAR Address Derivation ────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Convert ed25519 public key to NEAR implicit account address
|
|
97
|
+
*
|
|
98
|
+
* NEAR implicit accounts are lowercase hex-encoded ed25519 public keys (64 characters).
|
|
99
|
+
* No prefix, just raw 32 bytes as lowercase hex.
|
|
100
|
+
*/
|
|
101
|
+
export function ed25519PublicKeyToNearAddress(publicKey: HexString): string {
|
|
102
|
+
if (!isValidHex(publicKey)) {
|
|
103
|
+
throw new ValidationError(
|
|
104
|
+
'publicKey must be a valid hex string with 0x prefix',
|
|
105
|
+
'publicKey'
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!isValidEd25519PublicKey(publicKey)) {
|
|
110
|
+
throw new ValidationError(
|
|
111
|
+
'publicKey must be 32 bytes (64 hex characters)',
|
|
112
|
+
'publicKey'
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// NEAR implicit accounts are lowercase hex without 0x prefix
|
|
117
|
+
return publicKey.slice(2).toLowerCase()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Convert NEAR implicit account address back to ed25519 public key
|
|
122
|
+
*/
|
|
123
|
+
export function nearAddressToEd25519PublicKey(address: string): HexString {
|
|
124
|
+
if (!isValidNearImplicitAddress(address)) {
|
|
125
|
+
throw new ValidationError(
|
|
126
|
+
'Invalid NEAR implicit address format',
|
|
127
|
+
'address'
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return `0x${address.toLowerCase()}` as HexString
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Validate a NEAR implicit account address
|
|
136
|
+
*
|
|
137
|
+
* NEAR implicit accounts are:
|
|
138
|
+
* - Exactly 64 lowercase hex characters
|
|
139
|
+
* - No prefix (no "0x")
|
|
140
|
+
* - Represent a 32-byte ed25519 public key
|
|
141
|
+
*/
|
|
142
|
+
export function isValidNearImplicitAddress(address: string): boolean {
|
|
143
|
+
// Must be a string
|
|
144
|
+
if (typeof address !== 'string' || address.length === 0) {
|
|
145
|
+
return false
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Must be exactly 64 characters (32 bytes as hex)
|
|
149
|
+
if (address.length !== 64) {
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Must be lowercase hex only (no 0x prefix)
|
|
154
|
+
return /^[0-9a-f]{64}$/.test(address)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Check if a string is a valid NEAR account ID (named or implicit)
|
|
159
|
+
*
|
|
160
|
+
* Supports both:
|
|
161
|
+
* - Named accounts: alice.near, bob.testnet
|
|
162
|
+
* - Implicit accounts: 64 hex characters
|
|
163
|
+
*/
|
|
164
|
+
export function isValidNearAccountId(accountId: string): boolean {
|
|
165
|
+
// Must be a string
|
|
166
|
+
if (typeof accountId !== 'string' || accountId.length === 0) {
|
|
167
|
+
return false
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check if it's a valid implicit account (64 hex chars)
|
|
171
|
+
if (isValidNearImplicitAddress(accountId)) {
|
|
172
|
+
return true
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Named accounts: 2-64 characters, lowercase alphanumeric with . _ -
|
|
176
|
+
// Must start and end with alphanumeric
|
|
177
|
+
if (accountId.length < 2 || accountId.length > 64) {
|
|
178
|
+
return false
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// NEAR account ID pattern
|
|
182
|
+
const nearAccountPattern = /^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$/
|
|
183
|
+
if (!nearAccountPattern.test(accountId)) {
|
|
184
|
+
return false
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Cannot have consecutive dots
|
|
188
|
+
if (accountId.includes('..')) {
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return true
|
|
193
|
+
}
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ed25519 Stealth Address Implementation
|
|
3
|
+
*
|
|
4
|
+
* Implements DKSAP (Dual-Key Stealth Address Protocol) for ed25519 curves.
|
|
5
|
+
* Used for Solana, NEAR, Aptos, and Sui chains.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ed25519 } from '@noble/curves/ed25519'
|
|
9
|
+
import { randomBytes } from '@noble/hashes/utils'
|
|
10
|
+
import type {
|
|
11
|
+
StealthMetaAddress,
|
|
12
|
+
StealthAddress,
|
|
13
|
+
StealthAddressRecovery,
|
|
14
|
+
ChainId,
|
|
15
|
+
HexString,
|
|
16
|
+
} from '@sip-protocol/types'
|
|
17
|
+
import { ValidationError } from '../errors'
|
|
18
|
+
import {
|
|
19
|
+
isValidChainId,
|
|
20
|
+
isValidEd25519PublicKey,
|
|
21
|
+
isValidPrivateKey,
|
|
22
|
+
} from '../validation'
|
|
23
|
+
import { secureWipe, secureWipeAll } from '../secure-memory'
|
|
24
|
+
import {
|
|
25
|
+
bytesToBigInt,
|
|
26
|
+
bigIntToBytesLE,
|
|
27
|
+
sha256,
|
|
28
|
+
bytesToHex,
|
|
29
|
+
hexToBytes,
|
|
30
|
+
ED25519_ORDER,
|
|
31
|
+
ED25519_CHAINS,
|
|
32
|
+
getEd25519Scalar,
|
|
33
|
+
} from './utils'
|
|
34
|
+
|
|
35
|
+
// ─── Chain Detection ────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if a chain uses ed25519 for stealth addresses
|
|
39
|
+
*/
|
|
40
|
+
export function isEd25519Chain(chain: ChainId): boolean {
|
|
41
|
+
return (ED25519_CHAINS as readonly string[]).includes(chain)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Curve type used for stealth addresses
|
|
46
|
+
*/
|
|
47
|
+
export type StealthCurve = 'secp256k1' | 'ed25519'
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the curve type used by a chain for stealth addresses
|
|
51
|
+
*
|
|
52
|
+
* @param chain - Chain identifier
|
|
53
|
+
* @returns 'ed25519' for Solana/NEAR/Aptos/Sui, 'secp256k1' for EVM chains
|
|
54
|
+
*/
|
|
55
|
+
export function getCurveForChain(chain: ChainId): StealthCurve {
|
|
56
|
+
return isEd25519Chain(chain) ? 'ed25519' : 'secp256k1'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── Validation ─────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Validate an ed25519 StealthMetaAddress object
|
|
63
|
+
*/
|
|
64
|
+
export function validateEd25519StealthMetaAddress(
|
|
65
|
+
metaAddress: StealthMetaAddress,
|
|
66
|
+
field: string = 'recipientMetaAddress'
|
|
67
|
+
): void {
|
|
68
|
+
if (!metaAddress || typeof metaAddress !== 'object') {
|
|
69
|
+
throw new ValidationError('must be an object', field)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!isValidChainId(metaAddress.chain)) {
|
|
73
|
+
throw new ValidationError(
|
|
74
|
+
`invalid chain '${metaAddress.chain}'`,
|
|
75
|
+
`${field}.chain`
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!isEd25519Chain(metaAddress.chain)) {
|
|
80
|
+
throw new ValidationError(
|
|
81
|
+
`chain '${metaAddress.chain}' does not use ed25519, use secp256k1 functions instead`,
|
|
82
|
+
`${field}.chain`
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!isValidEd25519PublicKey(metaAddress.spendingKey)) {
|
|
87
|
+
throw new ValidationError(
|
|
88
|
+
'spendingKey must be a valid ed25519 public key (32 bytes)',
|
|
89
|
+
`${field}.spendingKey`
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!isValidEd25519PublicKey(metaAddress.viewingKey)) {
|
|
94
|
+
throw new ValidationError(
|
|
95
|
+
'viewingKey must be a valid ed25519 public key (32 bytes)',
|
|
96
|
+
`${field}.viewingKey`
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Validate an ed25519 StealthAddress object
|
|
103
|
+
*/
|
|
104
|
+
export function validateEd25519StealthAddress(
|
|
105
|
+
stealthAddress: StealthAddress,
|
|
106
|
+
field: string = 'stealthAddress'
|
|
107
|
+
): void {
|
|
108
|
+
if (!stealthAddress || typeof stealthAddress !== 'object') {
|
|
109
|
+
throw new ValidationError('must be an object', field)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!isValidEd25519PublicKey(stealthAddress.address)) {
|
|
113
|
+
throw new ValidationError(
|
|
114
|
+
'address must be a valid ed25519 public key (32 bytes)',
|
|
115
|
+
`${field}.address`
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!isValidEd25519PublicKey(stealthAddress.ephemeralPublicKey)) {
|
|
120
|
+
throw new ValidationError(
|
|
121
|
+
'ephemeralPublicKey must be a valid ed25519 public key (32 bytes)',
|
|
122
|
+
`${field}.ephemeralPublicKey`
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (typeof stealthAddress.viewTag !== 'number' ||
|
|
127
|
+
!Number.isInteger(stealthAddress.viewTag) ||
|
|
128
|
+
stealthAddress.viewTag < 0 ||
|
|
129
|
+
stealthAddress.viewTag > 255) {
|
|
130
|
+
throw new ValidationError(
|
|
131
|
+
'viewTag must be an integer between 0 and 255',
|
|
132
|
+
`${field}.viewTag`
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── Meta-Address Generation ────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Generate a new ed25519 stealth meta-address keypair
|
|
141
|
+
*
|
|
142
|
+
* @param chain - Target chain (must be ed25519-compatible: solana, near, aptos, sui)
|
|
143
|
+
* @param label - Optional human-readable label
|
|
144
|
+
* @returns Stealth meta-address and private keys
|
|
145
|
+
* @throws {ValidationError} If chain is invalid or not ed25519-compatible
|
|
146
|
+
*/
|
|
147
|
+
export function generateEd25519StealthMetaAddress(
|
|
148
|
+
chain: ChainId,
|
|
149
|
+
label?: string,
|
|
150
|
+
): {
|
|
151
|
+
metaAddress: StealthMetaAddress
|
|
152
|
+
spendingPrivateKey: HexString
|
|
153
|
+
viewingPrivateKey: HexString
|
|
154
|
+
} {
|
|
155
|
+
if (!isValidChainId(chain)) {
|
|
156
|
+
throw new ValidationError(
|
|
157
|
+
`invalid chain '${chain}', must be one of: solana, ethereum, near, zcash, polygon, arbitrum, optimism, base, bitcoin, aptos, sui, cosmos, osmosis, injective, celestia, sei, dydx`,
|
|
158
|
+
'chain'
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!isEd25519Chain(chain)) {
|
|
163
|
+
throw new ValidationError(
|
|
164
|
+
`chain '${chain}' does not use ed25519, use generateStealthMetaAddress() for secp256k1 chains`,
|
|
165
|
+
'chain'
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Generate random private keys (32-byte seeds)
|
|
170
|
+
const spendingPrivateKey = randomBytes(32)
|
|
171
|
+
const viewingPrivateKey = randomBytes(32)
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
// Derive public keys using ed25519
|
|
175
|
+
const spendingKey = ed25519.getPublicKey(spendingPrivateKey)
|
|
176
|
+
const viewingKey = ed25519.getPublicKey(viewingPrivateKey)
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
metaAddress: {
|
|
180
|
+
spendingKey: `0x${bytesToHex(spendingKey)}` as HexString,
|
|
181
|
+
viewingKey: `0x${bytesToHex(viewingKey)}` as HexString,
|
|
182
|
+
chain,
|
|
183
|
+
label,
|
|
184
|
+
},
|
|
185
|
+
spendingPrivateKey: `0x${bytesToHex(spendingPrivateKey)}` as HexString,
|
|
186
|
+
viewingPrivateKey: `0x${bytesToHex(viewingPrivateKey)}` as HexString,
|
|
187
|
+
}
|
|
188
|
+
} finally {
|
|
189
|
+
secureWipeAll(spendingPrivateKey, viewingPrivateKey)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ─── Stealth Address Generation ─────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Generate a one-time ed25519 stealth address for a recipient
|
|
197
|
+
*
|
|
198
|
+
* Algorithm (DKSAP for ed25519):
|
|
199
|
+
* 1. Generate ephemeral keypair (r, R = r*G)
|
|
200
|
+
* 2. Compute shared secret: S = r * P_spend (ephemeral scalar * spending public)
|
|
201
|
+
* 3. Hash shared secret: h = SHA256(S)
|
|
202
|
+
* 4. Derive stealth public key: P_stealth = P_view + h*G
|
|
203
|
+
*/
|
|
204
|
+
export function generateEd25519StealthAddress(
|
|
205
|
+
recipientMetaAddress: StealthMetaAddress,
|
|
206
|
+
): {
|
|
207
|
+
stealthAddress: StealthAddress
|
|
208
|
+
sharedSecret: HexString
|
|
209
|
+
} {
|
|
210
|
+
validateEd25519StealthMetaAddress(recipientMetaAddress)
|
|
211
|
+
|
|
212
|
+
// Generate ephemeral keypair
|
|
213
|
+
const ephemeralPrivateKey = randomBytes(32)
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const ephemeralPublicKey = ed25519.getPublicKey(ephemeralPrivateKey)
|
|
217
|
+
|
|
218
|
+
// Parse recipient's keys (remove 0x prefix)
|
|
219
|
+
const spendingKeyBytes = hexToBytes(recipientMetaAddress.spendingKey.slice(2))
|
|
220
|
+
const viewingKeyBytes = hexToBytes(recipientMetaAddress.viewingKey.slice(2))
|
|
221
|
+
|
|
222
|
+
// Get ephemeral scalar from private key and reduce mod L
|
|
223
|
+
const rawEphemeralScalar = getEd25519Scalar(ephemeralPrivateKey)
|
|
224
|
+
const ephemeralScalar = rawEphemeralScalar % ED25519_ORDER
|
|
225
|
+
if (ephemeralScalar === 0n) {
|
|
226
|
+
throw new Error('CRITICAL: Zero ephemeral scalar after reduction - investigate RNG')
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// S = ephemeral_scalar * P_spend
|
|
230
|
+
const spendingPoint = ed25519.ExtendedPoint.fromHex(spendingKeyBytes)
|
|
231
|
+
const sharedSecretPoint = spendingPoint.multiply(ephemeralScalar)
|
|
232
|
+
|
|
233
|
+
// Hash the shared secret point
|
|
234
|
+
const sharedSecretHash = sha256(sharedSecretPoint.toRawBytes())
|
|
235
|
+
|
|
236
|
+
// Derive stealth public key: P_stealth = P_view + hash(S)*G
|
|
237
|
+
const hashScalar = bytesToBigInt(sharedSecretHash) % ED25519_ORDER
|
|
238
|
+
if (hashScalar === 0n) {
|
|
239
|
+
throw new Error('CRITICAL: Zero hash scalar after reduction - investigate hash computation')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Compute hash(S) * G
|
|
243
|
+
const hashTimesG = ed25519.ExtendedPoint.BASE.multiply(hashScalar)
|
|
244
|
+
|
|
245
|
+
// Add to viewing key: P_stealth = P_view + hash(S)*G
|
|
246
|
+
const viewingPoint = ed25519.ExtendedPoint.fromHex(viewingKeyBytes)
|
|
247
|
+
const stealthPoint = viewingPoint.add(hashTimesG)
|
|
248
|
+
const stealthAddressBytes = stealthPoint.toRawBytes()
|
|
249
|
+
|
|
250
|
+
// Compute view tag
|
|
251
|
+
const viewTag = sharedSecretHash[0]
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
stealthAddress: {
|
|
255
|
+
address: `0x${bytesToHex(stealthAddressBytes)}` as HexString,
|
|
256
|
+
ephemeralPublicKey: `0x${bytesToHex(ephemeralPublicKey)}` as HexString,
|
|
257
|
+
viewTag,
|
|
258
|
+
},
|
|
259
|
+
sharedSecret: `0x${bytesToHex(sharedSecretHash)}` as HexString,
|
|
260
|
+
}
|
|
261
|
+
} finally {
|
|
262
|
+
secureWipe(ephemeralPrivateKey)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ─── Private Key Derivation ─────────────────────────────────────────────────
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Derive the private key for an ed25519 stealth address
|
|
270
|
+
*
|
|
271
|
+
* **IMPORTANT: Derived Key Format**
|
|
272
|
+
*
|
|
273
|
+
* The returned `privateKey` is a **raw scalar** in little-endian format, NOT a standard
|
|
274
|
+
* ed25519 seed. To compute the public key from the derived private key:
|
|
275
|
+
* ```typescript
|
|
276
|
+
* const scalar = bytesToBigIntLE(hexToBytes(privateKey.slice(2)))
|
|
277
|
+
* const publicKey = ed25519.ExtendedPoint.BASE.multiply(scalar)
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
export function deriveEd25519StealthPrivateKey(
|
|
281
|
+
stealthAddress: StealthAddress,
|
|
282
|
+
spendingPrivateKey: HexString,
|
|
283
|
+
viewingPrivateKey: HexString,
|
|
284
|
+
): StealthAddressRecovery {
|
|
285
|
+
validateEd25519StealthAddress(stealthAddress)
|
|
286
|
+
|
|
287
|
+
if (!isValidPrivateKey(spendingPrivateKey)) {
|
|
288
|
+
throw new ValidationError(
|
|
289
|
+
'must be a valid 32-byte hex string',
|
|
290
|
+
'spendingPrivateKey'
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!isValidPrivateKey(viewingPrivateKey)) {
|
|
295
|
+
throw new ValidationError(
|
|
296
|
+
'must be a valid 32-byte hex string',
|
|
297
|
+
'viewingPrivateKey'
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2))
|
|
302
|
+
const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
|
|
303
|
+
const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
// Get spending scalar and reduce mod L
|
|
307
|
+
const rawSpendingScalar = getEd25519Scalar(spendingPrivBytes)
|
|
308
|
+
const spendingScalar = rawSpendingScalar % ED25519_ORDER
|
|
309
|
+
if (spendingScalar === 0n) {
|
|
310
|
+
throw new Error('CRITICAL: Zero spending scalar after reduction - investigate key derivation')
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Compute shared secret: S = spending_scalar * R
|
|
314
|
+
const ephemeralPoint = ed25519.ExtendedPoint.fromHex(ephemeralPubBytes)
|
|
315
|
+
const sharedSecretPoint = ephemeralPoint.multiply(spendingScalar)
|
|
316
|
+
|
|
317
|
+
// Hash the shared secret
|
|
318
|
+
const sharedSecretHash = sha256(sharedSecretPoint.toRawBytes())
|
|
319
|
+
|
|
320
|
+
// Get viewing scalar and reduce mod L
|
|
321
|
+
const rawViewingScalar = getEd25519Scalar(viewingPrivBytes)
|
|
322
|
+
const viewingScalar = rawViewingScalar % ED25519_ORDER
|
|
323
|
+
if (viewingScalar === 0n) {
|
|
324
|
+
throw new Error('CRITICAL: Zero viewing scalar after reduction - investigate key derivation')
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Derive stealth private key: s_stealth = s_view + hash(S) mod L
|
|
328
|
+
const hashScalar = bytesToBigInt(sharedSecretHash) % ED25519_ORDER
|
|
329
|
+
if (hashScalar === 0n) {
|
|
330
|
+
throw new Error('CRITICAL: Zero hash scalar after reduction - investigate hash computation')
|
|
331
|
+
}
|
|
332
|
+
const stealthPrivateScalar = (viewingScalar + hashScalar) % ED25519_ORDER
|
|
333
|
+
if (stealthPrivateScalar === 0n) {
|
|
334
|
+
throw new Error('CRITICAL: Zero stealth scalar after reduction - investigate key derivation')
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Convert to bytes (little-endian for ed25519)
|
|
338
|
+
const stealthPrivateKey = bigIntToBytesLE(stealthPrivateScalar, 32)
|
|
339
|
+
|
|
340
|
+
const result = {
|
|
341
|
+
stealthAddress: stealthAddress.address,
|
|
342
|
+
ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
|
|
343
|
+
privateKey: `0x${bytesToHex(stealthPrivateKey)}` as HexString,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
secureWipe(stealthPrivateKey)
|
|
347
|
+
|
|
348
|
+
return result
|
|
349
|
+
} finally {
|
|
350
|
+
secureWipeAll(spendingPrivBytes, viewingPrivBytes)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ─── Address Checking ───────────────────────────────────────────────────────
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Check if an ed25519 stealth address was intended for this recipient
|
|
358
|
+
*/
|
|
359
|
+
export function checkEd25519StealthAddress(
|
|
360
|
+
stealthAddress: StealthAddress,
|
|
361
|
+
spendingPrivateKey: HexString,
|
|
362
|
+
viewingPrivateKey: HexString,
|
|
363
|
+
): boolean {
|
|
364
|
+
validateEd25519StealthAddress(stealthAddress)
|
|
365
|
+
|
|
366
|
+
if (!isValidPrivateKey(spendingPrivateKey)) {
|
|
367
|
+
throw new ValidationError(
|
|
368
|
+
'must be a valid 32-byte hex string',
|
|
369
|
+
'spendingPrivateKey'
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!isValidPrivateKey(viewingPrivateKey)) {
|
|
374
|
+
throw new ValidationError(
|
|
375
|
+
'must be a valid 32-byte hex string',
|
|
376
|
+
'viewingPrivateKey'
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2))
|
|
381
|
+
const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
|
|
382
|
+
const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
// Get spending scalar and reduce mod L
|
|
386
|
+
const rawSpendingScalar = getEd25519Scalar(spendingPrivBytes)
|
|
387
|
+
const spendingScalar = rawSpendingScalar % ED25519_ORDER
|
|
388
|
+
if (spendingScalar === 0n) {
|
|
389
|
+
throw new Error('CRITICAL: Zero spending scalar after reduction - investigate key derivation')
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Compute shared secret: S = spending_scalar * R
|
|
393
|
+
const ephemeralPoint = ed25519.ExtendedPoint.fromHex(ephemeralPubBytes)
|
|
394
|
+
const sharedSecretPoint = ephemeralPoint.multiply(spendingScalar)
|
|
395
|
+
|
|
396
|
+
// Hash the shared secret
|
|
397
|
+
const sharedSecretHash = sha256(sharedSecretPoint.toRawBytes())
|
|
398
|
+
|
|
399
|
+
// View tag check
|
|
400
|
+
if (sharedSecretHash[0] !== stealthAddress.viewTag) {
|
|
401
|
+
return false
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Full check
|
|
405
|
+
const rawViewingScalar = getEd25519Scalar(viewingPrivBytes)
|
|
406
|
+
const viewingScalar = rawViewingScalar % ED25519_ORDER
|
|
407
|
+
if (viewingScalar === 0n) {
|
|
408
|
+
throw new Error('CRITICAL: Zero viewing scalar after reduction - investigate key derivation')
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const hashScalar = bytesToBigInt(sharedSecretHash) % ED25519_ORDER
|
|
412
|
+
if (hashScalar === 0n) {
|
|
413
|
+
throw new Error('CRITICAL: Zero hash scalar after reduction - investigate hash computation')
|
|
414
|
+
}
|
|
415
|
+
const stealthPrivateScalar = (viewingScalar + hashScalar) % ED25519_ORDER
|
|
416
|
+
if (stealthPrivateScalar === 0n) {
|
|
417
|
+
throw new Error('CRITICAL: Zero stealth scalar after reduction - investigate key derivation')
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Compute expected public key from derived scalar
|
|
421
|
+
const expectedPubKey = ed25519.ExtendedPoint.BASE.multiply(stealthPrivateScalar)
|
|
422
|
+
const expectedPubKeyBytes = expectedPubKey.toRawBytes()
|
|
423
|
+
|
|
424
|
+
// Compare with provided stealth address
|
|
425
|
+
const providedAddress = hexToBytes(stealthAddress.address.slice(2))
|
|
426
|
+
|
|
427
|
+
return bytesToHex(expectedPubKeyBytes) === bytesToHex(providedAddress)
|
|
428
|
+
} finally {
|
|
429
|
+
secureWipeAll(spendingPrivBytes, viewingPrivBytes)
|
|
430
|
+
}
|
|
431
|
+
}
|