@sip-protocol/sdk 0.1.9 → 0.2.1
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 +1037 -0
- package/dist/chunk-4VJHI66K.mjs +12120 -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 +1137 -266
- package/dist/index.mjs +260 -11114
- package/package.json +20 -14
- package/src/adapters/near-intents.ts +138 -30
- package/src/browser.ts +35 -0
- package/src/commitment.ts +4 -4
- package/src/index.ts +72 -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 +19 -1
- package/src/stealth.ts +868 -12
- package/src/validation.ts +7 -0
|
@@ -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
|
+
}
|