this repo has no description

fix: compute DID from signed operation (include sig in hash)

The PLC spec requires the DID to be derived from the hash of the
complete signed operation, not just the unsigned operation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Changed files
+47 -8
scripts
+12
package-lock.json
··· 1 + { 2 + "name": "cloudflare-pds", 3 + "version": "0.1.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "cloudflare-pds", 9 + "version": "0.1.0" 10 + } 11 + } 12 + }
+35 -8
scripts/setup.js
··· 149 149 return result 150 150 } 151 151 152 - // === CBOR ENCODING (minimal for PLC operations) === 152 + // === CBOR ENCODING (dag-cbor compliant for PLC operations) === 153 + 154 + function cborEncodeKey(key) { 155 + // Encode a string key to CBOR bytes (for sorting) 156 + const bytes = new TextEncoder().encode(key) 157 + const parts = [] 158 + const mt = 3 << 5 // major type 3 = text string 159 + if (bytes.length < 24) { 160 + parts.push(mt | bytes.length) 161 + } else if (bytes.length < 256) { 162 + parts.push(mt | 24, bytes.length) 163 + } else if (bytes.length < 65536) { 164 + parts.push(mt | 25, bytes.length >> 8, bytes.length & 0xff) 165 + } 166 + parts.push(...bytes) 167 + return new Uint8Array(parts) 168 + } 169 + 170 + function compareBytes(a, b) { 171 + // dag-cbor: bytewise lexicographic order of encoded keys 172 + const minLen = Math.min(a.length, b.length) 173 + for (let i = 0; i < minLen; i++) { 174 + if (a[i] !== b[i]) return a[i] - b[i] 175 + } 176 + return a.length - b.length 177 + } 153 178 154 179 function cborEncode(value) { 155 180 const parts = [] ··· 172 197 encodeHead(4, val.length) 173 198 for (const item of val) encode(item) 174 199 } else if (typeof val === 'object') { 175 - const keys = Object.keys(val).sort() 176 - encodeHead(5, keys.length) 177 - for (const key of keys) { 200 + // dag-cbor: sort keys by their CBOR-encoded bytes (length first, then lexicographic) 201 + const keys = Object.keys(val) 202 + const keysSorted = keys.sort((a, b) => compareBytes(cborEncodeKey(a), cborEncodeKey(b))) 203 + encodeHead(5, keysSorted.length) 204 + for (const key of keysSorted) { 178 205 encode(key) 179 206 encode(val[key]) 180 207 } ··· 289 316 } 290 317 291 318 async function deriveDidFromOperation(operation) { 292 - const { sig, ...opWithoutSig } = operation 293 - const encoded = cborEncode(opWithoutSig) 319 + // DID is computed from the FULL operation INCLUDING the signature 320 + const encoded = cborEncode(operation) 294 321 const hash = await sha256(encoded) 295 - // DID is base32 of first 24 bytes of hash 296 - return 'did:plc:' + base32Encode(hash.slice(0, 24)) 322 + // DID is base32 of first 15 bytes of hash (= 24 base32 chars) 323 + return 'did:plc:' + base32Encode(hash.slice(0, 15)) 297 324 } 298 325 299 326 function base32Encode(bytes) {