+81
src/pds.js
+81
src/pds.js
···
46
}
47
}
48
49
export function cborEncode(value) {
50
const parts = []
51
···
146
return new Uint8Array(parts)
147
}
148
149
export function cborDecode(bytes) {
150
let offset = 0
151
···
203
// === CID GENERATION ===
204
// dag-cbor (0x71) + sha-256 (0x12) + 32 bytes
205
206
export async function createCid(bytes) {
207
const hash = await crypto.subtle.digest('SHA-256', bytes)
208
const hashBytes = new Uint8Array(hash)
···
219
return cid
220
}
221
222
export function cidToString(cid) {
223
// base32lower encoding for CIDv1
224
return 'b' + base32Encode(cid)
225
}
226
227
export function base32Encode(bytes) {
228
const alphabet = 'abcdefghijklmnopqrstuvwxyz234567'
229
let result = ''
···
253
let lastTimestamp = 0
254
let clockId = Math.floor(Math.random() * 1024)
255
256
export function createTid() {
257
let timestamp = Date.now() * 1000 // microseconds
258
···
282
// === P-256 SIGNING ===
283
// Web Crypto ECDSA with P-256 curve
284
285
export async function importPrivateKey(privateKeyBytes) {
286
// Validate private key length (P-256 requires exactly 32 bytes)
287
if (!(privateKeyBytes instanceof Uint8Array) || privateKeyBytes.length !== 32) {
···
329
return bytes
330
}
331
332
export async function sign(privateKey, data) {
333
const signature = await crypto.subtle.sign(
334
{ name: 'ECDSA', hash: 'SHA-256' },
···
354
return sig
355
}
356
357
export async function generateKeyPair() {
358
const keyPair = await crypto.subtle.generateKey(
359
{ name: 'ECDSA', namedCurve: 'P-256' },
···
395
return bytes
396
}
397
398
export function bytesToHex(bytes) {
399
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')
400
}
401
402
export function hexToBytes(hex) {
403
const bytes = new Uint8Array(hex.length / 2)
404
for (let i = 0; i < hex.length; i += 2) {
···
418
// Cache for key depths (SHA-256 is expensive)
419
const keyDepthCache = new Map()
420
421
export async function getKeyDepth(key) {
422
// Count leading zeros in SHA-256 hash, divide by 2
423
if (keyDepthCache.has(key)) return keyDepthCache.get(key)
···
609
610
// === CAR FILE BUILDER ===
611
612
export function varint(n) {
613
const bytes = []
614
while (n >= 0x80) {
···
619
return new Uint8Array(bytes)
620
}
621
622
export function cidToBytes(cidStr) {
623
// Decode base32lower CID string to bytes
624
if (!cidStr.startsWith('b')) throw new Error('expected base32lower CID')
625
return base32Decode(cidStr.slice(1))
626
}
627
628
export function base32Decode(str) {
629
const alphabet = 'abcdefghijklmnopqrstuvwxyz234567'
630
let bits = 0
···
696
return new Uint8Array(parts)
697
}
698
699
export function buildCarFile(rootCid, blocks) {
700
const parts = []
701
···
46
}
47
}
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
+
*/
54
export function cborEncode(value) {
55
const parts = []
56
···
151
return new Uint8Array(parts)
152
}
153
154
+
/**
155
+
* Decode CBOR bytes to a JavaScript value
156
+
* @param {Uint8Array} bytes - CBOR-encoded bytes
157
+
* @returns {*} Decoded value
158
+
*/
159
export function cborDecode(bytes) {
160
let offset = 0
161
···
213
// === CID GENERATION ===
214
// dag-cbor (0x71) + sha-256 (0x12) + 32 bytes
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
+
*/
221
export async function createCid(bytes) {
222
const hash = await crypto.subtle.digest('SHA-256', bytes)
223
const hashBytes = new Uint8Array(hash)
···
234
return cid
235
}
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
+
*/
242
export function cidToString(cid) {
243
// base32lower encoding for CIDv1
244
return 'b' + base32Encode(cid)
245
}
246
247
+
/**
248
+
* Encode bytes as base32lower string
249
+
* @param {Uint8Array} bytes - Bytes to encode
250
+
* @returns {string} Base32lower-encoded string
251
+
*/
252
export function base32Encode(bytes) {
253
const alphabet = 'abcdefghijklmnopqrstuvwxyz234567'
254
let result = ''
···
278
let lastTimestamp = 0
279
let clockId = Math.floor(Math.random() * 1024)
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
+
*/
286
export function createTid() {
287
let timestamp = Date.now() * 1000 // microseconds
288
···
312
// === P-256 SIGNING ===
313
// Web Crypto ECDSA with P-256 curve
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
+
*/
320
export async function importPrivateKey(privateKeyBytes) {
321
// Validate private key length (P-256 requires exactly 32 bytes)
322
if (!(privateKeyBytes instanceof Uint8Array) || privateKeyBytes.length !== 32) {
···
364
return bytes
365
}
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
+
*/
373
export async function sign(privateKey, data) {
374
const signature = await crypto.subtle.sign(
375
{ name: 'ECDSA', hash: 'SHA-256' },
···
395
return sig
396
}
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
+
*/
402
export async function generateKeyPair() {
403
const keyPair = await crypto.subtle.generateKey(
404
{ name: 'ECDSA', namedCurve: 'P-256' },
···
440
return bytes
441
}
442
443
+
/**
444
+
* Convert bytes to hexadecimal string
445
+
* @param {Uint8Array} bytes - Bytes to convert
446
+
* @returns {string} Hex string
447
+
*/
448
export function bytesToHex(bytes) {
449
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')
450
}
451
452
+
/**
453
+
* Convert hexadecimal string to bytes
454
+
* @param {string} hex - Hex string
455
+
* @returns {Uint8Array} Decoded bytes
456
+
*/
457
export function hexToBytes(hex) {
458
const bytes = new Uint8Array(hex.length / 2)
459
for (let i = 0; i < hex.length; i += 2) {
···
473
// Cache for key depths (SHA-256 is expensive)
474
const keyDepthCache = new Map()
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
+
*/
481
export async function getKeyDepth(key) {
482
// Count leading zeros in SHA-256 hash, divide by 2
483
if (keyDepthCache.has(key)) return keyDepthCache.get(key)
···
669
670
// === CAR FILE BUILDER ===
671
672
+
/**
673
+
* Encode integer as unsigned varint
674
+
* @param {number} n - Non-negative integer
675
+
* @returns {Uint8Array} Varint-encoded bytes
676
+
*/
677
export function varint(n) {
678
const bytes = []
679
while (n >= 0x80) {
···
684
return new Uint8Array(bytes)
685
}
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
+
*/
692
export function cidToBytes(cidStr) {
693
// Decode base32lower CID string to bytes
694
if (!cidStr.startsWith('b')) throw new Error('expected base32lower CID')
695
return base32Decode(cidStr.slice(1))
696
}
697
698
+
/**
699
+
* Decode base32lower string to bytes
700
+
* @param {string} str - Base32lower-encoded string
701
+
* @returns {Uint8Array} Decoded bytes
702
+
*/
703
export function base32Decode(str) {
704
const alphabet = 'abcdefghijklmnopqrstuvwxyz234567'
705
let bits = 0
···
771
return new Uint8Array(parts)
772
}
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
+
*/
780
export function buildCarFile(rootCid, blocks) {
781
const parts = []
782