diff --git a/src/pds.js b/src/pds.js index 472e48c..d5f07d8 100644 --- a/src/pds.js +++ b/src/pds.js @@ -118,10 +118,11 @@ function cborEncodeDagCbor(value) { encodeHead(parts, 3, bytes.length) parts.push(...bytes) } else if (val instanceof CID) { - // CID - encode with CBOR tag 42 + 0x00 prefix + // CID links in DAG-CBOR use tag 42 + 0x00 multibase prefix + // The 0x00 prefix indicates "identity" multibase (raw bytes) parts.push(0xd8, CBOR_TAG_CID) encodeHead(parts, 2, val.bytes.length + 1) // +1 for 0x00 prefix - parts.push(0x00) // multibase identity prefix + parts.push(0x00) parts.push(...val.bytes) } else if (val instanceof Uint8Array) { // Regular byte string @@ -132,6 +133,7 @@ function cborEncodeDagCbor(value) { for (const item of val) encode(item) } else if (typeof val === 'object') { // DAG-CBOR: sort keys by length first, then lexicographically + // (differs from standard CBOR which sorts lexicographically only) const keys = Object.keys(val).filter(k => val[k] !== undefined) keys.sort((a, b) => { if (a.length !== b.length) return a.length - b.length @@ -378,11 +380,12 @@ export async function sign(privateKey, data) { ) const sig = new Uint8Array(signature) - // Low-S normalization: if S > N/2, replace S with N - S const r = sig.slice(0, 32) const s = sig.slice(32, 64) const sBigInt = bytesToBigInt(s) + // Low-S normalization: Bitcoin/ATProto require S <= N/2 to prevent + // signature malleability (two valid signatures for same message) if (sBigInt > P256_N_DIV_2) { const newS = P256_N - sBigInt const newSBytes = bigIntToBytes(newS, 32) @@ -499,6 +502,9 @@ export async function getKeyDepth(key) { } } + // MST depth = leading zeros in SHA-256 hash / 2 + // This creates a probabilistic tree where ~50% of keys are at depth 0, + // ~25% at depth 1, etc., giving O(log n) lookups const depth = Math.floor(zeros / 2) keyDepthCache.set(key, depth) return depth