@sip-protocol/sdk 0.3.2 → 0.4.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.
Files changed (47) hide show
  1. package/dist/browser.d.mts +2 -2
  2. package/dist/browser.d.ts +2 -2
  3. package/dist/browser.js +1019 -146
  4. package/dist/browser.mjs +49 -1
  5. package/dist/chunk-AOZIY3GU.mjs +12995 -0
  6. package/dist/chunk-BCLIX5T2.mjs +12940 -0
  7. package/dist/chunk-FKXPHKYD.mjs +12955 -0
  8. package/dist/chunk-OPQ2GQIO.mjs +13013 -0
  9. package/dist/index-BcWNakUD.d.ts +7990 -0
  10. package/dist/index-BsKY3Hr0.d.mts +7990 -0
  11. package/dist/index.d.mts +2 -2
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +990 -117
  14. package/dist/index.mjs +49 -1
  15. package/package.json +2 -1
  16. package/src/adapters/near-intents.ts +8 -0
  17. package/src/bitcoin/index.ts +51 -0
  18. package/src/bitcoin/silent-payments.ts +865 -0
  19. package/src/bitcoin/taproot.ts +590 -0
  20. package/src/cosmos/ibc-stealth.ts +825 -0
  21. package/src/cosmos/index.ts +83 -0
  22. package/src/cosmos/stealth.ts +487 -0
  23. package/src/index.ts +51 -0
  24. package/src/move/aptos.ts +369 -0
  25. package/src/move/index.ts +35 -0
  26. package/src/move/sui.ts +367 -0
  27. package/src/oracle/types.ts +8 -0
  28. package/src/settlement/backends/direct-chain.ts +8 -0
  29. package/src/stealth.ts +3 -3
  30. package/src/validation.ts +42 -1
  31. package/src/wallet/aptos/adapter.ts +422 -0
  32. package/src/wallet/aptos/index.ts +10 -0
  33. package/src/wallet/aptos/mock.ts +410 -0
  34. package/src/wallet/aptos/types.ts +278 -0
  35. package/src/wallet/bitcoin/adapter.ts +470 -0
  36. package/src/wallet/bitcoin/index.ts +38 -0
  37. package/src/wallet/bitcoin/mock.ts +516 -0
  38. package/src/wallet/bitcoin/types.ts +274 -0
  39. package/src/wallet/cosmos/adapter.ts +484 -0
  40. package/src/wallet/cosmos/index.ts +63 -0
  41. package/src/wallet/cosmos/mock.ts +596 -0
  42. package/src/wallet/cosmos/types.ts +462 -0
  43. package/src/wallet/index.ts +127 -0
  44. package/src/wallet/sui/adapter.ts +471 -0
  45. package/src/wallet/sui/index.ts +10 -0
  46. package/src/wallet/sui/mock.ts +439 -0
  47. package/src/wallet/sui/types.ts +245 -0
