@sip-protocol/sdk 0.1.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/index.d.mts +3640 -0
- package/dist/index.d.ts +3640 -0
- package/dist/index.js +5725 -0
- package/dist/index.mjs +5606 -0
- package/package.json +61 -0
- package/src/adapters/index.ts +19 -0
- package/src/adapters/near-intents.ts +475 -0
- package/src/adapters/oneclick-client.ts +367 -0
- package/src/commitment.ts +470 -0
- package/src/crypto.ts +93 -0
- package/src/errors.ts +471 -0
- package/src/index.ts +369 -0
- package/src/intent.ts +488 -0
- package/src/privacy.ts +382 -0
- package/src/proofs/index.ts +52 -0
- package/src/proofs/interface.ts +228 -0
- package/src/proofs/mock.ts +258 -0
- package/src/proofs/noir.ts +233 -0
- package/src/sip.ts +299 -0
- package/src/solver/index.ts +25 -0
- package/src/solver/mock-solver.ts +278 -0
- package/src/stealth.ts +414 -0
- package/src/validation.ts +401 -0
- package/src/wallet/base-adapter.ts +407 -0
- package/src/wallet/errors.ts +106 -0
- package/src/wallet/ethereum/adapter.ts +655 -0
- package/src/wallet/ethereum/index.ts +48 -0
- package/src/wallet/ethereum/mock.ts +505 -0
- package/src/wallet/ethereum/types.ts +364 -0
- package/src/wallet/index.ts +116 -0
- package/src/wallet/registry.ts +207 -0
- package/src/wallet/solana/adapter.ts +533 -0
- package/src/wallet/solana/index.ts +40 -0
- package/src/wallet/solana/mock.ts +522 -0
- package/src/wallet/solana/types.ts +253 -0
- package/src/zcash/index.ts +53 -0
- package/src/zcash/rpc-client.ts +623 -0
- package/src/zcash/shielded-service.ts +641 -0
package/src/stealth.ts
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stealth Address Generation for SIP Protocol
|
|
3
|
+
*
|
|
4
|
+
* Implements EIP-5564 style stealth addresses using secp256k1.
|
|
5
|
+
* Provides unlinkable one-time addresses for privacy-preserving transactions.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. Recipient generates stealth meta-address (spending key P, viewing key Q)
|
|
9
|
+
* 2. Sender generates ephemeral keypair (r, R = r*G)
|
|
10
|
+
* 3. Sender computes shared secret: S = r * P
|
|
11
|
+
* 4. Sender derives stealth address: A = Q + hash(S)*G
|
|
12
|
+
* 5. Recipient scans: for each R, compute S = p * R, check if A matches
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { secp256k1 } from '@noble/curves/secp256k1'
|
|
16
|
+
import { sha256 } from '@noble/hashes/sha256'
|
|
17
|
+
import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils'
|
|
18
|
+
import type {
|
|
19
|
+
StealthMetaAddress,
|
|
20
|
+
StealthAddress,
|
|
21
|
+
StealthAddressRecovery,
|
|
22
|
+
ChainId,
|
|
23
|
+
HexString,
|
|
24
|
+
} from '@sip-protocol/types'
|
|
25
|
+
import { ValidationError } from './errors'
|
|
26
|
+
import {
|
|
27
|
+
isValidChainId,
|
|
28
|
+
isValidHex,
|
|
29
|
+
isValidCompressedPublicKey,
|
|
30
|
+
isValidPrivateKey,
|
|
31
|
+
} from './validation'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generate a new stealth meta-address keypair
|
|
35
|
+
*
|
|
36
|
+
* @param chain - Target chain for the addresses
|
|
37
|
+
* @param label - Optional human-readable label
|
|
38
|
+
* @returns Stealth meta-address and private keys
|
|
39
|
+
* @throws {ValidationError} If chain is invalid
|
|
40
|
+
*/
|
|
41
|
+
export function generateStealthMetaAddress(
|
|
42
|
+
chain: ChainId,
|
|
43
|
+
label?: string,
|
|
44
|
+
): {
|
|
45
|
+
metaAddress: StealthMetaAddress
|
|
46
|
+
spendingPrivateKey: HexString
|
|
47
|
+
viewingPrivateKey: HexString
|
|
48
|
+
} {
|
|
49
|
+
// Validate chain
|
|
50
|
+
if (!isValidChainId(chain)) {
|
|
51
|
+
throw new ValidationError(
|
|
52
|
+
`invalid chain '${chain}', must be one of: solana, ethereum, near, zcash, polygon, arbitrum, optimism, base`,
|
|
53
|
+
'chain'
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Generate random private keys
|
|
58
|
+
const spendingPrivateKey = randomBytes(32)
|
|
59
|
+
const viewingPrivateKey = randomBytes(32)
|
|
60
|
+
|
|
61
|
+
// Derive public keys
|
|
62
|
+
const spendingKey = secp256k1.getPublicKey(spendingPrivateKey, true)
|
|
63
|
+
const viewingKey = secp256k1.getPublicKey(viewingPrivateKey, true)
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
metaAddress: {
|
|
67
|
+
spendingKey: `0x${bytesToHex(spendingKey)}` as HexString,
|
|
68
|
+
viewingKey: `0x${bytesToHex(viewingKey)}` as HexString,
|
|
69
|
+
chain,
|
|
70
|
+
label,
|
|
71
|
+
},
|
|
72
|
+
spendingPrivateKey: `0x${bytesToHex(spendingPrivateKey)}` as HexString,
|
|
73
|
+
viewingPrivateKey: `0x${bytesToHex(viewingPrivateKey)}` as HexString,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validate a StealthMetaAddress object
|
|
79
|
+
*/
|
|
80
|
+
function validateStealthMetaAddress(
|
|
81
|
+
metaAddress: StealthMetaAddress,
|
|
82
|
+
field: string = 'recipientMetaAddress'
|
|
83
|
+
): void {
|
|
84
|
+
if (!metaAddress || typeof metaAddress !== 'object') {
|
|
85
|
+
throw new ValidationError('must be an object', field)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Validate chain
|
|
89
|
+
if (!isValidChainId(metaAddress.chain)) {
|
|
90
|
+
throw new ValidationError(
|
|
91
|
+
`invalid chain '${metaAddress.chain}'`,
|
|
92
|
+
`${field}.chain`
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Validate spending key
|
|
97
|
+
if (!isValidCompressedPublicKey(metaAddress.spendingKey)) {
|
|
98
|
+
throw new ValidationError(
|
|
99
|
+
'spendingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)',
|
|
100
|
+
`${field}.spendingKey`
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Validate viewing key
|
|
105
|
+
if (!isValidCompressedPublicKey(metaAddress.viewingKey)) {
|
|
106
|
+
throw new ValidationError(
|
|
107
|
+
'viewingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)',
|
|
108
|
+
`${field}.viewingKey`
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Generate a one-time stealth address for a recipient
|
|
115
|
+
*
|
|
116
|
+
* @param recipientMetaAddress - Recipient's published stealth meta-address
|
|
117
|
+
* @returns Stealth address data (address + ephemeral key for publication)
|
|
118
|
+
* @throws {ValidationError} If recipientMetaAddress is invalid
|
|
119
|
+
*/
|
|
120
|
+
export function generateStealthAddress(
|
|
121
|
+
recipientMetaAddress: StealthMetaAddress,
|
|
122
|
+
): {
|
|
123
|
+
stealthAddress: StealthAddress
|
|
124
|
+
sharedSecret: HexString
|
|
125
|
+
} {
|
|
126
|
+
// Validate input
|
|
127
|
+
validateStealthMetaAddress(recipientMetaAddress)
|
|
128
|
+
|
|
129
|
+
// Generate ephemeral keypair
|
|
130
|
+
const ephemeralPrivateKey = randomBytes(32)
|
|
131
|
+
const ephemeralPublicKey = secp256k1.getPublicKey(ephemeralPrivateKey, true)
|
|
132
|
+
|
|
133
|
+
// Parse recipient's keys (remove 0x prefix)
|
|
134
|
+
const spendingKeyBytes = hexToBytes(recipientMetaAddress.spendingKey.slice(2))
|
|
135
|
+
const viewingKeyBytes = hexToBytes(recipientMetaAddress.viewingKey.slice(2))
|
|
136
|
+
|
|
137
|
+
// Compute shared secret: S = r * P (ephemeral private * spending public)
|
|
138
|
+
const sharedSecretPoint = secp256k1.getSharedSecret(
|
|
139
|
+
ephemeralPrivateKey,
|
|
140
|
+
spendingKeyBytes,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
// Hash the shared secret for use as a scalar
|
|
144
|
+
const sharedSecretHash = sha256(sharedSecretPoint)
|
|
145
|
+
|
|
146
|
+
// Compute stealth address: A = Q + hash(S)*G
|
|
147
|
+
// First get hash(S)*G
|
|
148
|
+
const hashTimesG = secp256k1.getPublicKey(sharedSecretHash, true)
|
|
149
|
+
|
|
150
|
+
// Then add to viewing key Q
|
|
151
|
+
const viewingKeyPoint = secp256k1.ProjectivePoint.fromHex(viewingKeyBytes)
|
|
152
|
+
const hashTimesGPoint = secp256k1.ProjectivePoint.fromHex(hashTimesG)
|
|
153
|
+
const stealthPoint = viewingKeyPoint.add(hashTimesGPoint)
|
|
154
|
+
const stealthAddressBytes = stealthPoint.toRawBytes(true)
|
|
155
|
+
|
|
156
|
+
// Compute view tag (first byte of hash for efficient scanning)
|
|
157
|
+
const viewTag = sharedSecretHash[0]
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
stealthAddress: {
|
|
161
|
+
address: `0x${bytesToHex(stealthAddressBytes)}` as HexString,
|
|
162
|
+
ephemeralPublicKey: `0x${bytesToHex(ephemeralPublicKey)}` as HexString,
|
|
163
|
+
viewTag,
|
|
164
|
+
},
|
|
165
|
+
sharedSecret: `0x${bytesToHex(sharedSecretHash)}` as HexString,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Validate a StealthAddress object
|
|
171
|
+
*/
|
|
172
|
+
function validateStealthAddress(
|
|
173
|
+
stealthAddress: StealthAddress,
|
|
174
|
+
field: string = 'stealthAddress'
|
|
175
|
+
): void {
|
|
176
|
+
if (!stealthAddress || typeof stealthAddress !== 'object') {
|
|
177
|
+
throw new ValidationError('must be an object', field)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Validate address (compressed public key)
|
|
181
|
+
if (!isValidCompressedPublicKey(stealthAddress.address)) {
|
|
182
|
+
throw new ValidationError(
|
|
183
|
+
'address must be a valid compressed secp256k1 public key',
|
|
184
|
+
`${field}.address`
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Validate ephemeral public key
|
|
189
|
+
if (!isValidCompressedPublicKey(stealthAddress.ephemeralPublicKey)) {
|
|
190
|
+
throw new ValidationError(
|
|
191
|
+
'ephemeralPublicKey must be a valid compressed secp256k1 public key',
|
|
192
|
+
`${field}.ephemeralPublicKey`
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Validate view tag (0-255)
|
|
197
|
+
if (typeof stealthAddress.viewTag !== 'number' ||
|
|
198
|
+
!Number.isInteger(stealthAddress.viewTag) ||
|
|
199
|
+
stealthAddress.viewTag < 0 ||
|
|
200
|
+
stealthAddress.viewTag > 255) {
|
|
201
|
+
throw new ValidationError(
|
|
202
|
+
'viewTag must be an integer between 0 and 255',
|
|
203
|
+
`${field}.viewTag`
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Derive the private key for a stealth address (for recipient to claim funds)
|
|
210
|
+
*
|
|
211
|
+
* @param stealthAddress - The stealth address to recover
|
|
212
|
+
* @param spendingPrivateKey - Recipient's spending private key
|
|
213
|
+
* @param viewingPrivateKey - Recipient's viewing private key
|
|
214
|
+
* @returns Recovery data including derived private key
|
|
215
|
+
* @throws {ValidationError} If any input is invalid
|
|
216
|
+
*/
|
|
217
|
+
export function deriveStealthPrivateKey(
|
|
218
|
+
stealthAddress: StealthAddress,
|
|
219
|
+
spendingPrivateKey: HexString,
|
|
220
|
+
viewingPrivateKey: HexString,
|
|
221
|
+
): StealthAddressRecovery {
|
|
222
|
+
// Validate stealth address
|
|
223
|
+
validateStealthAddress(stealthAddress)
|
|
224
|
+
|
|
225
|
+
// Validate private keys
|
|
226
|
+
if (!isValidPrivateKey(spendingPrivateKey)) {
|
|
227
|
+
throw new ValidationError(
|
|
228
|
+
'must be a valid 32-byte hex string',
|
|
229
|
+
'spendingPrivateKey'
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!isValidPrivateKey(viewingPrivateKey)) {
|
|
234
|
+
throw new ValidationError(
|
|
235
|
+
'must be a valid 32-byte hex string',
|
|
236
|
+
'viewingPrivateKey'
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Parse keys
|
|
241
|
+
const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2))
|
|
242
|
+
const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
|
|
243
|
+
const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
|
|
244
|
+
|
|
245
|
+
// Compute shared secret: S = p * R (spending private * ephemeral public)
|
|
246
|
+
const sharedSecretPoint = secp256k1.getSharedSecret(
|
|
247
|
+
spendingPrivBytes,
|
|
248
|
+
ephemeralPubBytes,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
// Hash the shared secret
|
|
252
|
+
const sharedSecretHash = sha256(sharedSecretPoint)
|
|
253
|
+
|
|
254
|
+
// Derive stealth private key: q + hash(S) mod n
|
|
255
|
+
// Where q is the viewing private key
|
|
256
|
+
const viewingScalar = bytesToBigInt(viewingPrivBytes)
|
|
257
|
+
const hashScalar = bytesToBigInt(sharedSecretHash)
|
|
258
|
+
const stealthPrivateScalar = (viewingScalar + hashScalar) % secp256k1.CURVE.n
|
|
259
|
+
|
|
260
|
+
// Convert back to bytes
|
|
261
|
+
const stealthPrivateKey = bigIntToBytes(stealthPrivateScalar, 32)
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
stealthAddress: stealthAddress.address,
|
|
265
|
+
ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
|
|
266
|
+
privateKey: `0x${bytesToHex(stealthPrivateKey)}` as HexString,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Check if a stealth address was intended for this recipient
|
|
272
|
+
* Uses view tag for efficient filtering before full computation
|
|
273
|
+
*
|
|
274
|
+
* @param stealthAddress - Stealth address to check
|
|
275
|
+
* @param spendingPrivateKey - Recipient's spending private key
|
|
276
|
+
* @param viewingPrivateKey - Recipient's viewing private key
|
|
277
|
+
* @returns true if this address belongs to the recipient
|
|
278
|
+
* @throws {ValidationError} If any input is invalid
|
|
279
|
+
*/
|
|
280
|
+
export function checkStealthAddress(
|
|
281
|
+
stealthAddress: StealthAddress,
|
|
282
|
+
spendingPrivateKey: HexString,
|
|
283
|
+
viewingPrivateKey: HexString,
|
|
284
|
+
): boolean {
|
|
285
|
+
// Validate stealth address
|
|
286
|
+
validateStealthAddress(stealthAddress)
|
|
287
|
+
|
|
288
|
+
// Validate private keys
|
|
289
|
+
if (!isValidPrivateKey(spendingPrivateKey)) {
|
|
290
|
+
throw new ValidationError(
|
|
291
|
+
'must be a valid 32-byte hex string',
|
|
292
|
+
'spendingPrivateKey'
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!isValidPrivateKey(viewingPrivateKey)) {
|
|
297
|
+
throw new ValidationError(
|
|
298
|
+
'must be a valid 32-byte hex string',
|
|
299
|
+
'viewingPrivateKey'
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Parse keys
|
|
304
|
+
const spendingPrivBytes = hexToBytes(spendingPrivateKey.slice(2))
|
|
305
|
+
const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
|
|
306
|
+
const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
|
|
307
|
+
|
|
308
|
+
// Quick check: compute shared secret and verify view tag first
|
|
309
|
+
const sharedSecretPoint = secp256k1.getSharedSecret(
|
|
310
|
+
spendingPrivBytes,
|
|
311
|
+
ephemeralPubBytes,
|
|
312
|
+
)
|
|
313
|
+
const sharedSecretHash = sha256(sharedSecretPoint)
|
|
314
|
+
|
|
315
|
+
// View tag check (optimization - reject quickly if doesn't match)
|
|
316
|
+
if (sharedSecretHash[0] !== stealthAddress.viewTag) {
|
|
317
|
+
return false
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Full check: derive the expected stealth address
|
|
321
|
+
const viewingScalar = bytesToBigInt(viewingPrivBytes)
|
|
322
|
+
const hashScalar = bytesToBigInt(sharedSecretHash)
|
|
323
|
+
const stealthPrivateScalar = (viewingScalar + hashScalar) % secp256k1.CURVE.n
|
|
324
|
+
|
|
325
|
+
// Compute expected public key from derived private key
|
|
326
|
+
const expectedPubKey = secp256k1.getPublicKey(
|
|
327
|
+
bigIntToBytes(stealthPrivateScalar, 32),
|
|
328
|
+
true,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
// Compare with provided stealth address
|
|
332
|
+
const providedAddress = hexToBytes(stealthAddress.address.slice(2))
|
|
333
|
+
|
|
334
|
+
return bytesToHex(expectedPubKey) === bytesToHex(providedAddress)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Encode a stealth meta-address as a string
|
|
339
|
+
* Format: sip:{chain}:{spendingKey}:{viewingKey}
|
|
340
|
+
*/
|
|
341
|
+
export function encodeStealthMetaAddress(metaAddress: StealthMetaAddress): string {
|
|
342
|
+
return `sip:${metaAddress.chain}:${metaAddress.spendingKey}:${metaAddress.viewingKey}`
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Decode a stealth meta-address from a string
|
|
347
|
+
*
|
|
348
|
+
* @param encoded - Encoded stealth meta-address (format: sip:<chain>:<spendingKey>:<viewingKey>)
|
|
349
|
+
* @returns Decoded StealthMetaAddress
|
|
350
|
+
* @throws {ValidationError} If format is invalid or keys are malformed
|
|
351
|
+
*/
|
|
352
|
+
export function decodeStealthMetaAddress(encoded: string): StealthMetaAddress {
|
|
353
|
+
if (typeof encoded !== 'string') {
|
|
354
|
+
throw new ValidationError('must be a string', 'encoded')
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const parts = encoded.split(':')
|
|
358
|
+
if (parts.length < 4 || parts[0] !== 'sip') {
|
|
359
|
+
throw new ValidationError(
|
|
360
|
+
'invalid format, expected: sip:<chain>:<spendingKey>:<viewingKey>',
|
|
361
|
+
'encoded'
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const [, chain, spendingKey, viewingKey] = parts
|
|
366
|
+
|
|
367
|
+
// Validate chain
|
|
368
|
+
if (!isValidChainId(chain)) {
|
|
369
|
+
throw new ValidationError(
|
|
370
|
+
`invalid chain '${chain}'`,
|
|
371
|
+
'encoded.chain'
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Validate keys
|
|
376
|
+
if (!isValidCompressedPublicKey(spendingKey)) {
|
|
377
|
+
throw new ValidationError(
|
|
378
|
+
'spendingKey must be a valid compressed secp256k1 public key',
|
|
379
|
+
'encoded.spendingKey'
|
|
380
|
+
)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (!isValidCompressedPublicKey(viewingKey)) {
|
|
384
|
+
throw new ValidationError(
|
|
385
|
+
'viewingKey must be a valid compressed secp256k1 public key',
|
|
386
|
+
'encoded.viewingKey'
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
chain: chain as ChainId,
|
|
392
|
+
spendingKey: spendingKey as HexString,
|
|
393
|
+
viewingKey: viewingKey as HexString,
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ─── Utility Functions ──────────────────────────────────────────────────────
|
|
398
|
+
|
|
399
|
+
function bytesToBigInt(bytes: Uint8Array): bigint {
|
|
400
|
+
let result = 0n
|
|
401
|
+
for (const byte of bytes) {
|
|
402
|
+
result = (result << 8n) + BigInt(byte)
|
|
403
|
+
}
|
|
404
|
+
return result
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function bigIntToBytes(value: bigint, length: number): Uint8Array {
|
|
408
|
+
const bytes = new Uint8Array(length)
|
|
409
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
410
|
+
bytes[i] = Number(value & 0xffn)
|
|
411
|
+
value >>= 8n
|
|
412
|
+
}
|
|
413
|
+
return bytes
|
|
414
|
+
}
|