@sip-protocol/sdk 0.1.9 → 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 +1854 -378
- package/dist/index.mjs +262 -11114
- package/package.json +23 -14
- package/src/adapters/near-intents.ts +138 -30
- 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,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oracle Attestation Serialization
|
|
3
|
+
*
|
|
4
|
+
* Canonical serialization of attestation messages for signing and verification.
|
|
5
|
+
*
|
|
6
|
+
* @see docs/specs/ORACLE-ATTESTATION.md Section 2.2
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { sha256 } from '@noble/hashes/sha256'
|
|
10
|
+
import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/hashes/utils'
|
|
11
|
+
import type { HexString } from '@sip-protocol/types'
|
|
12
|
+
import type { OracleAttestationMessage } from './types'
|
|
13
|
+
import { ORACLE_DOMAIN, CHAIN_NUMERIC_IDS } from './types'
|
|
14
|
+
import { ValidationError } from '../errors'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Serialize an attestation message to canonical byte format
|
|
18
|
+
*
|
|
19
|
+
* Layout (197 bytes total):
|
|
20
|
+
* - version: 1 byte
|
|
21
|
+
* - chainId: 4 bytes (big-endian)
|
|
22
|
+
* - intentHash: 32 bytes
|
|
23
|
+
* - recipient: 32 bytes
|
|
24
|
+
* - amount: 16 bytes (big-endian u128)
|
|
25
|
+
* - assetId: 32 bytes
|
|
26
|
+
* - txHash: 32 bytes
|
|
27
|
+
* - blockNumber: 8 bytes (big-endian)
|
|
28
|
+
* - blockHash: 32 bytes
|
|
29
|
+
* - timestamp: 8 bytes (big-endian)
|
|
30
|
+
*/
|
|
31
|
+
export function serializeAttestationMessage(
|
|
32
|
+
message: OracleAttestationMessage
|
|
33
|
+
): Uint8Array {
|
|
34
|
+
const buffer = new Uint8Array(197)
|
|
35
|
+
const view = new DataView(buffer.buffer)
|
|
36
|
+
let offset = 0
|
|
37
|
+
|
|
38
|
+
// version (1 byte)
|
|
39
|
+
buffer[offset++] = message.version
|
|
40
|
+
|
|
41
|
+
// chainId (4 bytes, big-endian)
|
|
42
|
+
view.setUint32(offset, message.chainId, false)
|
|
43
|
+
offset += 4
|
|
44
|
+
|
|
45
|
+
// intentHash (32 bytes)
|
|
46
|
+
const intentHashBytes = normalizeToBytes(message.intentHash, 32, 'intentHash')
|
|
47
|
+
buffer.set(intentHashBytes, offset)
|
|
48
|
+
offset += 32
|
|
49
|
+
|
|
50
|
+
// recipient (32 bytes, zero-padded if needed)
|
|
51
|
+
const recipientBytes = normalizeToBytes(message.recipient, 32, 'recipient')
|
|
52
|
+
buffer.set(recipientBytes, offset)
|
|
53
|
+
offset += 32
|
|
54
|
+
|
|
55
|
+
// amount (16 bytes, big-endian u128)
|
|
56
|
+
const amountBytes = bigintToBytes(message.amount, 16)
|
|
57
|
+
buffer.set(amountBytes, offset)
|
|
58
|
+
offset += 16
|
|
59
|
+
|
|
60
|
+
// assetId (32 bytes)
|
|
61
|
+
const assetIdBytes = normalizeToBytes(message.assetId, 32, 'assetId')
|
|
62
|
+
buffer.set(assetIdBytes, offset)
|
|
63
|
+
offset += 32
|
|
64
|
+
|
|
65
|
+
// txHash (32 bytes)
|
|
66
|
+
const txHashBytes = normalizeToBytes(message.txHash, 32, 'txHash')
|
|
67
|
+
buffer.set(txHashBytes, offset)
|
|
68
|
+
offset += 32
|
|
69
|
+
|
|
70
|
+
// blockNumber (8 bytes, big-endian)
|
|
71
|
+
view.setBigUint64(offset, message.blockNumber, false)
|
|
72
|
+
offset += 8
|
|
73
|
+
|
|
74
|
+
// blockHash (32 bytes)
|
|
75
|
+
const blockHashBytes = normalizeToBytes(message.blockHash, 32, 'blockHash')
|
|
76
|
+
buffer.set(blockHashBytes, offset)
|
|
77
|
+
offset += 32
|
|
78
|
+
|
|
79
|
+
// timestamp (8 bytes, big-endian)
|
|
80
|
+
view.setBigUint64(offset, BigInt(message.timestamp), false)
|
|
81
|
+
|
|
82
|
+
return buffer
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Deserialize bytes back to attestation message
|
|
87
|
+
*/
|
|
88
|
+
export function deserializeAttestationMessage(
|
|
89
|
+
bytes: Uint8Array
|
|
90
|
+
): OracleAttestationMessage {
|
|
91
|
+
if (bytes.length !== 197) {
|
|
92
|
+
throw new ValidationError(
|
|
93
|
+
`Invalid attestation message length: ${bytes.length}, expected 197`,
|
|
94
|
+
'bytes'
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset)
|
|
99
|
+
let offset = 0
|
|
100
|
+
|
|
101
|
+
// version
|
|
102
|
+
const version = bytes[offset++]
|
|
103
|
+
|
|
104
|
+
// chainId
|
|
105
|
+
const chainId = view.getUint32(offset, false)
|
|
106
|
+
offset += 4
|
|
107
|
+
|
|
108
|
+
// intentHash
|
|
109
|
+
const intentHash = `0x${bytesToHex(bytes.slice(offset, offset + 32))}` as HexString
|
|
110
|
+
offset += 32
|
|
111
|
+
|
|
112
|
+
// recipient
|
|
113
|
+
const recipient = `0x${bytesToHex(bytes.slice(offset, offset + 32))}` as HexString
|
|
114
|
+
offset += 32
|
|
115
|
+
|
|
116
|
+
// amount
|
|
117
|
+
const amount = bytesToBigint(bytes.slice(offset, offset + 16))
|
|
118
|
+
offset += 16
|
|
119
|
+
|
|
120
|
+
// assetId
|
|
121
|
+
const assetId = `0x${bytesToHex(bytes.slice(offset, offset + 32))}` as HexString
|
|
122
|
+
offset += 32
|
|
123
|
+
|
|
124
|
+
// txHash
|
|
125
|
+
const txHash = `0x${bytesToHex(bytes.slice(offset, offset + 32))}` as HexString
|
|
126
|
+
offset += 32
|
|
127
|
+
|
|
128
|
+
// blockNumber
|
|
129
|
+
const blockNumber = view.getBigUint64(offset, false)
|
|
130
|
+
offset += 8
|
|
131
|
+
|
|
132
|
+
// blockHash
|
|
133
|
+
const blockHash = `0x${bytesToHex(bytes.slice(offset, offset + 32))}` as HexString
|
|
134
|
+
offset += 32
|
|
135
|
+
|
|
136
|
+
// timestamp
|
|
137
|
+
const timestamp = Number(view.getBigUint64(offset, false))
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
version,
|
|
141
|
+
chainId,
|
|
142
|
+
intentHash,
|
|
143
|
+
recipient,
|
|
144
|
+
amount,
|
|
145
|
+
assetId,
|
|
146
|
+
txHash,
|
|
147
|
+
blockNumber,
|
|
148
|
+
blockHash,
|
|
149
|
+
timestamp,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Compute the hash to be signed for an attestation
|
|
155
|
+
*
|
|
156
|
+
* hash = SHA256(domain || serialized_message)
|
|
157
|
+
*/
|
|
158
|
+
export function computeAttestationHash(
|
|
159
|
+
message: OracleAttestationMessage
|
|
160
|
+
): Uint8Array {
|
|
161
|
+
const domain = utf8ToBytes(ORACLE_DOMAIN)
|
|
162
|
+
const messageBytes = serializeAttestationMessage(message)
|
|
163
|
+
|
|
164
|
+
// Concatenate domain and message
|
|
165
|
+
const toHash = new Uint8Array(domain.length + messageBytes.length)
|
|
166
|
+
toHash.set(domain, 0)
|
|
167
|
+
toHash.set(messageBytes, domain.length)
|
|
168
|
+
|
|
169
|
+
return sha256(toHash)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get the numeric chain ID for a chain identifier
|
|
174
|
+
*/
|
|
175
|
+
export function getChainNumericId(chain: string): number {
|
|
176
|
+
const id = CHAIN_NUMERIC_IDS[chain as keyof typeof CHAIN_NUMERIC_IDS]
|
|
177
|
+
if (id === undefined) {
|
|
178
|
+
throw new ValidationError(`Unknown chain: ${chain}`, 'chain')
|
|
179
|
+
}
|
|
180
|
+
return id
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ─── Utility Functions ────────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Convert hex string to bytes, normalizing to specified length
|
|
187
|
+
*/
|
|
188
|
+
function normalizeToBytes(
|
|
189
|
+
hex: HexString,
|
|
190
|
+
length: number,
|
|
191
|
+
field: string
|
|
192
|
+
): Uint8Array {
|
|
193
|
+
const stripped = hex.startsWith('0x') ? hex.slice(2) : hex
|
|
194
|
+
const bytes = hexToBytes(stripped)
|
|
195
|
+
|
|
196
|
+
if (bytes.length === length) {
|
|
197
|
+
return bytes
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (bytes.length > length) {
|
|
201
|
+
throw new ValidationError(
|
|
202
|
+
`${field} is too long: ${bytes.length} bytes, max ${length}`,
|
|
203
|
+
field
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Zero-pad on the left
|
|
208
|
+
const padded = new Uint8Array(length)
|
|
209
|
+
padded.set(bytes, length - bytes.length)
|
|
210
|
+
return padded
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Convert bigint to big-endian bytes
|
|
215
|
+
*/
|
|
216
|
+
function bigintToBytes(value: bigint, length: number): Uint8Array {
|
|
217
|
+
const bytes = new Uint8Array(length)
|
|
218
|
+
let v = value
|
|
219
|
+
|
|
220
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
221
|
+
bytes[i] = Number(v & 0xffn)
|
|
222
|
+
v >>= 8n
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return bytes
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Convert big-endian bytes to bigint
|
|
230
|
+
*/
|
|
231
|
+
function bytesToBigint(bytes: Uint8Array): bigint {
|
|
232
|
+
let result = 0n
|
|
233
|
+
for (const byte of bytes) {
|
|
234
|
+
result = (result << 8n) + BigInt(byte)
|
|
235
|
+
}
|
|
236
|
+
return result
|
|
237
|
+
}
|
|
@@ -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
|
+
}
|