@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,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* secp256k1 Stealth Address Implementation
|
|
3
|
+
*
|
|
4
|
+
* Implements EIP-5564 style stealth addresses using secp256k1.
|
|
5
|
+
* Used for Ethereum, Polygon, Arbitrum, Optimism, Base, Bitcoin, Zcash.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { secp256k1 } from '@noble/curves/secp256k1'
|
|
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
|
+
isValidCompressedPublicKey,
|
|
21
|
+
isValidPrivateKey,
|
|
22
|
+
} from '../validation'
|
|
23
|
+
import { secureWipe, secureWipeAll } from '../secure-memory'
|
|
24
|
+
import {
|
|
25
|
+
bytesToBigInt,
|
|
26
|
+
bigIntToBytes,
|
|
27
|
+
sha256,
|
|
28
|
+
bytesToHex,
|
|
29
|
+
hexToBytes,
|
|
30
|
+
toChecksumAddress,
|
|
31
|
+
keccak_256,
|
|
32
|
+
} from './utils'
|
|
33
|
+
// Note: isEd25519Chain is imported in the main index.ts for dispatch logic
|
|
34
|
+
|
|
35
|
+
// ─── Meta-Address Generation ────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generate a new secp256k1 stealth meta-address keypair
|
|
39
|
+
*
|
|
40
|
+
* @internal Use generateStealthMetaAddress() which dispatches to this
|
|
41
|
+
*/
|
|
42
|
+
export function generateSecp256k1StealthMetaAddress(
|
|
43
|
+
chain: ChainId,
|
|
44
|
+
label?: string,
|
|
45
|
+
): {
|
|
46
|
+
metaAddress: StealthMetaAddress
|
|
47
|
+
spendingPrivateKey: HexString
|
|
48
|
+
viewingPrivateKey: HexString
|
|
49
|
+
} {
|
|
50
|
+
const spendingPrivateKey = randomBytes(32)
|
|
51
|
+
const viewingPrivateKey = randomBytes(32)
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Derive public keys
|
|
55
|
+
const spendingKey = secp256k1.getPublicKey(spendingPrivateKey, true)
|
|
56
|
+
const viewingKey = secp256k1.getPublicKey(viewingPrivateKey, true)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
metaAddress: {
|
|
60
|
+
spendingKey: `0x${bytesToHex(spendingKey)}` as HexString,
|
|
61
|
+
viewingKey: `0x${bytesToHex(viewingKey)}` as HexString,
|
|
62
|
+
chain,
|
|
63
|
+
label,
|
|
64
|
+
},
|
|
65
|
+
spendingPrivateKey: `0x${bytesToHex(spendingPrivateKey)}` as HexString,
|
|
66
|
+
viewingPrivateKey: `0x${bytesToHex(viewingPrivateKey)}` as HexString,
|
|
67
|
+
}
|
|
68
|
+
} finally {
|
|
69
|
+
secureWipeAll(spendingPrivateKey, viewingPrivateKey)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ─── Stealth Address Generation ─────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Validate a secp256k1 StealthMetaAddress
|
|
77
|
+
*/
|
|
78
|
+
export function validateSecp256k1StealthMetaAddress(
|
|
79
|
+
metaAddress: StealthMetaAddress,
|
|
80
|
+
field: string = 'recipientMetaAddress'
|
|
81
|
+
): void {
|
|
82
|
+
if (!metaAddress || typeof metaAddress !== 'object') {
|
|
83
|
+
throw new ValidationError('must be an object', field)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!isValidChainId(metaAddress.chain)) {
|
|
87
|
+
throw new ValidationError(
|
|
88
|
+
`invalid chain '${metaAddress.chain}'`,
|
|
89
|
+
`${field}.chain`
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!isValidCompressedPublicKey(metaAddress.spendingKey)) {
|
|
94
|
+
throw new ValidationError(
|
|
95
|
+
'spendingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)',
|
|
96
|
+
`${field}.spendingKey`
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!isValidCompressedPublicKey(metaAddress.viewingKey)) {
|
|
101
|
+
throw new ValidationError(
|
|
102
|
+
'viewingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)',
|
|
103
|
+
`${field}.viewingKey`
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Validate a secp256k1 StealthAddress
|
|
110
|
+
*/
|
|
111
|
+
export function validateSecp256k1StealthAddress(
|
|
112
|
+
stealthAddress: StealthAddress,
|
|
113
|
+
field: string = 'stealthAddress'
|
|
114
|
+
): void {
|
|
115
|
+
if (!stealthAddress || typeof stealthAddress !== 'object') {
|
|
116
|
+
throw new ValidationError('must be an object', field)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!isValidCompressedPublicKey(stealthAddress.address)) {
|
|
120
|
+
throw new ValidationError(
|
|
121
|
+
'address must be a valid compressed secp256k1 public key',
|
|
122
|
+
`${field}.address`
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!isValidCompressedPublicKey(stealthAddress.ephemeralPublicKey)) {
|
|
127
|
+
throw new ValidationError(
|
|
128
|
+
'ephemeralPublicKey must be a valid compressed secp256k1 public key',
|
|
129
|
+
`${field}.ephemeralPublicKey`
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (typeof stealthAddress.viewTag !== 'number' ||
|
|
134
|
+
!Number.isInteger(stealthAddress.viewTag) ||
|
|
135
|
+
stealthAddress.viewTag < 0 ||
|
|
136
|
+
stealthAddress.viewTag > 255) {
|
|
137
|
+
throw new ValidationError(
|
|
138
|
+
'viewTag must be an integer between 0 and 255',
|
|
139
|
+
`${field}.viewTag`
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Generate a one-time secp256k1 stealth address
|
|
146
|
+
*
|
|
147
|
+
* @internal Use generateStealthAddress() which dispatches to this
|
|
148
|
+
*/
|
|
149
|
+
export function generateSecp256k1StealthAddress(
|
|
150
|
+
recipientMetaAddress: StealthMetaAddress,
|
|
151
|
+
): {
|
|
152
|
+
stealthAddress: StealthAddress
|
|
153
|
+
sharedSecret: HexString
|
|
154
|
+
} {
|
|
155
|
+
validateSecp256k1StealthMetaAddress(recipientMetaAddress)
|
|
156
|
+
|
|
157
|
+
const ephemeralPrivateKey = randomBytes(32)
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const ephemeralPublicKey = secp256k1.getPublicKey(ephemeralPrivateKey, true)
|
|
161
|
+
|
|
162
|
+
// Parse recipient's keys (remove 0x prefix)
|
|
163
|
+
const spendingKeyBytes = hexToBytes(recipientMetaAddress.spendingKey.slice(2))
|
|
164
|
+
const viewingKeyBytes = hexToBytes(recipientMetaAddress.viewingKey.slice(2))
|
|
165
|
+
|
|
166
|
+
// Compute shared secret: S = r * P (ephemeral private * spending public)
|
|
167
|
+
const sharedSecretPoint = secp256k1.getSharedSecret(
|
|
168
|
+
ephemeralPrivateKey,
|
|
169
|
+
spendingKeyBytes,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
// Hash the shared secret for use as a scalar
|
|
173
|
+
const sharedSecretHash = sha256(sharedSecretPoint)
|
|
174
|
+
|
|
175
|
+
// Compute stealth address: A = Q + hash(S)*G
|
|
176
|
+
const hashTimesG = secp256k1.getPublicKey(sharedSecretHash, true)
|
|
177
|
+
|
|
178
|
+
const viewingKeyPoint = secp256k1.ProjectivePoint.fromHex(viewingKeyBytes)
|
|
179
|
+
const hashTimesGPoint = secp256k1.ProjectivePoint.fromHex(hashTimesG)
|
|
180
|
+
const stealthPoint = viewingKeyPoint.add(hashTimesGPoint)
|
|
181
|
+
const stealthAddressBytes = stealthPoint.toRawBytes(true)
|
|
182
|
+
|
|
183
|
+
// Compute view tag (first byte of hash for efficient scanning)
|
|
184
|
+
const viewTag = sharedSecretHash[0]
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
stealthAddress: {
|
|
188
|
+
address: `0x${bytesToHex(stealthAddressBytes)}` as HexString,
|
|
189
|
+
ephemeralPublicKey: `0x${bytesToHex(ephemeralPublicKey)}` as HexString,
|
|
190
|
+
viewTag,
|
|
191
|
+
},
|
|
192
|
+
sharedSecret: `0x${bytesToHex(sharedSecretHash)}` as HexString,
|
|
193
|
+
}
|
|
194
|
+
} finally {
|
|
195
|
+
secureWipe(ephemeralPrivateKey)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ─── Private Key Derivation ─────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Derive the private key for a secp256k1 stealth address
|
|
203
|
+
*/
|
|
204
|
+
export function deriveSecp256k1StealthPrivateKey(
|
|
205
|
+
stealthAddress: StealthAddress,
|
|
206
|
+
spendingPrivateKey: HexString,
|
|
207
|
+
viewingPrivateKey: HexString,
|
|
208
|
+
): StealthAddressRecovery {
|
|
209
|
+
validateSecp256k1StealthAddress(stealthAddress)
|
|
210
|
+
|
|
211
|
+
if (!isValidPrivateKey(spendingPrivateKey)) {
|
|
212
|
+
throw new ValidationError(
|
|
213
|
+
'must be a valid 32-byte hex string',
|
|
214
|
+
'spendingPrivateKey'
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!isValidPrivateKey(viewingPrivateKey)) {
|
|
219
|
+
throw new ValidationError(
|
|
220
|
+
'must be a valid 32-byte hex string',
|
|
221
|
+
'viewingPrivateKey'
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2))
|
|
226
|
+
const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
|
|
227
|
+
const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
// Compute shared secret: S = p * R (spending private * ephemeral public)
|
|
231
|
+
const sharedSecretPoint = secp256k1.getSharedSecret(
|
|
232
|
+
spendingPrivBytes,
|
|
233
|
+
ephemeralPubBytes,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
// Hash the shared secret
|
|
237
|
+
const sharedSecretHash = sha256(sharedSecretPoint)
|
|
238
|
+
|
|
239
|
+
// Derive stealth private key: q + hash(S) mod n
|
|
240
|
+
const viewingScalar = bytesToBigInt(viewingPrivBytes)
|
|
241
|
+
const hashScalar = bytesToBigInt(sharedSecretHash)
|
|
242
|
+
const stealthPrivateScalar = (viewingScalar + hashScalar) % secp256k1.CURVE.n
|
|
243
|
+
|
|
244
|
+
// Convert back to bytes
|
|
245
|
+
const stealthPrivateKey = bigIntToBytes(stealthPrivateScalar, 32)
|
|
246
|
+
|
|
247
|
+
const result = {
|
|
248
|
+
stealthAddress: stealthAddress.address,
|
|
249
|
+
ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
|
|
250
|
+
privateKey: `0x${bytesToHex(stealthPrivateKey)}` as HexString,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
secureWipe(stealthPrivateKey)
|
|
254
|
+
|
|
255
|
+
return result
|
|
256
|
+
} finally {
|
|
257
|
+
secureWipeAll(spendingPrivBytes, viewingPrivBytes)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ─── Address Checking ───────────────────────────────────────────────────────
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Check if a secp256k1 stealth address belongs to this recipient
|
|
265
|
+
*/
|
|
266
|
+
export function checkSecp256k1StealthAddress(
|
|
267
|
+
stealthAddress: StealthAddress,
|
|
268
|
+
spendingPrivateKey: HexString,
|
|
269
|
+
viewingPrivateKey: HexString,
|
|
270
|
+
): boolean {
|
|
271
|
+
validateSecp256k1StealthAddress(stealthAddress)
|
|
272
|
+
|
|
273
|
+
if (!isValidPrivateKey(spendingPrivateKey)) {
|
|
274
|
+
throw new ValidationError(
|
|
275
|
+
'must be a valid 32-byte hex string',
|
|
276
|
+
'spendingPrivateKey'
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!isValidPrivateKey(viewingPrivateKey)) {
|
|
281
|
+
throw new ValidationError(
|
|
282
|
+
'must be a valid 32-byte hex string',
|
|
283
|
+
'viewingPrivateKey'
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2))
|
|
288
|
+
const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
|
|
289
|
+
const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
// Quick check: compute shared secret and verify view tag first
|
|
293
|
+
const sharedSecretPoint = secp256k1.getSharedSecret(
|
|
294
|
+
spendingPrivBytes,
|
|
295
|
+
ephemeralPubBytes,
|
|
296
|
+
)
|
|
297
|
+
const sharedSecretHash = sha256(sharedSecretPoint)
|
|
298
|
+
|
|
299
|
+
// View tag check (optimization - reject quickly if doesn't match)
|
|
300
|
+
if (sharedSecretHash[0] !== stealthAddress.viewTag) {
|
|
301
|
+
return false
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Full check: derive the expected stealth address
|
|
305
|
+
const viewingScalar = bytesToBigInt(viewingPrivBytes)
|
|
306
|
+
const hashScalar = bytesToBigInt(sharedSecretHash)
|
|
307
|
+
const stealthPrivateScalar = (viewingScalar + hashScalar) % secp256k1.CURVE.n
|
|
308
|
+
|
|
309
|
+
// Compute expected public key from derived private key
|
|
310
|
+
const derivedKeyBytes = bigIntToBytes(stealthPrivateScalar, 32)
|
|
311
|
+
const expectedPubKey = secp256k1.getPublicKey(derivedKeyBytes, true)
|
|
312
|
+
|
|
313
|
+
// Wipe derived key immediately after use
|
|
314
|
+
secureWipe(derivedKeyBytes)
|
|
315
|
+
|
|
316
|
+
// Compare with provided stealth address
|
|
317
|
+
const providedAddress = hexToBytes(stealthAddress.address.slice(2))
|
|
318
|
+
|
|
319
|
+
return bytesToHex(expectedPubKey) === bytesToHex(providedAddress)
|
|
320
|
+
} finally {
|
|
321
|
+
secureWipeAll(spendingPrivBytes, viewingPrivBytes)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ─── Ethereum Address Derivation ────────────────────────────────────────────
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Convert a secp256k1 public key to an Ethereum address
|
|
329
|
+
*
|
|
330
|
+
* Algorithm (EIP-5564 style):
|
|
331
|
+
* 1. Decompress the public key to uncompressed form (65 bytes)
|
|
332
|
+
* 2. Remove the 0x04 prefix (take last 64 bytes)
|
|
333
|
+
* 3. keccak256 hash of the 64 bytes
|
|
334
|
+
* 4. Take the last 20 bytes as the address
|
|
335
|
+
*/
|
|
336
|
+
export function publicKeyToEthAddress(publicKey: HexString): HexString {
|
|
337
|
+
// Remove 0x prefix if present
|
|
338
|
+
const keyHex = publicKey.startsWith('0x') ? publicKey.slice(2) : publicKey
|
|
339
|
+
const keyBytes = hexToBytes(keyHex)
|
|
340
|
+
|
|
341
|
+
let uncompressedBytes: Uint8Array
|
|
342
|
+
|
|
343
|
+
// Check if compressed (33 bytes) or uncompressed (65 bytes)
|
|
344
|
+
if (keyBytes.length === 33) {
|
|
345
|
+
// Decompress using secp256k1
|
|
346
|
+
const point = secp256k1.ProjectivePoint.fromHex(keyBytes)
|
|
347
|
+
uncompressedBytes = point.toRawBytes(false) // false = uncompressed
|
|
348
|
+
} else if (keyBytes.length === 65) {
|
|
349
|
+
uncompressedBytes = keyBytes
|
|
350
|
+
} else {
|
|
351
|
+
throw new ValidationError(
|
|
352
|
+
`invalid public key length: ${keyBytes.length}, expected 33 (compressed) or 65 (uncompressed)`,
|
|
353
|
+
'publicKey'
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Remove the 0x04 prefix (first byte of uncompressed key)
|
|
358
|
+
const pubKeyWithoutPrefix = uncompressedBytes.slice(1)
|
|
359
|
+
|
|
360
|
+
// keccak256 hash
|
|
361
|
+
const hash = keccak_256(pubKeyWithoutPrefix)
|
|
362
|
+
|
|
363
|
+
// Take last 20 bytes
|
|
364
|
+
const addressBytes = hash.slice(-20)
|
|
365
|
+
|
|
366
|
+
// Convert to checksummed address
|
|
367
|
+
return toChecksumAddress(`0x${bytesToHex(addressBytes)}`)
|
|
368
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stealth Address Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared utility functions for stealth address operations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { sha256 } from '@noble/hashes/sha256'
|
|
8
|
+
import { sha512 } from '@noble/hashes/sha512'
|
|
9
|
+
import { keccak_256 } from '@noble/hashes/sha3'
|
|
10
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
|
|
11
|
+
import type { HexString } from '@sip-protocol/types'
|
|
12
|
+
import { ValidationError } from '../errors'
|
|
13
|
+
|
|
14
|
+
// ─── Byte/BigInt Conversion ─────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Convert bytes to bigint (big-endian)
|
|
18
|
+
*/
|
|
19
|
+
export function bytesToBigInt(bytes: Uint8Array): bigint {
|
|
20
|
+
let result = 0n
|
|
21
|
+
for (const byte of bytes) {
|
|
22
|
+
result = (result << 8n) + BigInt(byte)
|
|
23
|
+
}
|
|
24
|
+
return result
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Convert bigint to bytes (big-endian)
|
|
29
|
+
*/
|
|
30
|
+
export function bigIntToBytes(value: bigint, length: number): Uint8Array {
|
|
31
|
+
const bytes = new Uint8Array(length)
|
|
32
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
33
|
+
bytes[i] = Number(value & 0xffn)
|
|
34
|
+
value >>= 8n
|
|
35
|
+
}
|
|
36
|
+
return bytes
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convert bytes to bigint (little-endian, used by ed25519)
|
|
41
|
+
*/
|
|
42
|
+
export function bytesToBigIntLE(bytes: Uint8Array): bigint {
|
|
43
|
+
let result = 0n
|
|
44
|
+
for (let i = bytes.length - 1; i >= 0; i--) {
|
|
45
|
+
result = (result << 8n) + BigInt(bytes[i])
|
|
46
|
+
}
|
|
47
|
+
return result
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Convert bigint to bytes (little-endian, used by ed25519)
|
|
52
|
+
*/
|
|
53
|
+
export function bigIntToBytesLE(value: bigint, length: number): Uint8Array {
|
|
54
|
+
const bytes = new Uint8Array(length)
|
|
55
|
+
for (let i = 0; i < length; i++) {
|
|
56
|
+
bytes[i] = Number(value & 0xffn)
|
|
57
|
+
value >>= 8n
|
|
58
|
+
}
|
|
59
|
+
return bytes
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── Base58 Encoding ────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
/** Base58 alphabet (Bitcoin/Solana standard) */
|
|
65
|
+
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Encode bytes to base58 string
|
|
69
|
+
* Used for Solana address encoding
|
|
70
|
+
*/
|
|
71
|
+
export function bytesToBase58(bytes: Uint8Array): string {
|
|
72
|
+
// Count leading zeros
|
|
73
|
+
let leadingZeros = 0
|
|
74
|
+
for (let i = 0; i < bytes.length && bytes[i] === 0; i++) {
|
|
75
|
+
leadingZeros++
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Convert bytes to bigint
|
|
79
|
+
let value = 0n
|
|
80
|
+
for (const byte of bytes) {
|
|
81
|
+
value = value * 256n + BigInt(byte)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Convert to base58
|
|
85
|
+
let result = ''
|
|
86
|
+
while (value > 0n) {
|
|
87
|
+
const remainder = value % 58n
|
|
88
|
+
value = value / 58n
|
|
89
|
+
result = BASE58_ALPHABET[Number(remainder)] + result
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Add leading '1's for each leading zero byte
|
|
93
|
+
return '1'.repeat(leadingZeros) + result
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Decode base58 string to bytes
|
|
98
|
+
* Used for Solana address validation
|
|
99
|
+
*/
|
|
100
|
+
export function base58ToBytes(str: string): Uint8Array {
|
|
101
|
+
// Count leading '1's (they represent leading zero bytes)
|
|
102
|
+
let leadingOnes = 0
|
|
103
|
+
for (let i = 0; i < str.length && str[i] === '1'; i++) {
|
|
104
|
+
leadingOnes++
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Convert from base58 to bigint
|
|
108
|
+
let value = 0n
|
|
109
|
+
for (const char of str) {
|
|
110
|
+
const index = BASE58_ALPHABET.indexOf(char)
|
|
111
|
+
if (index === -1) {
|
|
112
|
+
throw new ValidationError(`Invalid base58 character: ${char}`, 'address')
|
|
113
|
+
}
|
|
114
|
+
value = value * 58n + BigInt(index)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Convert bigint to bytes
|
|
118
|
+
const bytes: number[] = []
|
|
119
|
+
while (value > 0n) {
|
|
120
|
+
bytes.unshift(Number(value % 256n))
|
|
121
|
+
value = value / 256n
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Add leading zeros
|
|
125
|
+
const result = new Uint8Array(leadingOnes + bytes.length)
|
|
126
|
+
for (let i = 0; i < leadingOnes; i++) {
|
|
127
|
+
result[i] = 0
|
|
128
|
+
}
|
|
129
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
130
|
+
result[leadingOnes + i] = bytes[i]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return result
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── EIP-55 Checksum Address ────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Convert address to EIP-55 checksummed format
|
|
140
|
+
*/
|
|
141
|
+
export function toChecksumAddress(address: string): HexString {
|
|
142
|
+
const addr = address.toLowerCase().replace('0x', '')
|
|
143
|
+
const hash = bytesToHex(keccak_256(new TextEncoder().encode(addr)))
|
|
144
|
+
|
|
145
|
+
let checksummed = '0x'
|
|
146
|
+
for (let i = 0; i < addr.length; i++) {
|
|
147
|
+
if (parseInt(hash[i], 16) >= 8) {
|
|
148
|
+
checksummed += addr[i].toUpperCase()
|
|
149
|
+
} else {
|
|
150
|
+
checksummed += addr[i]
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return checksummed as HexString
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ─── ed25519 Scalar Derivation ──────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* ed25519 curve order (L) - the order of the base point
|
|
161
|
+
*/
|
|
162
|
+
export const ED25519_ORDER = 2n ** 252n + 27742317777372353535851937790883648493n
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Chains that use ed25519 for stealth addresses
|
|
166
|
+
*/
|
|
167
|
+
export const ED25519_CHAINS = ['solana', 'near', 'aptos', 'sui'] as const
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get the scalar from an ed25519 private key
|
|
171
|
+
*
|
|
172
|
+
* ed25519 key derivation:
|
|
173
|
+
* 1. Hash the 32-byte seed with SHA-512 to get 64 bytes
|
|
174
|
+
* 2. First 32 bytes are the scalar (after clamping)
|
|
175
|
+
* 3. Last 32 bytes are used for nonce generation (not needed here)
|
|
176
|
+
*/
|
|
177
|
+
export function getEd25519Scalar(privateKey: Uint8Array): bigint {
|
|
178
|
+
// Hash the private key seed with SHA-512
|
|
179
|
+
const hash = sha512(privateKey)
|
|
180
|
+
|
|
181
|
+
// Take first 32 bytes and clamp as per ed25519 spec
|
|
182
|
+
const scalar = hash.slice(0, 32)
|
|
183
|
+
|
|
184
|
+
// Clamp: clear lowest 3 bits, clear highest bit, set second highest bit
|
|
185
|
+
scalar[0] &= 248
|
|
186
|
+
scalar[31] &= 127
|
|
187
|
+
scalar[31] |= 64
|
|
188
|
+
|
|
189
|
+
// Convert to bigint (little-endian for ed25519)
|
|
190
|
+
return bytesToBigIntLE(scalar)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Re-export for convenience
|
|
194
|
+
export { sha256, sha512, keccak_256, bytesToHex, hexToBytes }
|