+81
src/pds.js
+81
src/pds.js
···
46
46
}
47
47
}
48
48
49
+
/**
50
+
* Encode a value as CBOR bytes (RFC 8949 deterministic encoding)
51
+
* @param {*} value - Value to encode (null, boolean, number, string, Uint8Array, array, or object)
52
+
* @returns {Uint8Array} CBOR-encoded bytes
53
+
*/
49
54
export function cborEncode(value) {
50
55
const parts = []
51
56
···
146
151
return new Uint8Array(parts)
147
152
}
148
153
154
+
/**
155
+
* Decode CBOR bytes to a JavaScript value
156
+
* @param {Uint8Array} bytes - CBOR-encoded bytes
157
+
* @returns {*} Decoded value
158
+
*/
149
159
export function cborDecode(bytes) {
150
160
let offset = 0
151
161
···
203
213
// === CID GENERATION ===
204
214
// dag-cbor (0x71) + sha-256 (0x12) + 32 bytes
205
215
216
+
/**
217
+
* Create a CIDv1 (dag-cbor + sha-256) from raw bytes
218
+
* @param {Uint8Array} bytes - Content to hash
219
+
* @returns {Promise<Uint8Array>} CID bytes (36 bytes: version + codec + multihash)
220
+
*/
206
221
export async function createCid(bytes) {
207
222
const hash = await crypto.subtle.digest('SHA-256', bytes)
208
223
const hashBytes = new Uint8Array(hash)
···
219
234
return cid
220
235
}
221
236
237
+
/**
238
+
* Convert CID bytes to base32lower string representation
239
+
* @param {Uint8Array} cid - CID bytes
240
+
* @returns {string} Base32lower-encoded CID with 'b' prefix
241
+
*/
222
242
export function cidToString(cid) {
223
243
// base32lower encoding for CIDv1
224
244
return 'b' + base32Encode(cid)
225
245
}
226
246
247
+
/**
248
+
* Encode bytes as base32lower string
249
+
* @param {Uint8Array} bytes - Bytes to encode
250
+
* @returns {string} Base32lower-encoded string
251
+
*/
227
252
export function base32Encode(bytes) {
228
253
const alphabet = 'abcdefghijklmnopqrstuvwxyz234567'
229
254
let result = ''
···
253
278
let lastTimestamp = 0
254
279
let clockId = Math.floor(Math.random() * 1024)
255
280
281
+
/**
282
+
* Generate a timestamp-based ID (TID) for record keys
283
+
* Monotonic within a process, sortable by time
284
+
* @returns {string} 13-character base32-sort encoded TID
285
+
*/
256
286
export function createTid() {
257
287
let timestamp = Date.now() * 1000 // microseconds
258
288
···
282
312
// === P-256 SIGNING ===
283
313
// Web Crypto ECDSA with P-256 curve
284
314
315
+
/**
316
+
* Import a raw P-256 private key for signing
317
+
* @param {Uint8Array} privateKeyBytes - 32-byte raw private key
318
+
* @returns {Promise<CryptoKey>} Web Crypto key handle
319
+
*/
285
320
export async function importPrivateKey(privateKeyBytes) {
286
321
// Validate private key length (P-256 requires exactly 32 bytes)
287
322
if (!(privateKeyBytes instanceof Uint8Array) || privateKeyBytes.length !== 32) {
···
329
364
return bytes
330
365
}
331
366
367
+
/**
368
+
* Sign data with ECDSA P-256, returning low-S normalized signature
369
+
* @param {CryptoKey} privateKey - Web Crypto key from importPrivateKey
370
+
* @param {Uint8Array} data - Data to sign
371
+
* @returns {Promise<Uint8Array>} 64-byte signature (r || s)
372
+
*/
332
373
export async function sign(privateKey, data) {
333
374
const signature = await crypto.subtle.sign(
334
375
{ name: 'ECDSA', hash: 'SHA-256' },
···
354
395
return sig
355
396
}
356
397
398
+
/**
399
+
* Generate a new P-256 key pair
400
+
* @returns {Promise<{privateKey: Uint8Array, publicKey: Uint8Array}>} 32-byte private key, 33-byte compressed public key
401
+
*/
357
402
export async function generateKeyPair() {
358
403
const keyPair = await crypto.subtle.generateKey(
359
404
{ name: 'ECDSA', namedCurve: 'P-256' },
···
395
440
return bytes
396
441
}
397
442
443
+
/**
444
+
* Convert bytes to hexadecimal string
445
+
* @param {Uint8Array} bytes - Bytes to convert
446
+
* @returns {string} Hex string
447
+
*/
398
448
export function bytesToHex(bytes) {
399
449
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')
400
450
}
401
451
452
+
/**
453
+
* Convert hexadecimal string to bytes
454
+
* @param {string} hex - Hex string
455
+
* @returns {Uint8Array} Decoded bytes
456
+
*/
402
457
export function hexToBytes(hex) {
403
458
const bytes = new Uint8Array(hex.length / 2)
404
459
for (let i = 0; i < hex.length; i += 2) {
···
418
473
// Cache for key depths (SHA-256 is expensive)
419
474
const keyDepthCache = new Map()
420
475
476
+
/**
477
+
* Get MST tree depth for a key based on leading zeros in SHA-256 hash
478
+
* @param {string} key - Record key (collection/rkey)
479
+
* @returns {Promise<number>} Tree depth (leading zeros / 2)
480
+
*/
421
481
export async function getKeyDepth(key) {
422
482
// Count leading zeros in SHA-256 hash, divide by 2
423
483
if (keyDepthCache.has(key)) return keyDepthCache.get(key)
···
609
669
610
670
// === CAR FILE BUILDER ===
611
671
672
+
/**
673
+
* Encode integer as unsigned varint
674
+
* @param {number} n - Non-negative integer
675
+
* @returns {Uint8Array} Varint-encoded bytes
676
+
*/
612
677
export function varint(n) {
613
678
const bytes = []
614
679
while (n >= 0x80) {
···
619
684
return new Uint8Array(bytes)
620
685
}
621
686
687
+
/**
688
+
* Convert base32lower CID string to raw bytes
689
+
* @param {string} cidStr - CID string with 'b' prefix
690
+
* @returns {Uint8Array} CID bytes
691
+
*/
622
692
export function cidToBytes(cidStr) {
623
693
// Decode base32lower CID string to bytes
624
694
if (!cidStr.startsWith('b')) throw new Error('expected base32lower CID')
625
695
return base32Decode(cidStr.slice(1))
626
696
}
627
697
698
+
/**
699
+
* Decode base32lower string to bytes
700
+
* @param {string} str - Base32lower-encoded string
701
+
* @returns {Uint8Array} Decoded bytes
702
+
*/
628
703
export function base32Decode(str) {
629
704
const alphabet = 'abcdefghijklmnopqrstuvwxyz234567'
630
705
let bits = 0
···
696
771
return new Uint8Array(parts)
697
772
}
698
773
774
+
/**
775
+
* Build a CAR (Content Addressable aRchive) file
776
+
* @param {string} rootCid - Root CID string
777
+
* @param {Array<{cid: string, data: Uint8Array}>} blocks - Blocks to include
778
+
* @returns {Uint8Array} CAR file bytes
779
+
*/
699
780
export function buildCarFile(rootCid, blocks) {
700
781
const parts = []
701
782