@@ -0,0 +1,590 @@
1
+ /**
2
+ * Bitcoin Taproot (BIP-340/341) Implementation
3
+ *
4
+ * Implements Schnorr signatures (BIP-340) and Taproot outputs (BIP-341)
5
+ * for Silent Payments support.
6
+ *
7
+ * References:
8
+ * - BIP-340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
9
+ * - BIP-341: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki
10
+ * - BIP-350: https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki (Bech32m)
11
+ */
12
+
13
+ import { secp256k1, schnorr } from '@noble/curves/secp256k1'
14
+ import { sha256 } from '@noble/hashes/sha256'
15
+ import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
16
+ import type { HexString } from '@sip-protocol/types'
17
+ import { ValidationError } from '../errors'
18
+ import { isValidHex, isValidPrivateKey } from '../validation'
19
+
20
+ /**
21
+ * Taproot output structure
22
+ */
23
+ export interface TaprootOutput {
24
+ /** Tweaked public key (32 bytes x-only) */
25
+ tweakedKey: HexString
26
+ /** Original internal key (32 bytes x-only) */
27
+ internalKey: HexString
28
+ /** Merkle root of tapscript tree (if any) */
29
+ merkleRoot?: HexString
30
+ /** Parity bit for the tweaked key (0 or 1) */
31
+ parity: number
32
+ }
33
+
34
+ /**
35
+ * Tapscript structure (simplified for now)
36
+ */
37
+ export interface TapScript {
38
+ /** Script bytes */
39
+ script: Uint8Array
40
+ /** Leaf version (0xc0 for BIP-341) */
41
+ leafVersion: number
42
+ }
43
+
44
+ /**
45
+ * Bitcoin network types for address encoding
46
+ */
47
+ export type BitcoinNetwork = 'mainnet' | 'testnet' | 'regtest'
48
+
49
+ /**
50
+ * Bech32m character set
51
+ */
52
+ const BECH32_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
53
+
54
+ /**
55
+ * Bech32m generator values
56
+ */
57
+ const BECH32_GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
58
+
59
+ /**
60
+ * Tagged hash for Schnorr signatures and Taproot
61
+ * Implements BIP-340 tagged hash: SHA256(SHA256(tag) || SHA256(tag) || data)
62
+ */
63
+ function taggedHash(tag: string, data: Uint8Array): Uint8Array {
64
+ const tagHash = sha256(new TextEncoder().encode(tag))
65
+ const taggedData = new Uint8Array(tagHash.length * 2 + data.length)
66
+ taggedData.set(tagHash, 0)
67
+ taggedData.set(tagHash, tagHash.length)
68
+ taggedData.set(data, tagHash.length * 2)
69
+ return sha256(taggedData)
70
+ }
71
+
72
+ /**
73
+ * Sign a message using BIP-340 Schnorr signatures
74
+ *
75
+ * @param message - 32-byte message to sign
76
+ * @param privateKey - 32-byte private key
77
+ * @param auxRand - Optional 32-byte auxiliary random data (for deterministic signatures if omitted)
78
+ * @returns 64-byte Schnorr signature
79
+ * @throws {ValidationError} If inputs are invalid
80
+ */
81
+ export function schnorrSign(
82
+ message: Uint8Array,
83
+ privateKey: Uint8Array,
84
+ auxRand?: Uint8Array,
85
+ ): Uint8Array {
86
+ // Validate inputs
87
+ if (message.length !== 32) {
88
+ throw new ValidationError('message must be 32 bytes', 'message')
89
+ }
90
+
91
+ if (privateKey.length !== 32) {
92
+ throw new ValidationError('privateKey must be 32 bytes', 'privateKey')
93
+ }
94
+
95
+ if (auxRand && auxRand.length !== 32) {
96
+ throw new ValidationError('auxRand must be 32 bytes if provided', 'auxRand')
97
+ }
98
+
99
+ // Use @noble/curves schnorr implementation
100
+ // It follows BIP-340 exactly
101
+ return schnorr.sign(message, privateKey, auxRand)
102
+ }
103
+
104
+ /**
105
+ * Verify a BIP-340 Schnorr signature
106
+ *
107
+ * @param signature - 64-byte Schnorr signature
108
+ * @param message - 32-byte message that was signed
109
+ * @param publicKey - 32-byte x-only public key
110
+ * @returns true if signature is valid
111
+ * @throws {ValidationError} If inputs are invalid
112
+ */
113
+ export function schnorrVerify(
114
+ signature: Uint8Array,
115
+ message: Uint8Array,
116
+ publicKey: Uint8Array,
117
+ ): boolean {
118
+ // Validate inputs
119
+ if (signature.length !== 64) {
120
+ throw new ValidationError('signature must be 64 bytes', 'signature')
121
+ }
122
+
123
+ if (message.length !== 32) {
124
+ throw new ValidationError('message must be 32 bytes', 'message')
125
+ }
126
+
127
+ if (publicKey.length !== 32) {
128
+ throw new ValidationError('publicKey must be 32 bytes (x-only)', 'publicKey')
129
+ }
130
+
131
+ try {
132
+ // Use @noble/curves schnorr verification
133
+ return schnorr.verify(signature, message, publicKey)
134
+ } catch {
135
+ return false
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Get x-only public key from a private key
141
+ * Returns the 32-byte x coordinate with even y coordinate
142
+ *
143
+ * @param privateKey - 32-byte private key
144
+ * @returns 32-byte x-only public key
145
+ */
146
+ export function getXOnlyPublicKey(privateKey: Uint8Array): Uint8Array {
147
+ if (privateKey.length !== 32) {
148
+ throw new ValidationError('privateKey must be 32 bytes', 'privateKey')
149
+ }
150
+
151
+ // Get the full public key point
152
+ const publicKey = secp256k1.getPublicKey(privateKey, false)
153
+
154
+ // x-only pubkey is just the x coordinate (first 32 bytes after prefix)
155
+ // @noble/curves returns uncompressed: [0x04, x(32), y(32)]
156
+ return publicKey.slice(1, 33)
157
+ }
158
+
159
+ /**
160
+ * Compute tweaked public key for Taproot
161
+ * Implements BIP-341: P' = P + hash_TapTweak(P || merkle_root) * G
162
+ *
163
+ * @param internalKey - 32-byte x-only internal public key
164
+ * @param merkleRoot - Optional 32-byte merkle root of tapscript tree
165
+ * @returns Tweaked x-only public key and parity
166
+ */
167
+ export function computeTweakedKey(
168
+ internalKey: Uint8Array,
169
+ merkleRoot?: Uint8Array,
170
+ ): { tweakedKey: Uint8Array; parity: number } {
171
+ // Validate inputs
172
+ if (internalKey.length !== 32) {
173
+ throw new ValidationError('internalKey must be 32 bytes (x-only)', 'internalKey')
174
+ }
175
+
176
+ if (merkleRoot && merkleRoot.length !== 32) {
177
+ throw new ValidationError('merkleRoot must be 32 bytes if provided', 'merkleRoot')
178
+ }
179
+
180
+ // Compute tweak: t = hash_TapTweak(P || merkle_root)
181
+ const tweakData = merkleRoot
182
+ ? new Uint8Array([...internalKey, ...merkleRoot])
183
+ : internalKey
184
+ const tweak = taggedHash('TapTweak', tweakData)
185
+
186
+ // Convert tweak to scalar
187
+ const tweakScalar = BigInt('0x' + bytesToHex(tweak)) % secp256k1.CURVE.n
188
+
189
+ // Lift x-only key to full point (assume even y)
190
+ // For x-only keys, we assume y is even (parity = 0)
191
+ const internalPoint = secp256k1.ProjectivePoint.fromHex(
192
+ '02' + bytesToHex(internalKey),
193
+ )
194
+
195
+ // Compute tweaked point: P' = P + t*G
196
+ const tweakPoint = secp256k1.ProjectivePoint.BASE.multiply(tweakScalar)
197
+ const tweakedPoint = internalPoint.add(tweakPoint)
198
+
199
+ // Get x-only representation
200
+ const tweakedKeyBytes = tweakedPoint.toRawBytes(false)
201
+ const xOnly = tweakedKeyBytes.slice(1, 33)
202
+
203
+ // Determine parity (whether y coordinate is even or odd)
204
+ const yCoord = tweakedKeyBytes.slice(33, 65)
205
+ const yBigInt = BigInt('0x' + bytesToHex(yCoord))
206
+ const parity = Number(yBigInt & 1n)
207
+
208
+ return {
209
+ tweakedKey: xOnly,
210
+ parity,
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Create a Taproot output
216
+ * Implements BIP-341 output construction
217
+ *
218
+ * @param internalKey - Internal public key (32 bytes, x-only)
219
+ * @param scripts - Optional tapscripts for the tree
220
+ * @returns Taproot output structure
221
+ */
222
+ export function createTaprootOutput(
223
+ internalKey: Uint8Array,
224
+ scripts?: TapScript[],
225
+ ): TaprootOutput {
226
+ if (internalKey.length !== 32) {
227
+ throw new ValidationError('internalKey must be 32 bytes (x-only)', 'internalKey')
228
+ }
229
+
230
+ // If no scripts, merkle_root is empty
231
+ let merkleRoot: Uint8Array | undefined
232
+
233
+ if (scripts && scripts.length > 0) {
234
+ // For now, we implement simple single-leaf case
235
+ // Full merkle tree construction would be more complex
236
+ if (scripts.length === 1) {
237
+ // Single script: merkle_root = hash_TapLeaf(script)
238
+ const script = scripts[0]
239
+ const leafData = new Uint8Array([
240
+ script.leafVersion,
241
+ script.script.length,
242
+ ...script.script,
243
+ ])
244
+ merkleRoot = taggedHash('TapLeaf', leafData)
245
+ } else {
246
+ // Multiple scripts require merkle tree construction
247
+ // This is a simplified implementation - full BIP-341 would sort and hash pairs
248
+ throw new ValidationError(
249
+ 'Multiple tapscripts not yet implemented - use single script or no scripts',
250
+ 'scripts',
251
+ )
252
+ }
253
+ }
254
+
255
+ // Compute tweaked key
256
+ const { tweakedKey, parity } = computeTweakedKey(internalKey, merkleRoot)
257
+
258
+ return {
259
+ tweakedKey: `0x${bytesToHex(tweakedKey)}` as HexString,
260
+ internalKey: `0x${bytesToHex(internalKey)}` as HexString,
261
+ merkleRoot: merkleRoot ? (`0x${bytesToHex(merkleRoot)}` as HexString) : undefined,
262
+ parity,
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Bech32m polymod for checksum computation
268
+ */
269
+ function bech32Polymod(values: number[]): number {
270
+ let chk = 1
271
+ for (const value of values) {
272
+ const top = chk >> 25
273
+ chk = ((chk & 0x1ffffff) << 5) ^ value
274
+ for (let i = 0; i < 5; i++) {
275
+ if ((top >> i) & 1) {
276
+ chk ^= BECH32_GENERATOR[i]
277
+ }
278
+ }
279
+ }
280
+ return chk
281
+ }
282
+
283
+ /**
284
+ * Expand HRP for bech32m checksum
285
+ */
286
+ function bech32HrpExpand(hrp: string): number[] {
287
+ const result: number[] = []
288
+ for (let i = 0; i < hrp.length; i++) {
289
+ result.push(hrp.charCodeAt(i) >> 5)
290
+ }
291
+ result.push(0)
292
+ for (let i = 0; i < hrp.length; i++) {
293
+ result.push(hrp.charCodeAt(i) & 31)
294
+ }
295
+ return result
296
+ }
297
+
298
+ /**
299
+ * Verify bech32m checksum
300
+ */
301
+ function bech32VerifyChecksum(hrp: string, data: number[]): boolean {
302
+ return bech32Polymod([...bech32HrpExpand(hrp), ...data]) === 0x2bc830a3
303
+ }
304
+
305
+ /**
306
+ * Create bech32m checksum
307
+ */
308
+ function bech32CreateChecksum(hrp: string, data: number[]): number[] {
309
+ const values = [...bech32HrpExpand(hrp), ...data, 0, 0, 0, 0, 0, 0]
310
+ const polymod = bech32Polymod(values) ^ 0x2bc830a3
311
+ const checksum: number[] = []
312
+ for (let i = 0; i < 6; i++) {
313
+ checksum.push((polymod >> (5 * (5 - i))) & 31)
314
+ }
315
+ return checksum
316
+ }
317
+
318
+ /**
319
+ * Convert 8-bit bytes to 5-bit groups for bech32
320
+ */
321
+ function convertBits(
322
+ data: Uint8Array,
323
+ fromBits: number,
324
+ toBits: number,
325
+ pad: boolean,
326
+ ): number[] | null {
327
+ let acc = 0
328
+ let bits = 0
329
+ const result: number[] = []
330
+ const maxv = (1 << toBits) - 1
331
+
332
+ for (const value of data) {
333
+ if (value < 0 || value >> fromBits !== 0) {
334
+ return null
335
+ }
336
+ acc = (acc << fromBits) | value
337
+ bits += fromBits
338
+ while (bits >= toBits) {
339
+ bits -= toBits
340
+ result.push((acc >> bits) & maxv)
341
+ }
342
+ }
343
+
344
+ if (pad) {
345
+ if (bits > 0) {
346
+ result.push((acc << (toBits - bits)) & maxv)
347
+ }
348
+ } else if (bits >= fromBits || (acc << (toBits - bits)) & maxv) {
349
+ return null
350
+ }
351
+
352
+ return result
353
+ }
354
+
355
+ /**
356
+ * Encode Taproot address using Bech32m (BIP-350)
357
+ *
358
+ * @param tweakedKey - 32-byte tweaked x-only public key
359
+ * @param network - Bitcoin network (mainnet, testnet, regtest)
360
+ * @returns Bech32m encoded Taproot address (bc1p... for mainnet)
361
+ */
362
+ export function taprootAddress(
363
+ tweakedKey: Uint8Array,
364
+ network: BitcoinNetwork = 'mainnet',
365
+ ): string {
366
+ if (tweakedKey.length !== 32) {
367
+ throw new ValidationError('tweakedKey must be 32 bytes', 'tweakedKey')
368
+ }
369
+
370
+ // Get HRP (Human Readable Part) based on network
371
+ const hrp = network === 'mainnet' ? 'bc' : network === 'testnet' ? 'tb' : 'bcrt'
372
+
373
+ // Witness version 1 (Taproot)
374
+ const version = 1
375
+
376
+ // Convert to 5-bit groups
377
+ const words = convertBits(tweakedKey, 8, 5, true)
378
+ if (!words) {
379
+ throw new ValidationError('Failed to convert tweaked key to bech32 format', 'tweakedKey')
380
+ }
381
+
382
+ // Prepend version
383
+ const data = [version, ...words]
384
+
385
+ // Create checksum
386
+ const checksum = bech32CreateChecksum(hrp, data)
387
+
388
+ // Encode
389
+ const combined = [...data, ...checksum]
390
+ let result = hrp + '1'
391
+ for (const value of combined) {
392
+ result += BECH32_CHARSET[value]
393
+ }
394
+
395
+ return result
396
+ }
397
+
398
+ /**
399
+ * Decode a Taproot address
400
+ *
401
+ * @param address - Bech32m encoded address (bc1p... or tb1p...)
402
+ * @returns Decoded tweaked key and network
403
+ * @throws {ValidationError} If address is invalid
404
+ */
405
+ export function decodeTaprootAddress(address: string): {
406
+ tweakedKey: Uint8Array
407
+ network: BitcoinNetwork
408
+ } {
409
+ // Validate format
410
+ if (typeof address !== 'string' || address.length < 14 || address.length > 90) {
411
+ throw new ValidationError('Invalid Taproot address format', 'address')
412
+ }
413
+
414
+ const addressLower = address.toLowerCase()
415
+
416
+ // Find separator
417
+ const sepIndex = addressLower.lastIndexOf('1')
418
+ if (sepIndex === -1 || sepIndex + 7 > addressLower.length) {
419
+ throw new ValidationError('Invalid Taproot address: no separator', 'address')
420
+ }
421
+
422
+ // Extract HRP and data
423
+ const hrp = addressLower.slice(0, sepIndex)
424
+ const dataStr = addressLower.slice(sepIndex + 1)
425
+
426
+ // Determine network
427
+ let network: BitcoinNetwork
428
+ if (hrp === 'bc') {
429
+ network = 'mainnet'
430
+ } else if (hrp === 'tb') {
431
+ network = 'testnet'
432
+ } else if (hrp === 'bcrt') {
433
+ network = 'regtest'
434
+ } else {
435
+ throw new ValidationError(`Unknown HRP: ${hrp}`, 'address')
436
+ }
437
+
438
+ // Decode data
439
+ const data: number[] = []
440
+ for (const char of dataStr) {
441
+ const index = BECH32_CHARSET.indexOf(char)
442
+ if (index === -1) {
443
+ throw new ValidationError(`Invalid bech32 character: ${char}`, 'address')
444
+ }
445
+ data.push(index)
446
+ }
447
+
448
+ // Verify checksum
449
+ if (!bech32VerifyChecksum(hrp, data)) {
450
+ throw new ValidationError('Invalid Taproot address checksum', 'address')
451
+ }
452
+
453
+ // Extract witness version and program
454
+ const version = data[0]
455
+ if (version !== 1) {
456
+ throw new ValidationError(`Expected witness version 1 (Taproot), got ${version}`, 'address')
457
+ }
458
+
459
+ // Convert from 5-bit to 8-bit
460
+ const program = convertBits(new Uint8Array(data.slice(1, -6)), 5, 8, false)
461
+ if (!program || program.length !== 32) {
462
+ throw new ValidationError('Invalid Taproot program length', 'address')
463
+ }
464
+
465
+ return {
466
+ tweakedKey: new Uint8Array(program),
467
+ network,
468
+ }
469
+ }
470
+
471
+ /**
472
+ * Create a Taproot key-spend-only output (no scripts)
473
+ * This is the most common case for Silent Payments
474
+ *
475
+ * @param privateKey - 32-byte private key (will be used as internal key)
476
+ * @returns Taproot output and address
477
+ */
478
+ export function createKeySpendOnlyOutput(
479
+ privateKey: HexString,
480
+ network: BitcoinNetwork = 'mainnet',
481
+ ): {
482
+ output: TaprootOutput
483
+ address: string
484
+ internalPrivateKey: HexString
485
+ } {
486
+ if (!isValidPrivateKey(privateKey)) {
487
+ throw new ValidationError('privateKey must be a valid 32-byte hex string', 'privateKey')
488
+ }
489
+
490
+ const privKeyBytes = hexToBytes(privateKey.slice(2))
491
+
492
+ // Get x-only public key
493
+ const internalKey = getXOnlyPublicKey(privKeyBytes)
494
+
495
+ // Create taproot output (no scripts)
496
+ const output = createTaprootOutput(internalKey)
497
+
498
+ // Generate address
499
+ const tweakedKeyBytes = hexToBytes(output.tweakedKey.slice(2))
500
+ const address = taprootAddress(tweakedKeyBytes, network)
501
+
502
+ return {
503
+ output,
504
+ address,
505
+ internalPrivateKey: privateKey,
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Validate a Taproot address format
511
+ *
512
+ * @param address - Address to validate
513
+ * @returns true if valid Taproot address
514
+ */
515
+ export function isValidTaprootAddress(address: string): boolean {
516
+ try {
517
+ decodeTaprootAddress(address)
518
+ return true
519
+ } catch {
520
+ return false
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Sign a message with Schnorr signature using hex inputs
526
+ * Convenience wrapper for schnorrSign
527
+ *
528
+ * @param message - 32-byte message as hex string
529
+ * @param privateKey - 32-byte private key as hex string
530
+ * @param auxRand - Optional 32-byte auxiliary random data as hex string
531
+ * @returns 64-byte signature as hex string
532
+ */
533
+ export function schnorrSignHex(
534
+ message: HexString,
535
+ privateKey: HexString,
536
+ auxRand?: HexString,
537
+ ): HexString {
538
+ if (!isValidHex(message)) {
539
+ throw new ValidationError('message must be a hex string', 'message')
540
+ }
541
+
542
+ if (!isValidPrivateKey(privateKey)) {
543
+ throw new ValidationError('privateKey must be a valid 32-byte hex string', 'privateKey')
544
+ }
545
+
546
+ if (auxRand && !isValidHex(auxRand)) {
547
+ throw new ValidationError('auxRand must be a hex string', 'auxRand')
548
+ }
549
+
550
+ const messageBytes = hexToBytes(message.slice(2))
551
+ const privateKeyBytes = hexToBytes(privateKey.slice(2))
552
+ const auxRandBytes = auxRand ? hexToBytes(auxRand.slice(2)) : undefined
553
+
554
+ const signature = schnorrSign(messageBytes, privateKeyBytes, auxRandBytes)
555
+
556
+ return `0x${bytesToHex(signature)}` as HexString
557
+ }
558
+
559
+ /**
560
+ * Verify a Schnorr signature using hex inputs
561
+ * Convenience wrapper for schnorrVerify
562
+ *
563
+ * @param signature - 64-byte signature as hex string
564
+ * @param message - 32-byte message as hex string
565
+ * @param publicKey - 32-byte x-only public key as hex string
566
+ * @returns true if signature is valid
567
+ */
568
+ export function schnorrVerifyHex(
569
+ signature: HexString,
570
+ message: HexString,
571
+ publicKey: HexString,
572
+ ): boolean {
573
+ if (!isValidHex(signature)) {
574
+ throw new ValidationError('signature must be a hex string', 'signature')
575
+ }
576
+
577
+ if (!isValidHex(message)) {
578
+ throw new ValidationError('message must be a hex string', 'message')
579
+ }
580
+
581
+ if (!isValidHex(publicKey)) {
582
+ throw new ValidationError('publicKey must be a hex string', 'publicKey')
583
+ }
584
+
585
+ const signatureBytes = hexToBytes(signature.slice(2))
586
+ const messageBytes = hexToBytes(message.slice(2))
587
+ const publicKeyBytes = hexToBytes(publicKey.slice(2))
588
+
589
+ return schnorrVerify(signatureBytes, messageBytes, publicKeyBytes)
590
+ }