@sip-protocol/sdk 0.1.8 → 0.2.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/LICENSE +21 -0
- package/dist/browser.d.mts +2 -0
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +12925 -0
- package/dist/browser.mjs +432 -0
- package/dist/chunk-O4Y2ZUDL.mjs +12721 -0
- package/dist/index.d.mts +800 -91
- package/dist/index.d.ts +800 -91
- package/dist/index.js +1855 -376
- package/dist/index.mjs +262 -11111
- package/package.json +23 -14
- package/src/adapters/near-intents.ts +139 -22
- package/src/browser.ts +33 -0
- package/src/commitment.ts +4 -4
- package/src/index.ts +71 -0
- package/src/oracle/index.ts +12 -0
- package/src/oracle/serialization.ts +237 -0
- package/src/oracle/types.ts +257 -0
- package/src/oracle/verification.ts +257 -0
- package/src/proofs/browser-utils.ts +141 -0
- package/src/proofs/browser.ts +884 -0
- package/src/proofs/index.ts +14 -0
- package/src/stealth.ts +868 -12
- package/src/validation.ts +7 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oracle Attestation Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the oracle attestation protocol.
|
|
5
|
+
*
|
|
6
|
+
* @see docs/specs/ORACLE-ATTESTATION.md
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { HexString, ChainId } from '@sip-protocol/types'
|
|
10
|
+
|
|
11
|
+
// ─── Oracle Identity ──────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Oracle identifier (SHA256 hash of public key)
|
|
15
|
+
*/
|
|
16
|
+
export type OracleId = HexString
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Oracle status in the registry
|
|
20
|
+
*/
|
|
21
|
+
export type OracleStatus = 'active' | 'suspended' | 'removed'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Oracle information stored in registry
|
|
25
|
+
*/
|
|
26
|
+
export interface OracleInfo {
|
|
27
|
+
/** Unique oracle identifier (hash of public key) */
|
|
28
|
+
id: OracleId
|
|
29
|
+
|
|
30
|
+
/** Oracle's Ed25519 public key (32 bytes) */
|
|
31
|
+
publicKey: HexString
|
|
32
|
+
|
|
33
|
+
/** Human-readable name */
|
|
34
|
+
name: string
|
|
35
|
+
|
|
36
|
+
/** Supported destination chains */
|
|
37
|
+
supportedChains: ChainId[]
|
|
38
|
+
|
|
39
|
+
/** Oracle endpoint URL */
|
|
40
|
+
endpoint: string
|
|
41
|
+
|
|
42
|
+
/** Registration timestamp (Unix seconds) */
|
|
43
|
+
registeredAt: number
|
|
44
|
+
|
|
45
|
+
/** Current status */
|
|
46
|
+
status: OracleStatus
|
|
47
|
+
|
|
48
|
+
/** Reputation score (0-100) */
|
|
49
|
+
reputation: number
|
|
50
|
+
|
|
51
|
+
/** Staked amount in smallest unit */
|
|
52
|
+
stake: bigint
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── Attestation Message ──────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The canonical message format that oracles sign
|
|
59
|
+
*
|
|
60
|
+
* This structure is serialized deterministically for signing.
|
|
61
|
+
* Total serialized size: 197 bytes
|
|
62
|
+
*/
|
|
63
|
+
export interface OracleAttestationMessage {
|
|
64
|
+
/** Protocol version (current: 1) */
|
|
65
|
+
version: number
|
|
66
|
+
|
|
67
|
+
/** Destination chain numeric ID */
|
|
68
|
+
chainId: number
|
|
69
|
+
|
|
70
|
+
/** Hash of original intent (32 bytes) */
|
|
71
|
+
intentHash: HexString
|
|
72
|
+
|
|
73
|
+
/** Recipient address, normalized to 32 bytes */
|
|
74
|
+
recipient: HexString
|
|
75
|
+
|
|
76
|
+
/** Amount delivered in smallest unit */
|
|
77
|
+
amount: bigint
|
|
78
|
+
|
|
79
|
+
/** Asset identifier hash (32 bytes) */
|
|
80
|
+
assetId: HexString
|
|
81
|
+
|
|
82
|
+
/** Transaction hash on destination chain (32 bytes) */
|
|
83
|
+
txHash: HexString
|
|
84
|
+
|
|
85
|
+
/** Block number containing transaction */
|
|
86
|
+
blockNumber: bigint
|
|
87
|
+
|
|
88
|
+
/** Block hash for finality verification (32 bytes) */
|
|
89
|
+
blockHash: HexString
|
|
90
|
+
|
|
91
|
+
/** Unix timestamp of attestation creation */
|
|
92
|
+
timestamp: number
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── Signatures ───────────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A single oracle's signature on an attestation
|
|
99
|
+
*/
|
|
100
|
+
export interface OracleSignature {
|
|
101
|
+
/** Oracle that produced this signature */
|
|
102
|
+
oracleId: OracleId
|
|
103
|
+
|
|
104
|
+
/** Ed25519 signature (64 bytes) */
|
|
105
|
+
signature: HexString
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Complete attestation with message and signatures
|
|
110
|
+
*/
|
|
111
|
+
export interface SignedOracleAttestation {
|
|
112
|
+
/** The attested message */
|
|
113
|
+
message: OracleAttestationMessage
|
|
114
|
+
|
|
115
|
+
/** Signatures from k-of-n oracles */
|
|
116
|
+
signatures: OracleSignature[]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─── Registry ─────────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Oracle registry containing all registered oracles
|
|
123
|
+
*/
|
|
124
|
+
export interface OracleRegistry {
|
|
125
|
+
/** Registered oracles indexed by ID */
|
|
126
|
+
oracles: Map<OracleId, OracleInfo>
|
|
127
|
+
|
|
128
|
+
/** Required signature threshold (k in k-of-n) */
|
|
129
|
+
threshold: number
|
|
130
|
+
|
|
131
|
+
/** Total number of oracles (n in k-of-n) */
|
|
132
|
+
totalOracles: number
|
|
133
|
+
|
|
134
|
+
/** Registry version for upgrades */
|
|
135
|
+
version: number
|
|
136
|
+
|
|
137
|
+
/** Last update timestamp */
|
|
138
|
+
lastUpdated: number
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Oracle registry configuration
|
|
143
|
+
*/
|
|
144
|
+
export interface OracleRegistryConfig {
|
|
145
|
+
/** Minimum required signatures (default: 3) */
|
|
146
|
+
threshold?: number
|
|
147
|
+
|
|
148
|
+
/** Oracle endpoint timeout in ms (default: 30000) */
|
|
149
|
+
timeout?: number
|
|
150
|
+
|
|
151
|
+
/** Custom registry data (for testing) */
|
|
152
|
+
customOracles?: OracleInfo[]
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ─── Attestation Request ──────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Request for oracle attestation
|
|
159
|
+
*/
|
|
160
|
+
export interface AttestationRequest {
|
|
161
|
+
/** Intent hash to attest */
|
|
162
|
+
intentHash: HexString
|
|
163
|
+
|
|
164
|
+
/** Destination chain */
|
|
165
|
+
destinationChain: ChainId
|
|
166
|
+
|
|
167
|
+
/** Expected recipient address */
|
|
168
|
+
expectedRecipient: HexString
|
|
169
|
+
|
|
170
|
+
/** Expected asset identifier */
|
|
171
|
+
expectedAsset: HexString
|
|
172
|
+
|
|
173
|
+
/** Minimum amount expected */
|
|
174
|
+
minAmount: bigint
|
|
175
|
+
|
|
176
|
+
/** Deadline for fulfillment (Unix timestamp) */
|
|
177
|
+
deadline: number
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Result of attestation request
|
|
182
|
+
*/
|
|
183
|
+
export interface AttestationResult {
|
|
184
|
+
/** Whether attestation was successful */
|
|
185
|
+
success: boolean
|
|
186
|
+
|
|
187
|
+
/** The signed attestation (if successful) */
|
|
188
|
+
attestation?: SignedOracleAttestation
|
|
189
|
+
|
|
190
|
+
/** Error message (if failed) */
|
|
191
|
+
error?: string
|
|
192
|
+
|
|
193
|
+
/** Number of oracles that responded */
|
|
194
|
+
oracleResponses: number
|
|
195
|
+
|
|
196
|
+
/** Number of valid signatures collected */
|
|
197
|
+
validSignatures: number
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─── Verification ─────────────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Result of attestation verification
|
|
204
|
+
*/
|
|
205
|
+
export interface VerificationResult {
|
|
206
|
+
/** Whether verification passed */
|
|
207
|
+
valid: boolean
|
|
208
|
+
|
|
209
|
+
/** Number of valid signatures found */
|
|
210
|
+
validSignatures: number
|
|
211
|
+
|
|
212
|
+
/** Required threshold */
|
|
213
|
+
threshold: number
|
|
214
|
+
|
|
215
|
+
/** List of oracle IDs with valid signatures */
|
|
216
|
+
validOracles: OracleId[]
|
|
217
|
+
|
|
218
|
+
/** Error details (if invalid) */
|
|
219
|
+
errors?: string[]
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Domain separator for attestation signing
|
|
226
|
+
*/
|
|
227
|
+
export const ORACLE_DOMAIN = 'SIP-ORACLE-ATTESTATION-V1'
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Current attestation protocol version
|
|
231
|
+
*/
|
|
232
|
+
export const ATTESTATION_VERSION = 1
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Default signature threshold
|
|
236
|
+
*/
|
|
237
|
+
export const DEFAULT_THRESHOLD = 3
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Default total oracles
|
|
241
|
+
*/
|
|
242
|
+
export const DEFAULT_TOTAL_ORACLES = 5
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Chain numeric IDs for attestation message
|
|
246
|
+
*/
|
|
247
|
+
export const CHAIN_NUMERIC_IDS: Record<ChainId, number> = {
|
|
248
|
+
ethereum: 1,
|
|
249
|
+
polygon: 137,
|
|
250
|
+
arbitrum: 42161,
|
|
251
|
+
optimism: 10,
|
|
252
|
+
base: 8453,
|
|
253
|
+
bitcoin: 0, // Non-standard, SIP-specific (Bitcoin mainnet)
|
|
254
|
+
solana: 501, // Non-standard, SIP-specific
|
|
255
|
+
near: 502, // Non-standard, SIP-specific
|
|
256
|
+
zcash: 503, // Non-standard, SIP-specific
|
|
257
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oracle Attestation Verification
|
|
3
|
+
*
|
|
4
|
+
* Verification of oracle signatures on attestation messages.
|
|
5
|
+
*
|
|
6
|
+
* @see docs/specs/ORACLE-ATTESTATION.md Section 4
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ed25519 } from '@noble/curves/ed25519'
|
|
10
|
+
import { sha256 } from '@noble/hashes/sha256'
|
|
11
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
|
|
12
|
+
import type { HexString } from '@sip-protocol/types'
|
|
13
|
+
import type {
|
|
14
|
+
OracleId,
|
|
15
|
+
OracleInfo,
|
|
16
|
+
OracleRegistry,
|
|
17
|
+
OracleRegistryConfig,
|
|
18
|
+
OracleSignature,
|
|
19
|
+
SignedOracleAttestation,
|
|
20
|
+
VerificationResult,
|
|
21
|
+
} from './types'
|
|
22
|
+
import { DEFAULT_THRESHOLD, DEFAULT_TOTAL_ORACLES } from './types'
|
|
23
|
+
import { computeAttestationHash } from './serialization'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Derive oracle ID from public key
|
|
27
|
+
*
|
|
28
|
+
* OracleId = SHA256(publicKey)
|
|
29
|
+
*/
|
|
30
|
+
export function deriveOracleId(publicKey: HexString | Uint8Array): OracleId {
|
|
31
|
+
const keyBytes = typeof publicKey === 'string'
|
|
32
|
+
? hexToBytes(publicKey.startsWith('0x') ? publicKey.slice(2) : publicKey)
|
|
33
|
+
: publicKey
|
|
34
|
+
|
|
35
|
+
const hash = sha256(keyBytes)
|
|
36
|
+
return `0x${bytesToHex(hash)}` as OracleId
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Verify a signed oracle attestation
|
|
41
|
+
*
|
|
42
|
+
* Checks:
|
|
43
|
+
* 1. Sufficient signatures (>= threshold)
|
|
44
|
+
* 2. All signatures are from registered, active oracles
|
|
45
|
+
* 3. All signatures are valid Ed25519 signatures
|
|
46
|
+
* 4. No duplicate oracles
|
|
47
|
+
*/
|
|
48
|
+
export function verifyAttestation(
|
|
49
|
+
attestation: SignedOracleAttestation,
|
|
50
|
+
registry: OracleRegistry
|
|
51
|
+
): VerificationResult {
|
|
52
|
+
const { message, signatures } = attestation
|
|
53
|
+
const errors: string[] = []
|
|
54
|
+
const validOracles: OracleId[] = []
|
|
55
|
+
|
|
56
|
+
// Check we have enough signatures
|
|
57
|
+
if (signatures.length < registry.threshold) {
|
|
58
|
+
errors.push(
|
|
59
|
+
`Insufficient signatures: ${signatures.length} < ${registry.threshold} required`
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Compute message hash for verification
|
|
64
|
+
const messageHash = computeAttestationHash(message)
|
|
65
|
+
|
|
66
|
+
// Track seen oracles to prevent duplicates
|
|
67
|
+
const seenOracles = new Set<OracleId>()
|
|
68
|
+
let validCount = 0
|
|
69
|
+
|
|
70
|
+
for (const sig of signatures) {
|
|
71
|
+
// Check for duplicate oracles
|
|
72
|
+
if (seenOracles.has(sig.oracleId)) {
|
|
73
|
+
errors.push(`Duplicate signature from oracle: ${sig.oracleId}`)
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
seenOracles.add(sig.oracleId)
|
|
77
|
+
|
|
78
|
+
// Get oracle from registry
|
|
79
|
+
const oracle = registry.oracles.get(sig.oracleId)
|
|
80
|
+
if (!oracle) {
|
|
81
|
+
errors.push(`Unknown oracle: ${sig.oracleId}`)
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (oracle.status !== 'active') {
|
|
86
|
+
errors.push(`Oracle not active: ${sig.oracleId} (status: ${oracle.status})`)
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Verify Ed25519 signature
|
|
91
|
+
try {
|
|
92
|
+
const publicKeyBytes = hexToBytes(
|
|
93
|
+
oracle.publicKey.startsWith('0x')
|
|
94
|
+
? oracle.publicKey.slice(2)
|
|
95
|
+
: oracle.publicKey
|
|
96
|
+
)
|
|
97
|
+
const signatureBytes = hexToBytes(
|
|
98
|
+
sig.signature.startsWith('0x')
|
|
99
|
+
? sig.signature.slice(2)
|
|
100
|
+
: sig.signature
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
const isValid = ed25519.verify(signatureBytes, messageHash, publicKeyBytes)
|
|
104
|
+
|
|
105
|
+
if (isValid) {
|
|
106
|
+
validCount++
|
|
107
|
+
validOracles.push(sig.oracleId)
|
|
108
|
+
} else {
|
|
109
|
+
errors.push(`Invalid signature from oracle: ${sig.oracleId}`)
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
errors.push(`Signature verification error for ${sig.oracleId}: ${e}`)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const valid = validCount >= registry.threshold && errors.length === 0
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
valid,
|
|
120
|
+
validSignatures: validCount,
|
|
121
|
+
threshold: registry.threshold,
|
|
122
|
+
validOracles,
|
|
123
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Verify a single oracle signature
|
|
129
|
+
*/
|
|
130
|
+
export function verifyOracleSignature(
|
|
131
|
+
signature: OracleSignature,
|
|
132
|
+
messageHash: Uint8Array,
|
|
133
|
+
oracle: OracleInfo
|
|
134
|
+
): boolean {
|
|
135
|
+
try {
|
|
136
|
+
const publicKeyBytes = hexToBytes(
|
|
137
|
+
oracle.publicKey.startsWith('0x')
|
|
138
|
+
? oracle.publicKey.slice(2)
|
|
139
|
+
: oracle.publicKey
|
|
140
|
+
)
|
|
141
|
+
const signatureBytes = hexToBytes(
|
|
142
|
+
signature.signature.startsWith('0x')
|
|
143
|
+
? signature.signature.slice(2)
|
|
144
|
+
: signature.signature
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return ed25519.verify(signatureBytes, messageHash, publicKeyBytes)
|
|
148
|
+
} catch {
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Sign an attestation message (for oracle implementations)
|
|
155
|
+
*/
|
|
156
|
+
export function signAttestationMessage(
|
|
157
|
+
messageHash: Uint8Array,
|
|
158
|
+
privateKey: Uint8Array
|
|
159
|
+
): OracleSignature {
|
|
160
|
+
const signature = ed25519.sign(messageHash, privateKey)
|
|
161
|
+
const publicKey = ed25519.getPublicKey(privateKey)
|
|
162
|
+
const oracleId = deriveOracleId(publicKey)
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
oracleId,
|
|
166
|
+
signature: `0x${bytesToHex(signature)}` as HexString,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─── Registry Management ──────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Create a new oracle registry
|
|
174
|
+
*/
|
|
175
|
+
export function createOracleRegistry(
|
|
176
|
+
config: OracleRegistryConfig = {}
|
|
177
|
+
): OracleRegistry {
|
|
178
|
+
const registry: OracleRegistry = {
|
|
179
|
+
oracles: new Map(),
|
|
180
|
+
threshold: config.threshold ?? DEFAULT_THRESHOLD,
|
|
181
|
+
totalOracles: 0,
|
|
182
|
+
version: 1,
|
|
183
|
+
lastUpdated: Date.now(),
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Add custom oracles if provided
|
|
187
|
+
if (config.customOracles) {
|
|
188
|
+
for (const oracle of config.customOracles) {
|
|
189
|
+
registry.oracles.set(oracle.id, oracle)
|
|
190
|
+
}
|
|
191
|
+
registry.totalOracles = config.customOracles.length
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return registry
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Add an oracle to the registry
|
|
199
|
+
*/
|
|
200
|
+
export function addOracle(
|
|
201
|
+
registry: OracleRegistry,
|
|
202
|
+
oracle: OracleInfo
|
|
203
|
+
): void {
|
|
204
|
+
registry.oracles.set(oracle.id, oracle)
|
|
205
|
+
registry.totalOracles = registry.oracles.size
|
|
206
|
+
registry.lastUpdated = Date.now()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Remove an oracle from the registry
|
|
211
|
+
*/
|
|
212
|
+
export function removeOracle(
|
|
213
|
+
registry: OracleRegistry,
|
|
214
|
+
oracleId: OracleId
|
|
215
|
+
): boolean {
|
|
216
|
+
const removed = registry.oracles.delete(oracleId)
|
|
217
|
+
if (removed) {
|
|
218
|
+
registry.totalOracles = registry.oracles.size
|
|
219
|
+
registry.lastUpdated = Date.now()
|
|
220
|
+
}
|
|
221
|
+
return removed
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Update oracle status
|
|
226
|
+
*/
|
|
227
|
+
export function updateOracleStatus(
|
|
228
|
+
registry: OracleRegistry,
|
|
229
|
+
oracleId: OracleId,
|
|
230
|
+
status: OracleInfo['status']
|
|
231
|
+
): boolean {
|
|
232
|
+
const oracle = registry.oracles.get(oracleId)
|
|
233
|
+
if (!oracle) {
|
|
234
|
+
return false
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
oracle.status = status
|
|
238
|
+
registry.lastUpdated = Date.now()
|
|
239
|
+
return true
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get all active oracles
|
|
244
|
+
*/
|
|
245
|
+
export function getActiveOracles(registry: OracleRegistry): OracleInfo[] {
|
|
246
|
+
return Array.from(registry.oracles.values()).filter(
|
|
247
|
+
(oracle) => oracle.status === 'active'
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Check if registry has enough active oracles for threshold
|
|
253
|
+
*/
|
|
254
|
+
export function hasEnoughOracles(registry: OracleRegistry): boolean {
|
|
255
|
+
const activeCount = getActiveOracles(registry).length
|
|
256
|
+
return activeCount >= registry.threshold
|
|
257
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-compatible utilities for proof generation
|
|
3
|
+
*
|
|
4
|
+
* These utilities replace Node.js-specific functions (like Buffer)
|
|
5
|
+
* with browser-compatible alternatives using Web APIs.
|
|
6
|
+
*
|
|
7
|
+
* @module proofs/browser-utils
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convert hex string to Uint8Array (browser-compatible)
|
|
12
|
+
*/
|
|
13
|
+
export function hexToBytes(hex: string): Uint8Array {
|
|
14
|
+
const h = hex.startsWith('0x') ? hex.slice(2) : hex
|
|
15
|
+
if (h.length === 0) return new Uint8Array(0)
|
|
16
|
+
if (h.length % 2 !== 0) {
|
|
17
|
+
throw new Error('Hex string must have even length')
|
|
18
|
+
}
|
|
19
|
+
const bytes = new Uint8Array(h.length / 2)
|
|
20
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
21
|
+
bytes[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16)
|
|
22
|
+
}
|
|
23
|
+
return bytes
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Convert Uint8Array to hex string (browser-compatible)
|
|
28
|
+
*/
|
|
29
|
+
export function bytesToHex(bytes: Uint8Array): string {
|
|
30
|
+
return Array.from(bytes)
|
|
31
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
32
|
+
.join('')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if running in browser environment
|
|
37
|
+
*/
|
|
38
|
+
export function isBrowser(): boolean {
|
|
39
|
+
return typeof window !== 'undefined' && typeof window.document !== 'undefined'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if Web Workers are available
|
|
44
|
+
*/
|
|
45
|
+
export function supportsWebWorkers(): boolean {
|
|
46
|
+
return typeof Worker !== 'undefined'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if SharedArrayBuffer is available (required for some WASM operations)
|
|
51
|
+
*/
|
|
52
|
+
export function supportsSharedArrayBuffer(): boolean {
|
|
53
|
+
try {
|
|
54
|
+
return typeof SharedArrayBuffer !== 'undefined'
|
|
55
|
+
} catch {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get browser info for diagnostics
|
|
62
|
+
*/
|
|
63
|
+
export function getBrowserInfo(): {
|
|
64
|
+
isBrowser: boolean
|
|
65
|
+
supportsWorkers: boolean
|
|
66
|
+
supportsSharedArrayBuffer: boolean
|
|
67
|
+
userAgent: string | null
|
|
68
|
+
} {
|
|
69
|
+
return {
|
|
70
|
+
isBrowser: isBrowser(),
|
|
71
|
+
supportsWorkers: supportsWebWorkers(),
|
|
72
|
+
supportsSharedArrayBuffer: supportsSharedArrayBuffer(),
|
|
73
|
+
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : null,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Load script dynamically (for WASM loading)
|
|
79
|
+
*/
|
|
80
|
+
export function loadScript(src: string): Promise<void> {
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
if (!isBrowser()) {
|
|
83
|
+
reject(new Error('loadScript can only be used in browser'))
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
const script = document.createElement('script')
|
|
87
|
+
script.src = src
|
|
88
|
+
script.onload = () => resolve()
|
|
89
|
+
script.onerror = () => reject(new Error(`Failed to load script: ${src}`))
|
|
90
|
+
document.head.appendChild(script)
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Estimate available memory (approximate, browser-specific)
|
|
96
|
+
*/
|
|
97
|
+
export async function estimateAvailableMemory(): Promise<number | null> {
|
|
98
|
+
if (!isBrowser()) return null
|
|
99
|
+
|
|
100
|
+
// Use Performance API if available (Chrome)
|
|
101
|
+
// @ts-expect-error - Performance.measureUserAgentSpecificMemory is Chrome-specific
|
|
102
|
+
if (typeof performance !== 'undefined' && performance.measureUserAgentSpecificMemory) {
|
|
103
|
+
try {
|
|
104
|
+
// @ts-expect-error - Chrome-specific API
|
|
105
|
+
const result = await performance.measureUserAgentSpecificMemory()
|
|
106
|
+
return result.bytes
|
|
107
|
+
} catch {
|
|
108
|
+
// API not available or permission denied
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Use navigator.deviceMemory if available (Chrome, Opera)
|
|
113
|
+
// @ts-expect-error - deviceMemory is non-standard
|
|
114
|
+
if (typeof navigator !== 'undefined' && navigator.deviceMemory) {
|
|
115
|
+
// Returns approximate device memory in GB
|
|
116
|
+
// @ts-expect-error - deviceMemory is non-standard
|
|
117
|
+
return navigator.deviceMemory * 1024 * 1024 * 1024
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Create a blob URL for worker code (inline worker support)
|
|
125
|
+
*/
|
|
126
|
+
export function createWorkerBlobUrl(code: string): string {
|
|
127
|
+
if (!isBrowser()) {
|
|
128
|
+
throw new Error('createWorkerBlobUrl can only be used in browser')
|
|
129
|
+
}
|
|
130
|
+
const blob = new Blob([code], { type: 'application/javascript' })
|
|
131
|
+
return URL.createObjectURL(blob)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Revoke a blob URL to free memory
|
|
136
|
+
*/
|
|
137
|
+
export function revokeWorkerBlobUrl(url: string): void {
|
|
138
|
+
if (isBrowser() && url.startsWith('blob:')) {
|
|
139
|
+
URL.revokeObjectURL(url)
|
|
140
|
+
}
|
|
141
|
+
}
|