{"version":3,"file":"indexes.mjs","names":["attributes: IndexedAttribute[]","entry: IndexedAttribute"],"sources":["../../src/edv/indexes.ts"],"sourcesContent":["/**\n * Encrypted equality indexes — spec § EDV Usage § Indexes.\n *\n * Client-side HMAC-SHA-256 over attribute name + value. The server\n * never sees plaintext; it matches HMAC bytes verbatim during query.\n */\n\nimport { hmac } from '@noble/hashes/hmac'\nimport { sha256 } from '@noble/hashes/sha2'\n\nimport { b64uEncode } from '../jwe/b64url'\n\nconst textEncoder = new TextEncoder()\n\nexport interface IndexedAttribute {\n  name: string\n  value: string\n  unique?: boolean\n}\n\nexport interface IndexEntry {\n  hmac: { id: string; type: 'Sha256HmacKey2019' }\n  sequence: number\n  attributes: IndexedAttribute[]\n}\n\nexport class IndexComputer {\n  private readonly key: Uint8Array\n  public readonly keyId: string\n\n  public constructor(opts: { key: Uint8Array; keyId: string }) {\n    if (opts.key.length < 32) {\n      throw new Error(`index HMAC key must be at least 32 bytes (got ${opts.key.length})`)\n    }\n    this.key = opts.key\n    this.keyId = opts.keyId\n  }\n\n  public hmac(plaintext: string): string {\n    const digest = hmac(sha256, this.key, textEncoder.encode(plaintext))\n    return b64uEncode(digest)\n  }\n\n  public buildEntry(args: {\n    sequence: number\n    attributes: Record<string, string>\n    uniqueKeys?: string[]\n  }): IndexEntry {\n    const uniqueKeys = new Set(args.uniqueKeys ?? [])\n    const attributes: IndexedAttribute[] = Object.entries(args.attributes).map(([k, v]) => {\n      const entry: IndexedAttribute = {\n        name: this.hmac(k),\n        value: this.hmac(v),\n      }\n      if (uniqueKeys.has(k)) entry.unique = true\n      return entry\n    })\n    return {\n      hmac: { id: this.keyId, type: 'Sha256HmacKey2019' },\n      sequence: args.sequence,\n      attributes,\n    }\n  }\n}\n\n/**\n * Server-side query predicate. Returns true iff some indexed block\n * contains an attribute matching the HMAC name + value pair.\n */\nexport function matchesQuery(\n  indexed: Array<Record<string, unknown>>,\n  hmacName: string,\n  hmacValue: string\n): boolean {\n  for (const entry of indexed) {\n    const attrs = (entry.attributes as Array<{ name?: string; value?: string }>) ?? []\n    for (const a of attrs) {\n      if (a.name === hmacName && a.value === hmacValue) return true\n    }\n  }\n  return false\n}\n"],"mappings":";;;;;;;;;;;AAYA,MAAM,cAAc,IAAI,aAAa;AAcrC,IAAa,gBAAb,MAA2B;CAIzB,AAAO,YAAY,MAA0C;AAC3D,MAAI,KAAK,IAAI,SAAS,GACpB,OAAM,IAAI,MAAM,iDAAiD,KAAK,IAAI,OAAO,GAAG;AAEtF,OAAK,MAAM,KAAK;AAChB,OAAK,QAAQ,KAAK;;CAGpB,AAAO,KAAK,WAA2B;AAErC,SAAO,WADQ,KAAK,QAAQ,KAAK,KAAK,YAAY,OAAO,UAAU,CAAC,CAC3C;;CAG3B,AAAO,WAAW,MAIH;EACb,MAAM,aAAa,IAAI,IAAI,KAAK,cAAc,EAAE,CAAC;EACjD,MAAMA,aAAiC,OAAO,QAAQ,KAAK,WAAW,CAAC,KAAK,CAAC,GAAG,OAAO;GACrF,MAAMC,QAA0B;IAC9B,MAAM,KAAK,KAAK,EAAE;IAClB,OAAO,KAAK,KAAK,EAAE;IACpB;AACD,OAAI,WAAW,IAAI,EAAE,CAAE,OAAM,SAAS;AACtC,UAAO;IACP;AACF,SAAO;GACL,MAAM;IAAE,IAAI,KAAK;IAAO,MAAM;IAAqB;GACnD,UAAU,KAAK;GACf;GACD;;;;;;;AAQL,SAAgB,aACd,SACA,UACA,WACS;AACT,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,QAAS,MAAM,cAA2D,EAAE;AAClF,OAAK,MAAM,KAAK,MACd,KAAI,EAAE,SAAS,YAAY,EAAE,UAAU,UAAW,QAAO;;AAG7D,QAAO"}