@cardanowall/crypto-core 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/kem.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/kem/x25519.ts","../src/kem/mlkem768x25519.ts"],"names":["x25519","XWing"],"mappings":"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","file":"kem.cjs","sourcesContent":["import { x25519 } from '@noble/curves/ed25519.js';\n\n// RFC 7748 §6.1 contributory-behaviour rejection: a small-order (low-order)\n// Montgomery `u` coordinate makes the X25519 shared secret all-zero, which\n// @noble/curves refuses with `Error: invalid private or public key received`.\n// We rethrow that as a *typed* error so callers can distinguish a structurally\n// valid-but-malicious peer public key (a property of attacker-supplied wire\n// data — trial-decrypt MUST treat the slot as a non-match, not crash) from\n// genuine caller misuse such as a wrong-length key (which @noble raises as a\n// RangeError and which we deliberately let propagate untouched).\nexport class X25519LowOrderPointError extends Error {\n readonly code = 'X25519_LOW_ORDER_POINT' as const;\n constructor(options?: { cause?: unknown }) {\n super('x25519 ECDH rejected: peer public key is a small-order point', options);\n this.name = 'X25519LowOrderPointError';\n }\n}\n\n// @noble/curves v2 signals a small-order/all-zero shared secret with this exact\n// message. Matching on it (rather than the broad Error class) keeps unrelated\n// failures — e.g. a future internal assertion — surfacing as themselves.\nconst NOBLE_LOW_ORDER_MESSAGE = 'invalid private or public key received';\n\nexport interface X25519KeyPair {\n readonly secretKey: Uint8Array;\n readonly publicKey: Uint8Array;\n}\n\nexport interface X25519PublicKeyOpts {\n readonly secretKey: Uint8Array;\n}\n\nexport interface X25519EcdhOpts {\n readonly secretKey: Uint8Array;\n readonly theirPublicKey: Uint8Array;\n}\n\nexport function x25519Keygen(): X25519KeyPair {\n return x25519.keygen();\n}\n\nexport function x25519PublicKey(opts: X25519PublicKeyOpts): Uint8Array {\n return x25519.getPublicKey(opts.secretKey);\n}\n\nexport function x25519Ecdh(opts: X25519EcdhOpts): Uint8Array {\n try {\n return x25519.getSharedSecret(opts.secretKey, opts.theirPublicKey);\n } catch (e) {\n // Translate ONLY the contributory-check rejection into our typed error.\n // A wrong-length key throws a RangeError from @noble's length assertion;\n // that is caller misuse, not malicious wire data, so it must propagate.\n if (e instanceof Error && e.message === NOBLE_LOW_ORDER_MESSAGE) {\n throw new X25519LowOrderPointError({ cause: e });\n }\n throw e;\n }\n}\n","import { XWing } from '@noble/post-quantum/hybrid.js';\n\n// X-Wing (ML-KEM-768 + X25519) hybrid KEM per draft-connolly-cfrg-xwing-kem-06.\n// `XWing` is @noble/post-quantum's alias for `ml_kem768_x25519`. We expose it\n// through opts-object wrappers that pin the wire lengths and map noble's field\n// names onto the project's vocabulary.\n//\n// Unlike the bare X25519 KEM, there is no contributory-behaviour rejection to\n// translate: X-Wing combines the ML-KEM and X25519 shared secrets through a\n// SHA3-256 combiner that also binds the X25519 ephemeral and recipient public\n// keys, and ML-KEM's implicit rejection already yields a constant-work\n// pseudorandom secret on a malformed ciphertext. Decapsulation therefore never\n// throws on attacker-supplied wire data — a wrong shared secret is the correct,\n// indistinguishable failure mode, and callers MUST treat it as a non-match\n// rather than expecting an exception.\n\nexport const MLKEM768X25519_PUBLIC_KEY_LENGTH = 1216 as const;\nexport const MLKEM768X25519_ENC_LENGTH = 1120 as const;\nexport const MLKEM768X25519_SHARED_SECRET_LENGTH = 32 as const;\nexport const MLKEM768X25519_SEED_LENGTH = 32 as const;\nexport const MLKEM768X25519_ESEED_LENGTH = 64 as const;\n\nexport interface Mlkem768X25519KeyPair {\n // The 32-byte root seed IS the secret key in draft-06: the ML-KEM coins and\n // the X25519 scalar are re-expanded from it via SHAKE-256 at decapsulation.\n readonly secretSeed: Uint8Array;\n readonly publicKey: Uint8Array;\n}\n\nexport interface Mlkem768X25519EncapsulateOpts {\n readonly publicKey: Uint8Array;\n // Optional 64-byte encapsulation randomness (msgRand). When supplied the\n // ciphertext and shared secret are fully deterministic; a 32-byte value is\n // rejected by noble, so we pin the length here too.\n readonly eseed?: Uint8Array;\n}\n\nexport interface Mlkem768X25519Encapsulation {\n readonly enc: Uint8Array;\n readonly ss: Uint8Array;\n}\n\nexport interface Mlkem768X25519DecapsulateOpts {\n readonly secretSeed: Uint8Array;\n readonly enc: Uint8Array;\n}\n\nexport function mlkem768x25519Keygen(seed: Uint8Array): Mlkem768X25519KeyPair {\n if (seed.length !== MLKEM768X25519_SEED_LENGTH) {\n throw new Error(\n `mlkem768x25519 seed must be ${MLKEM768X25519_SEED_LENGTH} bytes, got ${seed.length}`,\n );\n }\n const { secretKey, publicKey } = XWing.keygen(seed);\n return { secretSeed: secretKey, publicKey };\n}\n\nexport function mlkem768x25519Encapsulate(\n opts: Mlkem768X25519EncapsulateOpts,\n): Mlkem768X25519Encapsulation {\n if (opts.publicKey.length !== MLKEM768X25519_PUBLIC_KEY_LENGTH) {\n throw new Error(\n `mlkem768x25519 public key must be ${MLKEM768X25519_PUBLIC_KEY_LENGTH} bytes, got ${opts.publicKey.length}`,\n );\n }\n if (opts.eseed !== undefined && opts.eseed.length !== MLKEM768X25519_ESEED_LENGTH) {\n throw new Error(\n `mlkem768x25519 eseed must be ${MLKEM768X25519_ESEED_LENGTH} bytes, got ${opts.eseed.length}`,\n );\n }\n const { cipherText, sharedSecret } = XWing.encapsulate(opts.publicKey, opts.eseed);\n return { enc: cipherText, ss: sharedSecret };\n}\n\nexport function mlkem768x25519Decapsulate(opts: Mlkem768X25519DecapsulateOpts): Uint8Array {\n // Pre-check both lengths before calling noble: decapsulation must perform a\n // constant amount of work for any caller-supplied ciphertext (implicit\n // rejection), which requires the inputs to be the exact expected sizes.\n if (opts.secretSeed.length !== MLKEM768X25519_SEED_LENGTH) {\n throw new Error(\n `mlkem768x25519 secret seed must be ${MLKEM768X25519_SEED_LENGTH} bytes, got ${opts.secretSeed.length}`,\n );\n }\n if (opts.enc.length !== MLKEM768X25519_ENC_LENGTH) {\n throw new Error(\n `mlkem768x25519 enc must be ${MLKEM768X25519_ENC_LENGTH} bytes, got ${opts.enc.length}`,\n );\n }\n // noble's signature is decapsulate(cipherText, secretKey) — ciphertext first.\n return XWing.decapsulate(opts.enc, opts.secretSeed);\n}\n"]}
1
+ {"version":3,"sources":["../src/kem/x25519.ts","../src/kem/mlkem768x25519.ts"],"names":["x25519","XWing"],"mappings":";;;;;;AAUO,IAAM,wBAAA,GAAN,cAAuC,KAAA,CAAM;AAAA,EACzC,IAAA,GAAO,wBAAA;AAAA,EAChB,YAAY,OAAA,EAA+B;AACzC,IAAA,KAAA,CAAM,gEAAgE,OAAO,CAAA;AAC7E,IAAA,IAAA,CAAK,IAAA,GAAO,0BAAA;AAAA,EACd;AACF;AAKA,IAAM,uBAAA,GAA0B,wCAAA;AAgBzB,SAAS,YAAA,GAA8B;AAC5C,EAAA,OAAOA,kBAAO,MAAA,EAAO;AACvB;AAEO,SAAS,gBAAgB,IAAA,EAAuC;AACrE,EAAA,OAAOA,iBAAA,CAAO,YAAA,CAAa,IAAA,CAAK,SAAS,CAAA;AAC3C;AAEO,SAAS,WAAW,IAAA,EAAkC;AAC3D,EAAA,IAAI;AACF,IAAA,OAAOA,iBAAA,CAAO,eAAA,CAAgB,IAAA,CAAK,SAAA,EAAW,KAAK,cAAc,CAAA;AAAA,EACnE,SAAS,CAAA,EAAG;AAIV,IAAA,IAAI,CAAA,YAAa,KAAA,IAAS,CAAA,CAAE,OAAA,KAAY,uBAAA,EAAyB;AAC/D,MAAA,MAAM,IAAI,wBAAA,CAAyB,EAAE,KAAA,EAAO,GAAG,CAAA;AAAA,IACjD;AACA,IAAA,MAAM,CAAA;AAAA,EACR;AACF;ACzCO,IAAM,gCAAA,GAAmC;AACzC,IAAM,yBAAA,GAA4B;AAClC,IAAM,mCAAA,GAAsC;AAC5C,IAAM,0BAAA,GAA6B;AACnC,IAAM,2BAAA,GAA8B;AA6BpC,SAAS,qBAAqB,IAAA,EAAyC;AAC5E,EAAA,IAAI,IAAA,CAAK,WAAW,0BAAA,EAA4B;AAC9C,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,4BAAA,EAA+B,0BAA0B,CAAA,YAAA,EAAe,IAAA,CAAK,MAAM,CAAA;AAAA,KACrF;AAAA,EACF;AACA,EAAA,MAAM,EAAE,SAAA,EAAW,SAAA,EAAU,GAAIC,eAAA,CAAM,OAAO,IAAI,CAAA;AAClD,EAAA,OAAO,EAAE,UAAA,EAAY,SAAA,EAAW,SAAA,EAAU;AAC5C;AAEO,SAAS,0BACd,IAAA,EAC6B;AAC7B,EAAA,IAAI,IAAA,CAAK,SAAA,CAAU,MAAA,KAAW,gCAAA,EAAkC;AAC9D,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,kCAAA,EAAqC,gCAAgC,CAAA,YAAA,EAAe,IAAA,CAAK,UAAU,MAAM,CAAA;AAAA,KAC3G;AAAA,EACF;AACA,EAAA,IAAI,KAAK,KAAA,KAAU,MAAA,IAAa,IAAA,CAAK,KAAA,CAAM,WAAW,2BAAA,EAA6B;AACjF,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,6BAAA,EAAgC,2BAA2B,CAAA,YAAA,EAAe,IAAA,CAAK,MAAM,MAAM,CAAA;AAAA,KAC7F;AAAA,EACF;AACA,EAAA,MAAM,EAAE,YAAY,YAAA,EAAa,GAAIA,gBAAM,WAAA,CAAY,IAAA,CAAK,SAAA,EAAW,IAAA,CAAK,KAAK,CAAA;AACjF,EAAA,OAAO,EAAE,GAAA,EAAK,UAAA,EAAY,EAAA,EAAI,YAAA,EAAa;AAC7C;AAEO,SAAS,0BAA0B,IAAA,EAAiD;AAIzF,EAAA,IAAI,IAAA,CAAK,UAAA,CAAW,MAAA,KAAW,0BAAA,EAA4B;AACzD,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,mCAAA,EAAsC,0BAA0B,CAAA,YAAA,EAAe,IAAA,CAAK,WAAW,MAAM,CAAA;AAAA,KACvG;AAAA,EACF;AACA,EAAA,IAAI,IAAA,CAAK,GAAA,CAAI,MAAA,KAAW,yBAAA,EAA2B;AACjD,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,2BAAA,EAA8B,yBAAyB,CAAA,YAAA,EAAe,IAAA,CAAK,IAAI,MAAM,CAAA;AAAA,KACvF;AAAA,EACF;AAEA,EAAA,OAAOA,eAAA,CAAM,WAAA,CAAY,IAAA,CAAK,GAAA,EAAK,KAAK,UAAU,CAAA;AACpD","file":"kem.cjs","sourcesContent":["import { x25519 } from '@noble/curves/ed25519.js';\n\n// RFC 7748 §6.1 contributory-behaviour rejection: a small-order (low-order)\n// Montgomery `u` coordinate makes the X25519 shared secret all-zero, which\n// @noble/curves refuses with `Error: invalid private or public key received`.\n// We rethrow that as a *typed* error so callers can distinguish a structurally\n// valid-but-malicious peer public key (a property of attacker-supplied wire\n// data — trial-decrypt MUST treat the slot as a non-match, not crash) from\n// genuine caller misuse such as a wrong-length key (which @noble raises as a\n// RangeError and which we deliberately let propagate untouched).\nexport class X25519LowOrderPointError extends Error {\n readonly code = 'X25519_LOW_ORDER_POINT' as const;\n constructor(options?: { cause?: unknown }) {\n super('x25519 ECDH rejected: peer public key is a small-order point', options);\n this.name = 'X25519LowOrderPointError';\n }\n}\n\n// @noble/curves v2 signals a small-order/all-zero shared secret with this exact\n// message. Matching on it (rather than the broad Error class) keeps unrelated\n// failures — e.g. a future internal assertion — surfacing as themselves.\nconst NOBLE_LOW_ORDER_MESSAGE = 'invalid private or public key received';\n\nexport interface X25519KeyPair {\n readonly secretKey: Uint8Array;\n readonly publicKey: Uint8Array;\n}\n\nexport interface X25519PublicKeyOpts {\n readonly secretKey: Uint8Array;\n}\n\nexport interface X25519EcdhOpts {\n readonly secretKey: Uint8Array;\n readonly theirPublicKey: Uint8Array;\n}\n\nexport function x25519Keygen(): X25519KeyPair {\n return x25519.keygen();\n}\n\nexport function x25519PublicKey(opts: X25519PublicKeyOpts): Uint8Array {\n return x25519.getPublicKey(opts.secretKey);\n}\n\nexport function x25519Ecdh(opts: X25519EcdhOpts): Uint8Array {\n try {\n return x25519.getSharedSecret(opts.secretKey, opts.theirPublicKey);\n } catch (e) {\n // Translate ONLY the contributory-check rejection into our typed error.\n // A wrong-length key throws a RangeError from @noble's length assertion;\n // that is caller misuse, not malicious wire data, so it must propagate.\n if (e instanceof Error && e.message === NOBLE_LOW_ORDER_MESSAGE) {\n throw new X25519LowOrderPointError({ cause: e });\n }\n throw e;\n }\n}\n","import { XWing } from '@noble/post-quantum/hybrid.js';\n\n// X-Wing (ML-KEM-768 + X25519) hybrid KEM per draft-connolly-cfrg-xwing-kem-10.\n// `XWing` is @noble/post-quantum's alias for `ml_kem768_x25519`. We expose it\n// through opts-object wrappers that pin the wire lengths and map noble's field\n// names onto the project's vocabulary.\n//\n// Unlike the bare X25519 KEM, there is no contributory-behaviour rejection to\n// translate: X-Wing combines the ML-KEM and X25519 shared secrets through a\n// SHA3-256 combiner that also binds the X25519 ephemeral and recipient public\n// keys, and ML-KEM's implicit rejection already yields a constant-work\n// pseudorandom secret on a malformed ciphertext. Decapsulation therefore never\n// throws on attacker-supplied wire data — a wrong shared secret is the correct,\n// indistinguishable failure mode, and callers MUST treat it as a non-match\n// rather than expecting an exception.\n\nexport const MLKEM768X25519_PUBLIC_KEY_LENGTH = 1216 as const;\nexport const MLKEM768X25519_ENC_LENGTH = 1120 as const;\nexport const MLKEM768X25519_SHARED_SECRET_LENGTH = 32 as const;\nexport const MLKEM768X25519_SEED_LENGTH = 32 as const;\nexport const MLKEM768X25519_ESEED_LENGTH = 64 as const;\n\nexport interface Mlkem768X25519KeyPair {\n // The 32-byte root seed IS the secret key: the ML-KEM coins and the X25519\n // scalar are re-expanded from it via SHAKE-256 at decapsulation. (Later X-Wing\n // drafts also define an optional expanded decapsulation-key form; we keep the\n // seed-only key, which the draft-10 Appendix C vectors still pin.)\n readonly secretSeed: Uint8Array;\n readonly publicKey: Uint8Array;\n}\n\nexport interface Mlkem768X25519EncapsulateOpts {\n readonly publicKey: Uint8Array;\n // Optional 64-byte encapsulation randomness (msgRand). When supplied the\n // ciphertext and shared secret are fully deterministic; a 32-byte value is\n // rejected by noble, so we pin the length here too.\n readonly eseed?: Uint8Array;\n}\n\nexport interface Mlkem768X25519Encapsulation {\n readonly enc: Uint8Array;\n readonly ss: Uint8Array;\n}\n\nexport interface Mlkem768X25519DecapsulateOpts {\n readonly secretSeed: Uint8Array;\n readonly enc: Uint8Array;\n}\n\nexport function mlkem768x25519Keygen(seed: Uint8Array): Mlkem768X25519KeyPair {\n if (seed.length !== MLKEM768X25519_SEED_LENGTH) {\n throw new Error(\n `mlkem768x25519 seed must be ${MLKEM768X25519_SEED_LENGTH} bytes, got ${seed.length}`,\n );\n }\n const { secretKey, publicKey } = XWing.keygen(seed);\n return { secretSeed: secretKey, publicKey };\n}\n\nexport function mlkem768x25519Encapsulate(\n opts: Mlkem768X25519EncapsulateOpts,\n): Mlkem768X25519Encapsulation {\n if (opts.publicKey.length !== MLKEM768X25519_PUBLIC_KEY_LENGTH) {\n throw new Error(\n `mlkem768x25519 public key must be ${MLKEM768X25519_PUBLIC_KEY_LENGTH} bytes, got ${opts.publicKey.length}`,\n );\n }\n if (opts.eseed !== undefined && opts.eseed.length !== MLKEM768X25519_ESEED_LENGTH) {\n throw new Error(\n `mlkem768x25519 eseed must be ${MLKEM768X25519_ESEED_LENGTH} bytes, got ${opts.eseed.length}`,\n );\n }\n const { cipherText, sharedSecret } = XWing.encapsulate(opts.publicKey, opts.eseed);\n return { enc: cipherText, ss: sharedSecret };\n}\n\nexport function mlkem768x25519Decapsulate(opts: Mlkem768X25519DecapsulateOpts): Uint8Array {\n // Pre-check both lengths before calling noble: decapsulation must perform a\n // constant amount of work for any caller-supplied ciphertext (implicit\n // rejection), which requires the inputs to be the exact expected sizes.\n if (opts.secretSeed.length !== MLKEM768X25519_SEED_LENGTH) {\n throw new Error(\n `mlkem768x25519 secret seed must be ${MLKEM768X25519_SEED_LENGTH} bytes, got ${opts.secretSeed.length}`,\n );\n }\n if (opts.enc.length !== MLKEM768X25519_ENC_LENGTH) {\n throw new Error(\n `mlkem768x25519 enc must be ${MLKEM768X25519_ENC_LENGTH} bytes, got ${opts.enc.length}`,\n );\n }\n // noble's signature is decapsulate(cipherText, secretKey) — ciphertext first.\n return XWing.decapsulate(opts.enc, opts.secretSeed);\n}\n"]}
package/dist/kem.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/kem/x25519.ts","../src/kem/mlkem768x25519.ts"],"names":[],"mappings":"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","file":"kem.js","sourcesContent":["import { x25519 } from '@noble/curves/ed25519.js';\n\n// RFC 7748 §6.1 contributory-behaviour rejection: a small-order (low-order)\n// Montgomery `u` coordinate makes the X25519 shared secret all-zero, which\n// @noble/curves refuses with `Error: invalid private or public key received`.\n// We rethrow that as a *typed* error so callers can distinguish a structurally\n// valid-but-malicious peer public key (a property of attacker-supplied wire\n// data — trial-decrypt MUST treat the slot as a non-match, not crash) from\n// genuine caller misuse such as a wrong-length key (which @noble raises as a\n// RangeError and which we deliberately let propagate untouched).\nexport class X25519LowOrderPointError extends Error {\n readonly code = 'X25519_LOW_ORDER_POINT' as const;\n constructor(options?: { cause?: unknown }) {\n super('x25519 ECDH rejected: peer public key is a small-order point', options);\n this.name = 'X25519LowOrderPointError';\n }\n}\n\n// @noble/curves v2 signals a small-order/all-zero shared secret with this exact\n// message. Matching on it (rather than the broad Error class) keeps unrelated\n// failures — e.g. a future internal assertion — surfacing as themselves.\nconst NOBLE_LOW_ORDER_MESSAGE = 'invalid private or public key received';\n\nexport interface X25519KeyPair {\n readonly secretKey: Uint8Array;\n readonly publicKey: Uint8Array;\n}\n\nexport interface X25519PublicKeyOpts {\n readonly secretKey: Uint8Array;\n}\n\nexport interface X25519EcdhOpts {\n readonly secretKey: Uint8Array;\n readonly theirPublicKey: Uint8Array;\n}\n\nexport function x25519Keygen(): X25519KeyPair {\n return x25519.keygen();\n}\n\nexport function x25519PublicKey(opts: X25519PublicKeyOpts): Uint8Array {\n return x25519.getPublicKey(opts.secretKey);\n}\n\nexport function x25519Ecdh(opts: X25519EcdhOpts): Uint8Array {\n try {\n return x25519.getSharedSecret(opts.secretKey, opts.theirPublicKey);\n } catch (e) {\n // Translate ONLY the contributory-check rejection into our typed error.\n // A wrong-length key throws a RangeError from @noble's length assertion;\n // that is caller misuse, not malicious wire data, so it must propagate.\n if (e instanceof Error && e.message === NOBLE_LOW_ORDER_MESSAGE) {\n throw new X25519LowOrderPointError({ cause: e });\n }\n throw e;\n }\n}\n","import { XWing } from '@noble/post-quantum/hybrid.js';\n\n// X-Wing (ML-KEM-768 + X25519) hybrid KEM per draft-connolly-cfrg-xwing-kem-06.\n// `XWing` is @noble/post-quantum's alias for `ml_kem768_x25519`. We expose it\n// through opts-object wrappers that pin the wire lengths and map noble's field\n// names onto the project's vocabulary.\n//\n// Unlike the bare X25519 KEM, there is no contributory-behaviour rejection to\n// translate: X-Wing combines the ML-KEM and X25519 shared secrets through a\n// SHA3-256 combiner that also binds the X25519 ephemeral and recipient public\n// keys, and ML-KEM's implicit rejection already yields a constant-work\n// pseudorandom secret on a malformed ciphertext. Decapsulation therefore never\n// throws on attacker-supplied wire data — a wrong shared secret is the correct,\n// indistinguishable failure mode, and callers MUST treat it as a non-match\n// rather than expecting an exception.\n\nexport const MLKEM768X25519_PUBLIC_KEY_LENGTH = 1216 as const;\nexport const MLKEM768X25519_ENC_LENGTH = 1120 as const;\nexport const MLKEM768X25519_SHARED_SECRET_LENGTH = 32 as const;\nexport const MLKEM768X25519_SEED_LENGTH = 32 as const;\nexport const MLKEM768X25519_ESEED_LENGTH = 64 as const;\n\nexport interface Mlkem768X25519KeyPair {\n // The 32-byte root seed IS the secret key in draft-06: the ML-KEM coins and\n // the X25519 scalar are re-expanded from it via SHAKE-256 at decapsulation.\n readonly secretSeed: Uint8Array;\n readonly publicKey: Uint8Array;\n}\n\nexport interface Mlkem768X25519EncapsulateOpts {\n readonly publicKey: Uint8Array;\n // Optional 64-byte encapsulation randomness (msgRand). When supplied the\n // ciphertext and shared secret are fully deterministic; a 32-byte value is\n // rejected by noble, so we pin the length here too.\n readonly eseed?: Uint8Array;\n}\n\nexport interface Mlkem768X25519Encapsulation {\n readonly enc: Uint8Array;\n readonly ss: Uint8Array;\n}\n\nexport interface Mlkem768X25519DecapsulateOpts {\n readonly secretSeed: Uint8Array;\n readonly enc: Uint8Array;\n}\n\nexport function mlkem768x25519Keygen(seed: Uint8Array): Mlkem768X25519KeyPair {\n if (seed.length !== MLKEM768X25519_SEED_LENGTH) {\n throw new Error(\n `mlkem768x25519 seed must be ${MLKEM768X25519_SEED_LENGTH} bytes, got ${seed.length}`,\n );\n }\n const { secretKey, publicKey } = XWing.keygen(seed);\n return { secretSeed: secretKey, publicKey };\n}\n\nexport function mlkem768x25519Encapsulate(\n opts: Mlkem768X25519EncapsulateOpts,\n): Mlkem768X25519Encapsulation {\n if (opts.publicKey.length !== MLKEM768X25519_PUBLIC_KEY_LENGTH) {\n throw new Error(\n `mlkem768x25519 public key must be ${MLKEM768X25519_PUBLIC_KEY_LENGTH} bytes, got ${opts.publicKey.length}`,\n );\n }\n if (opts.eseed !== undefined && opts.eseed.length !== MLKEM768X25519_ESEED_LENGTH) {\n throw new Error(\n `mlkem768x25519 eseed must be ${MLKEM768X25519_ESEED_LENGTH} bytes, got ${opts.eseed.length}`,\n );\n }\n const { cipherText, sharedSecret } = XWing.encapsulate(opts.publicKey, opts.eseed);\n return { enc: cipherText, ss: sharedSecret };\n}\n\nexport function mlkem768x25519Decapsulate(opts: Mlkem768X25519DecapsulateOpts): Uint8Array {\n // Pre-check both lengths before calling noble: decapsulation must perform a\n // constant amount of work for any caller-supplied ciphertext (implicit\n // rejection), which requires the inputs to be the exact expected sizes.\n if (opts.secretSeed.length !== MLKEM768X25519_SEED_LENGTH) {\n throw new Error(\n `mlkem768x25519 secret seed must be ${MLKEM768X25519_SEED_LENGTH} bytes, got ${opts.secretSeed.length}`,\n );\n }\n if (opts.enc.length !== MLKEM768X25519_ENC_LENGTH) {\n throw new Error(\n `mlkem768x25519 enc must be ${MLKEM768X25519_ENC_LENGTH} bytes, got ${opts.enc.length}`,\n );\n }\n // noble's signature is decapsulate(cipherText, secretKey) — ciphertext first.\n return XWing.decapsulate(opts.enc, opts.secretSeed);\n}\n"]}
1
+ {"version":3,"sources":["../src/kem/x25519.ts","../src/kem/mlkem768x25519.ts"],"names":[],"mappings":"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","file":"kem.js","sourcesContent":["import { x25519 } from '@noble/curves/ed25519.js';\n\n// RFC 7748 §6.1 contributory-behaviour rejection: a small-order (low-order)\n// Montgomery `u` coordinate makes the X25519 shared secret all-zero, which\n// @noble/curves refuses with `Error: invalid private or public key received`.\n// We rethrow that as a *typed* error so callers can distinguish a structurally\n// valid-but-malicious peer public key (a property of attacker-supplied wire\n// data — trial-decrypt MUST treat the slot as a non-match, not crash) from\n// genuine caller misuse such as a wrong-length key (which @noble raises as a\n// RangeError and which we deliberately let propagate untouched).\nexport class X25519LowOrderPointError extends Error {\n readonly code = 'X25519_LOW_ORDER_POINT' as const;\n constructor(options?: { cause?: unknown }) {\n super('x25519 ECDH rejected: peer public key is a small-order point', options);\n this.name = 'X25519LowOrderPointError';\n }\n}\n\n// @noble/curves v2 signals a small-order/all-zero shared secret with this exact\n// message. Matching on it (rather than the broad Error class) keeps unrelated\n// failures — e.g. a future internal assertion — surfacing as themselves.\nconst NOBLE_LOW_ORDER_MESSAGE = 'invalid private or public key received';\n\nexport interface X25519KeyPair {\n readonly secretKey: Uint8Array;\n readonly publicKey: Uint8Array;\n}\n\nexport interface X25519PublicKeyOpts {\n readonly secretKey: Uint8Array;\n}\n\nexport interface X25519EcdhOpts {\n readonly secretKey: Uint8Array;\n readonly theirPublicKey: Uint8Array;\n}\n\nexport function x25519Keygen(): X25519KeyPair {\n return x25519.keygen();\n}\n\nexport function x25519PublicKey(opts: X25519PublicKeyOpts): Uint8Array {\n return x25519.getPublicKey(opts.secretKey);\n}\n\nexport function x25519Ecdh(opts: X25519EcdhOpts): Uint8Array {\n try {\n return x25519.getSharedSecret(opts.secretKey, opts.theirPublicKey);\n } catch (e) {\n // Translate ONLY the contributory-check rejection into our typed error.\n // A wrong-length key throws a RangeError from @noble's length assertion;\n // that is caller misuse, not malicious wire data, so it must propagate.\n if (e instanceof Error && e.message === NOBLE_LOW_ORDER_MESSAGE) {\n throw new X25519LowOrderPointError({ cause: e });\n }\n throw e;\n }\n}\n","import { XWing } from '@noble/post-quantum/hybrid.js';\n\n// X-Wing (ML-KEM-768 + X25519) hybrid KEM per draft-connolly-cfrg-xwing-kem-10.\n// `XWing` is @noble/post-quantum's alias for `ml_kem768_x25519`. We expose it\n// through opts-object wrappers that pin the wire lengths and map noble's field\n// names onto the project's vocabulary.\n//\n// Unlike the bare X25519 KEM, there is no contributory-behaviour rejection to\n// translate: X-Wing combines the ML-KEM and X25519 shared secrets through a\n// SHA3-256 combiner that also binds the X25519 ephemeral and recipient public\n// keys, and ML-KEM's implicit rejection already yields a constant-work\n// pseudorandom secret on a malformed ciphertext. Decapsulation therefore never\n// throws on attacker-supplied wire data — a wrong shared secret is the correct,\n// indistinguishable failure mode, and callers MUST treat it as a non-match\n// rather than expecting an exception.\n\nexport const MLKEM768X25519_PUBLIC_KEY_LENGTH = 1216 as const;\nexport const MLKEM768X25519_ENC_LENGTH = 1120 as const;\nexport const MLKEM768X25519_SHARED_SECRET_LENGTH = 32 as const;\nexport const MLKEM768X25519_SEED_LENGTH = 32 as const;\nexport const MLKEM768X25519_ESEED_LENGTH = 64 as const;\n\nexport interface Mlkem768X25519KeyPair {\n // The 32-byte root seed IS the secret key: the ML-KEM coins and the X25519\n // scalar are re-expanded from it via SHAKE-256 at decapsulation. (Later X-Wing\n // drafts also define an optional expanded decapsulation-key form; we keep the\n // seed-only key, which the draft-10 Appendix C vectors still pin.)\n readonly secretSeed: Uint8Array;\n readonly publicKey: Uint8Array;\n}\n\nexport interface Mlkem768X25519EncapsulateOpts {\n readonly publicKey: Uint8Array;\n // Optional 64-byte encapsulation randomness (msgRand). When supplied the\n // ciphertext and shared secret are fully deterministic; a 32-byte value is\n // rejected by noble, so we pin the length here too.\n readonly eseed?: Uint8Array;\n}\n\nexport interface Mlkem768X25519Encapsulation {\n readonly enc: Uint8Array;\n readonly ss: Uint8Array;\n}\n\nexport interface Mlkem768X25519DecapsulateOpts {\n readonly secretSeed: Uint8Array;\n readonly enc: Uint8Array;\n}\n\nexport function mlkem768x25519Keygen(seed: Uint8Array): Mlkem768X25519KeyPair {\n if (seed.length !== MLKEM768X25519_SEED_LENGTH) {\n throw new Error(\n `mlkem768x25519 seed must be ${MLKEM768X25519_SEED_LENGTH} bytes, got ${seed.length}`,\n );\n }\n const { secretKey, publicKey } = XWing.keygen(seed);\n return { secretSeed: secretKey, publicKey };\n}\n\nexport function mlkem768x25519Encapsulate(\n opts: Mlkem768X25519EncapsulateOpts,\n): Mlkem768X25519Encapsulation {\n if (opts.publicKey.length !== MLKEM768X25519_PUBLIC_KEY_LENGTH) {\n throw new Error(\n `mlkem768x25519 public key must be ${MLKEM768X25519_PUBLIC_KEY_LENGTH} bytes, got ${opts.publicKey.length}`,\n );\n }\n if (opts.eseed !== undefined && opts.eseed.length !== MLKEM768X25519_ESEED_LENGTH) {\n throw new Error(\n `mlkem768x25519 eseed must be ${MLKEM768X25519_ESEED_LENGTH} bytes, got ${opts.eseed.length}`,\n );\n }\n const { cipherText, sharedSecret } = XWing.encapsulate(opts.publicKey, opts.eseed);\n return { enc: cipherText, ss: sharedSecret };\n}\n\nexport function mlkem768x25519Decapsulate(opts: Mlkem768X25519DecapsulateOpts): Uint8Array {\n // Pre-check both lengths before calling noble: decapsulation must perform a\n // constant amount of work for any caller-supplied ciphertext (implicit\n // rejection), which requires the inputs to be the exact expected sizes.\n if (opts.secretSeed.length !== MLKEM768X25519_SEED_LENGTH) {\n throw new Error(\n `mlkem768x25519 secret seed must be ${MLKEM768X25519_SEED_LENGTH} bytes, got ${opts.secretSeed.length}`,\n );\n }\n if (opts.enc.length !== MLKEM768X25519_ENC_LENGTH) {\n throw new Error(\n `mlkem768x25519 enc must be ${MLKEM768X25519_ENC_LENGTH} bytes, got ${opts.enc.length}`,\n );\n }\n // noble's signature is decapsulate(cipherText, secretKey) — ciphertext first.\n return XWing.decapsulate(opts.enc, opts.secretSeed);\n}\n"]}
package/dist/merkle.cjs CHANGED
@@ -31,7 +31,7 @@ function decodeCanonicalCbor(bytes) {
31
31
  ...cbor2.cdeDecodeOptions,
32
32
  rejectStreaming: true,
33
33
  rejectDuplicateKeys: true,
34
- // A CIP-309 record carries integers, byte/text strings, arrays, maps and
34
+ // A Label 309 record carries integers, byte/text strings, arrays, maps and
35
35
  // `null` — and nothing else. Without these rejections the major-type-7
36
36
  // surface leaks into the decoder: a float16/32/64 that happens to hold an
37
37
  // integral value (e.g. 1.0) silently decodes to the integer 1 and passes
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cbor/errors.ts","../src/cbor/canonical.ts","../src/util/compare-ct.ts","../src/hash/merkle-sha2-256.ts","../src/merkle/leaves-list.ts"],"names":["encode","sortCoreDeterministic","decode","cdeDecodeOptions","sha256","DIGEST_LENGTH"],"mappings":"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","file":"merkle.cjs","sourcesContent":["// Every canonical-CBOR decode violation collapses to the single public CIP-309\n// taxonomy code MALFORMED_CBOR: indefinite-length (streaming) items, duplicate\n// keys, unsorted keys, non-minimal integer encodings, and invalid UTF-8 in text\n// strings. The taxonomy intentionally has one code for all of these; the\n// specific cause survives in the human-readable error message, not as a\n// separate code.\nexport type CanonicalCborErrorCode = 'MALFORMED_CBOR';\n\nexport class CanonicalCborError extends Error {\n readonly code: CanonicalCborErrorCode;\n\n constructor(code: CanonicalCborErrorCode, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = 'CanonicalCborError';\n this.code = code;\n }\n}\n","import { cdeDecodeOptions, decode, encode } from 'cbor2';\nimport { sortCoreDeterministic } from 'cbor2/sorts';\n\nimport { CanonicalCborError } from './errors';\n\nexport type CanonicalCborValue =\n | null\n | boolean\n | number\n | bigint\n | string\n | Uint8Array\n | readonly CanonicalCborValue[]\n | { readonly [key: string]: CanonicalCborValue }\n | ReadonlyMap<string | number, CanonicalCborValue>;\n\nexport function encodeCanonicalCbor(value: CanonicalCborValue): Uint8Array {\n return encode(value, {\n cde: true,\n collapseBigInts: true,\n rejectDuplicateKeys: true,\n sortKeys: sortCoreDeterministic,\n });\n}\n\nexport function decodeCanonicalCbor(bytes: Uint8Array): unknown {\n try {\n return decode(bytes, {\n ...cdeDecodeOptions,\n rejectStreaming: true,\n rejectDuplicateKeys: true,\n // A CIP-309 record carries integers, byte/text strings, arrays, maps and\n // `null` — and nothing else. Without these rejections the major-type-7\n // surface leaks into the decoder: a float16/32/64 that happens to hold an\n // integral value (e.g. 1.0) silently decodes to the integer 1 and passes\n // a `z.literal(1)` / Number.isInteger schema check, so two byte strings\n // that are NOT byte-identical canonicalise to the same record. That\n // breaks the cross-implementation parity invariant (the Python twin\n // already rejects non-integer `v` / `enc.scheme` outright). Reject the\n // whole non-record surface — floats, negative zero, undefined, and\n // non-{true,false,null} simple values — so any such input surfaces as\n // MALFORMED_CBOR via mapDecodeError rather than decoding to a look-alike.\n rejectFloats: true,\n rejectNegativeZero: true,\n rejectUndefined: true,\n rejectSimple: true,\n });\n } catch (cause) {\n throw mapDecodeError(cause);\n }\n}\n\nfunction mapDecodeError(cause: unknown): CanonicalCborError {\n const message = cause instanceof Error ? cause.message : String(cause);\n const lower = message.toLowerCase();\n // Every canonical-decode violation collapses to the single public taxonomy\n // code MALFORMED_CBOR: indefinite-length (streaming) items, duplicate keys,\n // non-canonical (unsorted) key ordering, non-minimal integer encodings, and\n // invalid UTF-8 in text strings. cbor2 raises the SAME \"Duplicate or out of\n // order key\" message for both true duplicates AND distinct-but-unsorted keys,\n // so the two are indistinguishable by message — and per the CIP-309 taxonomy\n // both belong under MALFORMED_CBOR anyway. The specific cause survives in the\n // human-readable message below; for indefinite-length we state it explicitly\n // so the diagnostic is not lost when the code is collapsed.\n const isIndefinite = lower.includes('streaming') || lower.includes('indefinite');\n const detail = isIndefinite\n ? `indefinite-length items are not permitted in canonical CBOR: ${message}`\n : message;\n return new CanonicalCborError('MALFORMED_CBOR', `cbor decode failed: ${detail}`, { cause });\n}\n","// Isomorphic constant-time byte-equality. crypto-core is browser-safe by\n// design, so we cannot import `node:crypto.timingSafeEqual` — webpack rejects\n// the `node:` scheme in the browser bundle. A pure-JS XOR loop is constant-time\n// for equal-length inputs; length mismatch is a deliberate early-return (the\n// API surface itself leaks length, same as node's timingSafeEqual which throws).\nexport function compareCt(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n // Lengths are equal and `i` stays in-bounds, so both indexes are always\n // defined — no nullish guard is needed (and one would read as a guard for\n // an impossible case).\n for (let i = 0; i < a.length; i++) diff |= (a[i] as number) ^ (b[i] as number);\n return diff === 0;\n}\n","// RFC 9162 §2.1.1 binary Merkle tree under SHA-256.\n// This implements the algorithm tier identified on the wire as the\n// `rfc9162-sha256` OPT-INFO; the record's `merkle[]` field carries the proof.\n//\n// Construction (RFC 9162 §2.1.1):\n// - Single leaf: MTH({d_0}) = SHA-256(0x00 || d_0)\n// - Internal node: MTH(L) = SHA-256(0x01 || MTH(L[0:k]) || MTH(L[k:n]))\n// where k = largest power of 2 strictly less than n.\n// - Empty trees (n == 0) are FORBIDDEN.\n// - The 0x00 leaf / 0x01 internal prefixes prevent the CVE-2012-2459\n// leaf-vs-internal collision family.\n\nimport { sha256 } from '@noble/hashes/sha2.js';\n\nimport { compareCt } from '../util/compare-ct';\n\nexport const MERKLE_ALG_ID = 'rfc9162-sha256' as const;\n\nconst LEAF_PREFIX = 0x00;\nconst NODE_PREFIX = 0x01;\nconst DIGEST_LENGTH = 32;\n\nfunction validateLeaves(leaves: ReadonlyArray<Uint8Array>, fnName: string): void {\n if (leaves.length === 0) {\n throw new Error(`${fnName}: empty leaf list (n == 0 is forbidden by RFC 9162 §2.1.1)`);\n }\n for (let i = 0; i < leaves.length; i++) {\n const leaf = leaves[i];\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {\n throw new Error(\n `${fnName}: leaf[${i}] must be a Uint8Array(${DIGEST_LENGTH}); got length ${\n leaf instanceof Uint8Array ? leaf.length : 'non-Uint8Array'\n }`,\n );\n }\n }\n}\n\nexport function merkleSha2256Root(leaves: ReadonlyArray<Uint8Array>): Uint8Array {\n validateLeaves(leaves, 'merkleSha2256Root');\n return mthRecursive(leaves, 0, leaves.length);\n}\n\nexport function merkleSha2256InclusionProof(\n leaves: ReadonlyArray<Uint8Array>,\n index: number,\n): Uint8Array[] {\n validateLeaves(leaves, 'merkleSha2256InclusionProof');\n if (!Number.isInteger(index) || index < 0 || index >= leaves.length) {\n throw new Error(\n `merkleSha2256InclusionProof: index ${index} out of range [0, ${leaves.length})`,\n );\n }\n return auditPath(leaves, index, 0, leaves.length);\n}\n\n/**\n * Verify an inclusion proof per RFC 9162 §2.1.3.2 (iterative form).\n *\n * `proof` is ordered leaf-to-root: `proof[0]` is the sibling at the leaf\n * level, `proof[m-1]` is the top-level sibling. The fold uses the\n * `sn`/`fn` tracking from RFC 9162: `sn` is the leaf index within the\n * current subtree, `fn` is (subtree_size - 1). At each step, `sn` odd\n * OR `sn == fn` means the current node is a right child (sibling on\n * the left); otherwise it is a left child (sibling on the right).\n * Both shift right by one each iteration. This handles non-power-of-2\n * sizes including the \"promote a lone right subtree\" cases.\n */\nexport function merkleSha2256VerifyInclusion(\n leaf: Uint8Array,\n index: number,\n treeSize: number,\n proof: ReadonlyArray<Uint8Array>,\n root: Uint8Array,\n): boolean {\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) return false;\n if (!(root instanceof Uint8Array) || root.length !== DIGEST_LENGTH) return false;\n if (\n !Number.isInteger(index) ||\n !Number.isInteger(treeSize) ||\n treeSize < 1 ||\n index < 0 ||\n index >= treeSize\n ) {\n return false;\n }\n for (let i = 0; i < proof.length; i++) {\n const sibling = proof[i];\n if (!(sibling instanceof Uint8Array) || sibling.length !== DIGEST_LENGTH) {\n return false;\n }\n }\n\n if (treeSize === 1) {\n if (proof.length !== 0 || index !== 0) return false;\n return compareCt(hashLeaf(leaf), root);\n }\n\n let h = hashLeaf(leaf);\n let sn = index;\n let fn = treeSize - 1;\n for (let i = 0; i < proof.length; i++) {\n if (fn === 0) return false;\n const sibling = proof[i] as Uint8Array;\n if ((sn & 1) === 1 || sn === fn) {\n h = hashNode(sibling, h);\n while ((sn & 1) === 0 && sn !== 0) {\n sn >>>= 1;\n fn >>>= 1;\n }\n } else {\n h = hashNode(h, sibling);\n }\n sn >>>= 1;\n fn >>>= 1;\n }\n if (fn !== 0) return false;\n return compareCt(h, root);\n}\n\nfunction largestPow2Lt(n: number): number {\n let k = 1;\n while (k * 2 < n) k *= 2;\n return k;\n}\n\nfunction hashLeaf(d: Uint8Array): Uint8Array {\n const buf = new Uint8Array(1 + d.length);\n buf[0] = LEAF_PREFIX;\n buf.set(d, 1);\n return sha256(buf);\n}\n\nfunction hashNode(left: Uint8Array, right: Uint8Array): Uint8Array {\n const buf = new Uint8Array(1 + left.length + right.length);\n buf[0] = NODE_PREFIX;\n buf.set(left, 1);\n buf.set(right, 1 + left.length);\n return sha256(buf);\n}\n\nfunction mthRecursive(leaves: ReadonlyArray<Uint8Array>, start: number, end: number): Uint8Array {\n const n = end - start;\n if (n === 1) {\n return hashLeaf(leaves[start] as Uint8Array);\n }\n const k = largestPow2Lt(n);\n const left = mthRecursive(leaves, start, start + k);\n const right = mthRecursive(leaves, start + k, end);\n return hashNode(left, right);\n}\n\nfunction auditPath(\n leaves: ReadonlyArray<Uint8Array>,\n i: number,\n start: number,\n end: number,\n): Uint8Array[] {\n const n = end - start;\n if (n === 1) return [];\n const k = largestPow2Lt(n);\n if (i < k) {\n const subPath = auditPath(leaves, i, start, start + k);\n subPath.push(mthRecursive(leaves, start + k, end));\n return subPath;\n }\n const subPath = auditPath(leaves, i - k, start + k, end);\n subPath.push(mthRecursive(leaves, start, start + k));\n return subPath;\n}\n","// Canonical-CBOR codec for the off-chain Merkle leaves-list artefact.\n// The on-chain `merkle[]` field binds to this file via `uris[]` / `leaf_count`;\n// the file itself carries the full leaf set. Canonical CBOR is RFC 8949 §4.2.1.\n//\n// CDDL:\n//\n// leaves-list = {\n// \"format\": \"cardano-poe-merkle-leaves-v1\",\n// \"tree_alg\": \"rfc9162-sha256\",\n// \"root\": bytes .size 32,\n// \"leaves\": [ + bytes .size 32 ],\n// \"leaf_count\": uint,\n// ? \"leaf_alg\": tstr,\n// }\n//\n// Canonical ordering is bytewise-lexicographic on encoded map keys (RFC 8949\n// §4.2.1) so the wire-key order is fixed by `cde:true` regardless of insertion\n// order: root (4B) < format (6B) < leaves (6B) < leaf_alg (8B) < tree_alg (8B)\n// < leaf_count (10B).\n\nimport { decodeCanonicalCbor, encodeCanonicalCbor } from '../cbor/canonical';\nimport { compareCt } from '../util/compare-ct';\nimport { merkleSha2256Root } from '../hash/merkle-sha2-256';\n\nexport const LEAVES_LIST_FORMAT_V1 = 'cardano-poe-merkle-leaves-v1' as const;\nconst TREE_ALG_RFC9162 = 'rfc9162-sha256' as const;\nconst DIGEST_LENGTH = 32;\nconst REGISTERED_FORMATS = new Set<string>([LEAVES_LIST_FORMAT_V1]);\n\nexport type MerkleLeavesListErrorCode =\n | 'SCHEMA_MERKLE_LEAVES_FORMAT_UNSUPPORTED'\n | 'SCHEMA_MERKLE_LEAVES_MALFORMED'\n | 'SCHEMA_MERKLE_LEAF_COUNT_MISMATCH'\n | 'MERKLE_ROOT_MISMATCH';\n\nexport class MerkleLeavesListError extends Error {\n readonly code: MerkleLeavesListErrorCode;\n constructor(code: MerkleLeavesListErrorCode, message?: string) {\n super(message ? `${code}: ${message}` : code);\n this.code = code;\n this.name = 'MerkleLeavesListError';\n }\n}\n\nexport interface EncodeLeavesListArgs {\n readonly leaves: ReadonlyArray<Uint8Array>;\n readonly root: Uint8Array;\n readonly leafAlg?: string;\n}\n\nexport interface DecodedLeavesList {\n readonly format: typeof LEAVES_LIST_FORMAT_V1;\n readonly treeAlg: typeof TREE_ALG_RFC9162;\n readonly root: Uint8Array;\n readonly leaves: Uint8Array[];\n readonly leafCount: number;\n readonly leafAlg?: string;\n}\n\nexport function encodeLeavesList(args: EncodeLeavesListArgs): Uint8Array {\n if (!(args.root instanceof Uint8Array) || args.root.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `root must be a Uint8Array(${DIGEST_LENGTH})`,\n );\n }\n if (args.leaves.length < 1) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaves array must be non-empty',\n );\n }\n const leavesCopy: Uint8Array[] = [];\n for (let i = 0; i < args.leaves.length; i++) {\n const leaf = args.leaves[i];\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `leaves[${i}] must be a Uint8Array(${DIGEST_LENGTH})`,\n );\n }\n leavesCopy.push(leaf);\n }\n if (args.leafAlg !== undefined && typeof args.leafAlg !== 'string') {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_alg must be a string when present',\n );\n }\n const map: Record<string, unknown> = {\n format: LEAVES_LIST_FORMAT_V1,\n tree_alg: TREE_ALG_RFC9162,\n root: args.root,\n leaves: leavesCopy,\n leaf_count: leavesCopy.length,\n };\n if (args.leafAlg !== undefined) {\n map['leaf_alg'] = args.leafAlg;\n }\n return encodeCanonicalCbor(map as never);\n}\n\nexport function decodeLeavesList(bytes: Uint8Array): DecodedLeavesList {\n const decoded = decodeCanonicalCbor(bytes);\n if (typeof decoded !== 'object' || decoded === null || Array.isArray(decoded)) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaves-list MUST be a CBOR map',\n );\n }\n const m = decoded as Record<string, unknown>;\n\n const format = m['format'];\n if (typeof format !== 'string') {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'format must be a text string',\n );\n }\n if (!REGISTERED_FORMATS.has(format)) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_FORMAT_UNSUPPORTED',\n `format '${format}' is not in the registered set`,\n );\n }\n\n const treeAlg = m['tree_alg'];\n if (treeAlg !== TREE_ALG_RFC9162) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `tree_alg '${String(treeAlg)}' is not '${TREE_ALG_RFC9162}'`,\n );\n }\n\n const root = m['root'];\n if (!(root instanceof Uint8Array) || root.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `root must be a ${DIGEST_LENGTH}-byte byte string`,\n );\n }\n\n const leavesRaw = m['leaves'];\n if (!Array.isArray(leavesRaw) || leavesRaw.length < 1) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaves must be a non-empty array',\n );\n }\n const leaves: Uint8Array[] = [];\n for (let i = 0; i < leavesRaw.length; i++) {\n const leaf = leavesRaw[i];\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `leaves[${i}] must be a ${DIGEST_LENGTH}-byte byte string`,\n );\n }\n leaves.push(leaf);\n }\n\n const leafCountRaw = m['leaf_count'];\n let leafCount: number;\n if (typeof leafCountRaw === 'number' && Number.isInteger(leafCountRaw) && leafCountRaw >= 0) {\n leafCount = leafCountRaw;\n } else if (typeof leafCountRaw === 'bigint' && leafCountRaw >= 0n) {\n if (leafCountRaw > BigInt(Number.MAX_SAFE_INTEGER)) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_count exceeds Number.MAX_SAFE_INTEGER',\n );\n }\n leafCount = Number(leafCountRaw);\n } else {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_count must be a non-negative CBOR uint',\n );\n }\n if (leaves.length !== leafCount) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAF_COUNT_MISMATCH',\n `leaves.length (${leaves.length}) != leaf_count (${leafCount})`,\n );\n }\n\n let leafAlg: string | undefined;\n if (m['leaf_alg'] !== undefined) {\n if (typeof m['leaf_alg'] !== 'string') {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_alg must be a text string when present',\n );\n }\n leafAlg = m['leaf_alg'];\n }\n\n const recomputed = merkleSha2256Root(leaves);\n if (!compareCt(recomputed, root)) {\n throw new MerkleLeavesListError(\n 'MERKLE_ROOT_MISMATCH',\n 'leaves recompute does not match declared root',\n );\n }\n\n const out: DecodedLeavesList = {\n format: LEAVES_LIST_FORMAT_V1,\n treeAlg: TREE_ALG_RFC9162,\n root,\n leaves,\n leafCount,\n ...(leafAlg !== undefined ? { leafAlg } : {}),\n };\n return out;\n}\n"]}
1
+ {"version":3,"sources":["../src/cbor/errors.ts","../src/cbor/canonical.ts","../src/util/compare-ct.ts","../src/hash/merkle-sha2-256.ts","../src/merkle/leaves-list.ts"],"names":["encode","sortCoreDeterministic","decode","cdeDecodeOptions","sha256","DIGEST_LENGTH"],"mappings":"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","file":"merkle.cjs","sourcesContent":["// Every canonical-CBOR decode violation collapses to the single public Label 309\n// taxonomy code MALFORMED_CBOR: indefinite-length (streaming) items, duplicate\n// keys, unsorted keys, non-minimal integer encodings, and invalid UTF-8 in text\n// strings. The taxonomy intentionally has one code for all of these; the\n// specific cause survives in the human-readable error message, not as a\n// separate code.\nexport type CanonicalCborErrorCode = 'MALFORMED_CBOR';\n\nexport class CanonicalCborError extends Error {\n readonly code: CanonicalCborErrorCode;\n\n constructor(code: CanonicalCborErrorCode, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = 'CanonicalCborError';\n this.code = code;\n }\n}\n","import { cdeDecodeOptions, decode, encode } from 'cbor2';\nimport { sortCoreDeterministic } from 'cbor2/sorts';\n\nimport { CanonicalCborError } from './errors';\n\nexport type CanonicalCborValue =\n | null\n | boolean\n | number\n | bigint\n | string\n | Uint8Array\n | readonly CanonicalCborValue[]\n | { readonly [key: string]: CanonicalCborValue }\n | ReadonlyMap<string | number, CanonicalCborValue>;\n\nexport function encodeCanonicalCbor(value: CanonicalCborValue): Uint8Array {\n return encode(value, {\n cde: true,\n collapseBigInts: true,\n rejectDuplicateKeys: true,\n sortKeys: sortCoreDeterministic,\n });\n}\n\nexport function decodeCanonicalCbor(bytes: Uint8Array): unknown {\n try {\n return decode(bytes, {\n ...cdeDecodeOptions,\n rejectStreaming: true,\n rejectDuplicateKeys: true,\n // A Label 309 record carries integers, byte/text strings, arrays, maps and\n // `null` — and nothing else. Without these rejections the major-type-7\n // surface leaks into the decoder: a float16/32/64 that happens to hold an\n // integral value (e.g. 1.0) silently decodes to the integer 1 and passes\n // a `z.literal(1)` / Number.isInteger schema check, so two byte strings\n // that are NOT byte-identical canonicalise to the same record. That\n // breaks the cross-implementation parity invariant (the Python twin\n // already rejects non-integer `v` / `enc.scheme` outright). Reject the\n // whole non-record surface — floats, negative zero, undefined, and\n // non-{true,false,null} simple values — so any such input surfaces as\n // MALFORMED_CBOR via mapDecodeError rather than decoding to a look-alike.\n rejectFloats: true,\n rejectNegativeZero: true,\n rejectUndefined: true,\n rejectSimple: true,\n });\n } catch (cause) {\n throw mapDecodeError(cause);\n }\n}\n\nfunction mapDecodeError(cause: unknown): CanonicalCborError {\n const message = cause instanceof Error ? cause.message : String(cause);\n const lower = message.toLowerCase();\n // Every canonical-decode violation collapses to the single public taxonomy\n // code MALFORMED_CBOR: indefinite-length (streaming) items, duplicate keys,\n // non-canonical (unsorted) key ordering, non-minimal integer encodings, and\n // invalid UTF-8 in text strings. cbor2 raises the SAME \"Duplicate or out of\n // order key\" message for both true duplicates AND distinct-but-unsorted keys,\n // so the two are indistinguishable by message — and per the Label 309 taxonomy\n // both belong under MALFORMED_CBOR anyway. The specific cause survives in the\n // human-readable message below; for indefinite-length we state it explicitly\n // so the diagnostic is not lost when the code is collapsed.\n const isIndefinite = lower.includes('streaming') || lower.includes('indefinite');\n const detail = isIndefinite\n ? `indefinite-length items are not permitted in canonical CBOR: ${message}`\n : message;\n return new CanonicalCborError('MALFORMED_CBOR', `cbor decode failed: ${detail}`, { cause });\n}\n","// Isomorphic constant-time byte-equality. crypto-core is browser-safe by\n// design, so we cannot import `node:crypto.timingSafeEqual` — webpack rejects\n// the `node:` scheme in the browser bundle. A pure-JS XOR loop is constant-time\n// for equal-length inputs; length mismatch is a deliberate early-return (the\n// API surface itself leaks length, same as node's timingSafeEqual which throws).\nexport function compareCt(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n // Lengths are equal and `i` stays in-bounds, so both indexes are always\n // defined — no nullish guard is needed (and one would read as a guard for\n // an impossible case).\n for (let i = 0; i < a.length; i++) diff |= (a[i] as number) ^ (b[i] as number);\n return diff === 0;\n}\n","// RFC 9162 §2.1.1 binary Merkle tree under SHA-256.\n// This implements the algorithm tier identified on the wire as the\n// `rfc9162-sha256` OPT-INFO; the record's `merkle[]` field carries the proof.\n//\n// Construction (RFC 9162 §2.1.1):\n// - Single leaf: MTH({d_0}) = SHA-256(0x00 || d_0)\n// - Internal node: MTH(L) = SHA-256(0x01 || MTH(L[0:k]) || MTH(L[k:n]))\n// where k = largest power of 2 strictly less than n.\n// - Empty trees (n == 0) are FORBIDDEN.\n// - The 0x00 leaf / 0x01 internal prefixes prevent the CVE-2012-2459\n// leaf-vs-internal collision family.\n\nimport { sha256 } from '@noble/hashes/sha2.js';\n\nimport { compareCt } from '../util/compare-ct';\n\nexport const MERKLE_ALG_ID = 'rfc9162-sha256' as const;\n\nconst LEAF_PREFIX = 0x00;\nconst NODE_PREFIX = 0x01;\nconst DIGEST_LENGTH = 32;\n\nfunction validateLeaves(leaves: ReadonlyArray<Uint8Array>, fnName: string): void {\n if (leaves.length === 0) {\n throw new Error(`${fnName}: empty leaf list (n == 0 is forbidden by RFC 9162 §2.1.1)`);\n }\n for (let i = 0; i < leaves.length; i++) {\n const leaf = leaves[i];\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {\n throw new Error(\n `${fnName}: leaf[${i}] must be a Uint8Array(${DIGEST_LENGTH}); got length ${\n leaf instanceof Uint8Array ? leaf.length : 'non-Uint8Array'\n }`,\n );\n }\n }\n}\n\nexport function merkleSha2256Root(leaves: ReadonlyArray<Uint8Array>): Uint8Array {\n validateLeaves(leaves, 'merkleSha2256Root');\n return mthRecursive(leaves, 0, leaves.length);\n}\n\nexport function merkleSha2256InclusionProof(\n leaves: ReadonlyArray<Uint8Array>,\n index: number,\n): Uint8Array[] {\n validateLeaves(leaves, 'merkleSha2256InclusionProof');\n if (!Number.isInteger(index) || index < 0 || index >= leaves.length) {\n throw new Error(\n `merkleSha2256InclusionProof: index ${index} out of range [0, ${leaves.length})`,\n );\n }\n return auditPath(leaves, index, 0, leaves.length);\n}\n\n/**\n * Verify an inclusion proof per RFC 9162 §2.1.3.2 (iterative form).\n *\n * `proof` is ordered leaf-to-root: `proof[0]` is the sibling at the leaf\n * level, `proof[m-1]` is the top-level sibling. The fold uses the\n * `sn`/`fn` tracking from RFC 9162: `sn` is the leaf index within the\n * current subtree, `fn` is (subtree_size - 1). At each step, `sn` odd\n * OR `sn == fn` means the current node is a right child (sibling on\n * the left); otherwise it is a left child (sibling on the right).\n * Both shift right by one each iteration. This handles non-power-of-2\n * sizes including the \"promote a lone right subtree\" cases.\n */\nexport function merkleSha2256VerifyInclusion(\n leaf: Uint8Array,\n index: number,\n treeSize: number,\n proof: ReadonlyArray<Uint8Array>,\n root: Uint8Array,\n): boolean {\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) return false;\n if (!(root instanceof Uint8Array) || root.length !== DIGEST_LENGTH) return false;\n if (\n !Number.isInteger(index) ||\n !Number.isInteger(treeSize) ||\n treeSize < 1 ||\n index < 0 ||\n index >= treeSize\n ) {\n return false;\n }\n for (let i = 0; i < proof.length; i++) {\n const sibling = proof[i];\n if (!(sibling instanceof Uint8Array) || sibling.length !== DIGEST_LENGTH) {\n return false;\n }\n }\n\n if (treeSize === 1) {\n if (proof.length !== 0 || index !== 0) return false;\n return compareCt(hashLeaf(leaf), root);\n }\n\n let h = hashLeaf(leaf);\n let sn = index;\n let fn = treeSize - 1;\n for (let i = 0; i < proof.length; i++) {\n if (fn === 0) return false;\n const sibling = proof[i] as Uint8Array;\n if ((sn & 1) === 1 || sn === fn) {\n h = hashNode(sibling, h);\n while ((sn & 1) === 0 && sn !== 0) {\n sn >>>= 1;\n fn >>>= 1;\n }\n } else {\n h = hashNode(h, sibling);\n }\n sn >>>= 1;\n fn >>>= 1;\n }\n if (fn !== 0) return false;\n return compareCt(h, root);\n}\n\nfunction largestPow2Lt(n: number): number {\n let k = 1;\n while (k * 2 < n) k *= 2;\n return k;\n}\n\nfunction hashLeaf(d: Uint8Array): Uint8Array {\n const buf = new Uint8Array(1 + d.length);\n buf[0] = LEAF_PREFIX;\n buf.set(d, 1);\n return sha256(buf);\n}\n\nfunction hashNode(left: Uint8Array, right: Uint8Array): Uint8Array {\n const buf = new Uint8Array(1 + left.length + right.length);\n buf[0] = NODE_PREFIX;\n buf.set(left, 1);\n buf.set(right, 1 + left.length);\n return sha256(buf);\n}\n\nfunction mthRecursive(leaves: ReadonlyArray<Uint8Array>, start: number, end: number): Uint8Array {\n const n = end - start;\n if (n === 1) {\n return hashLeaf(leaves[start] as Uint8Array);\n }\n const k = largestPow2Lt(n);\n const left = mthRecursive(leaves, start, start + k);\n const right = mthRecursive(leaves, start + k, end);\n return hashNode(left, right);\n}\n\nfunction auditPath(\n leaves: ReadonlyArray<Uint8Array>,\n i: number,\n start: number,\n end: number,\n): Uint8Array[] {\n const n = end - start;\n if (n === 1) return [];\n const k = largestPow2Lt(n);\n if (i < k) {\n const subPath = auditPath(leaves, i, start, start + k);\n subPath.push(mthRecursive(leaves, start + k, end));\n return subPath;\n }\n const subPath = auditPath(leaves, i - k, start + k, end);\n subPath.push(mthRecursive(leaves, start, start + k));\n return subPath;\n}\n","// Canonical-CBOR codec for the off-chain Merkle leaves-list artefact.\n// The on-chain `merkle[]` field binds to this file via `uris[]` / `leaf_count`;\n// the file itself carries the full leaf set. Canonical CBOR is RFC 8949 §4.2.1.\n//\n// CDDL:\n//\n// leaves-list = {\n// \"format\": \"cardano-poe-merkle-leaves-v1\",\n// \"tree_alg\": \"rfc9162-sha256\",\n// \"root\": bytes .size 32,\n// \"leaves\": [ + bytes .size 32 ],\n// \"leaf_count\": uint,\n// ? \"leaf_alg\": tstr,\n// }\n//\n// Canonical ordering is bytewise-lexicographic on encoded map keys (RFC 8949\n// §4.2.1) so the wire-key order is fixed by `cde:true` regardless of insertion\n// order: root (4B) < format (6B) < leaves (6B) < leaf_alg (8B) < tree_alg (8B)\n// < leaf_count (10B).\n\nimport { decodeCanonicalCbor, encodeCanonicalCbor } from '../cbor/canonical';\nimport { compareCt } from '../util/compare-ct';\nimport { merkleSha2256Root } from '../hash/merkle-sha2-256';\n\nexport const LEAVES_LIST_FORMAT_V1 = 'cardano-poe-merkle-leaves-v1' as const;\nconst TREE_ALG_RFC9162 = 'rfc9162-sha256' as const;\nconst DIGEST_LENGTH = 32;\nconst REGISTERED_FORMATS = new Set<string>([LEAVES_LIST_FORMAT_V1]);\n\nexport type MerkleLeavesListErrorCode =\n | 'SCHEMA_MERKLE_LEAVES_FORMAT_UNSUPPORTED'\n | 'SCHEMA_MERKLE_LEAVES_MALFORMED'\n | 'SCHEMA_MERKLE_LEAF_COUNT_MISMATCH'\n | 'MERKLE_ROOT_MISMATCH';\n\nexport class MerkleLeavesListError extends Error {\n readonly code: MerkleLeavesListErrorCode;\n constructor(code: MerkleLeavesListErrorCode, message?: string) {\n super(message ? `${code}: ${message}` : code);\n this.code = code;\n this.name = 'MerkleLeavesListError';\n }\n}\n\nexport interface EncodeLeavesListArgs {\n readonly leaves: ReadonlyArray<Uint8Array>;\n readonly root: Uint8Array;\n readonly leafAlg?: string;\n}\n\nexport interface DecodedLeavesList {\n readonly format: typeof LEAVES_LIST_FORMAT_V1;\n readonly treeAlg: typeof TREE_ALG_RFC9162;\n readonly root: Uint8Array;\n readonly leaves: Uint8Array[];\n readonly leafCount: number;\n readonly leafAlg?: string;\n}\n\nexport function encodeLeavesList(args: EncodeLeavesListArgs): Uint8Array {\n if (!(args.root instanceof Uint8Array) || args.root.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `root must be a Uint8Array(${DIGEST_LENGTH})`,\n );\n }\n if (args.leaves.length < 1) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaves array must be non-empty',\n );\n }\n const leavesCopy: Uint8Array[] = [];\n for (let i = 0; i < args.leaves.length; i++) {\n const leaf = args.leaves[i];\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `leaves[${i}] must be a Uint8Array(${DIGEST_LENGTH})`,\n );\n }\n leavesCopy.push(leaf);\n }\n if (args.leafAlg !== undefined && typeof args.leafAlg !== 'string') {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_alg must be a string when present',\n );\n }\n const map: Record<string, unknown> = {\n format: LEAVES_LIST_FORMAT_V1,\n tree_alg: TREE_ALG_RFC9162,\n root: args.root,\n leaves: leavesCopy,\n leaf_count: leavesCopy.length,\n };\n if (args.leafAlg !== undefined) {\n map['leaf_alg'] = args.leafAlg;\n }\n return encodeCanonicalCbor(map as never);\n}\n\nexport function decodeLeavesList(bytes: Uint8Array): DecodedLeavesList {\n const decoded = decodeCanonicalCbor(bytes);\n if (typeof decoded !== 'object' || decoded === null || Array.isArray(decoded)) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaves-list MUST be a CBOR map',\n );\n }\n const m = decoded as Record<string, unknown>;\n\n const format = m['format'];\n if (typeof format !== 'string') {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'format must be a text string',\n );\n }\n if (!REGISTERED_FORMATS.has(format)) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_FORMAT_UNSUPPORTED',\n `format '${format}' is not in the registered set`,\n );\n }\n\n const treeAlg = m['tree_alg'];\n if (treeAlg !== TREE_ALG_RFC9162) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `tree_alg '${String(treeAlg)}' is not '${TREE_ALG_RFC9162}'`,\n );\n }\n\n const root = m['root'];\n if (!(root instanceof Uint8Array) || root.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `root must be a ${DIGEST_LENGTH}-byte byte string`,\n );\n }\n\n const leavesRaw = m['leaves'];\n if (!Array.isArray(leavesRaw) || leavesRaw.length < 1) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaves must be a non-empty array',\n );\n }\n const leaves: Uint8Array[] = [];\n for (let i = 0; i < leavesRaw.length; i++) {\n const leaf = leavesRaw[i];\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `leaves[${i}] must be a ${DIGEST_LENGTH}-byte byte string`,\n );\n }\n leaves.push(leaf);\n }\n\n const leafCountRaw = m['leaf_count'];\n let leafCount: number;\n if (typeof leafCountRaw === 'number' && Number.isInteger(leafCountRaw) && leafCountRaw >= 0) {\n leafCount = leafCountRaw;\n } else if (typeof leafCountRaw === 'bigint' && leafCountRaw >= 0n) {\n if (leafCountRaw > BigInt(Number.MAX_SAFE_INTEGER)) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_count exceeds Number.MAX_SAFE_INTEGER',\n );\n }\n leafCount = Number(leafCountRaw);\n } else {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_count must be a non-negative CBOR uint',\n );\n }\n if (leaves.length !== leafCount) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAF_COUNT_MISMATCH',\n `leaves.length (${leaves.length}) != leaf_count (${leafCount})`,\n );\n }\n\n let leafAlg: string | undefined;\n if (m['leaf_alg'] !== undefined) {\n if (typeof m['leaf_alg'] !== 'string') {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_alg must be a text string when present',\n );\n }\n leafAlg = m['leaf_alg'];\n }\n\n const recomputed = merkleSha2256Root(leaves);\n if (!compareCt(recomputed, root)) {\n throw new MerkleLeavesListError(\n 'MERKLE_ROOT_MISMATCH',\n 'leaves recompute does not match declared root',\n );\n }\n\n const out: DecodedLeavesList = {\n format: LEAVES_LIST_FORMAT_V1,\n treeAlg: TREE_ALG_RFC9162,\n root,\n leaves,\n leafCount,\n ...(leafAlg !== undefined ? { leafAlg } : {}),\n };\n return out;\n}\n"]}
package/dist/merkle.js CHANGED
@@ -29,7 +29,7 @@ function decodeCanonicalCbor(bytes) {
29
29
  ...cdeDecodeOptions,
30
30
  rejectStreaming: true,
31
31
  rejectDuplicateKeys: true,
32
- // A CIP-309 record carries integers, byte/text strings, arrays, maps and
32
+ // A Label 309 record carries integers, byte/text strings, arrays, maps and
33
33
  // `null` — and nothing else. Without these rejections the major-type-7
34
34
  // surface leaks into the decoder: a float16/32/64 that happens to hold an
35
35
  // integral value (e.g. 1.0) silently decodes to the integer 1 and passes
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cbor/errors.ts","../src/cbor/canonical.ts","../src/util/compare-ct.ts","../src/hash/merkle-sha2-256.ts","../src/merkle/leaves-list.ts"],"names":["DIGEST_LENGTH"],"mappings":";;;;;;;AAQO,IAAM,kBAAA,GAAN,cAAiC,KAAA,CAAM;AAAA,EACnC,IAAA;AAAA,EAET,WAAA,CAAY,IAAA,EAA8B,OAAA,EAAiB,OAAA,EAA+B;AACxF,IAAA,KAAA,CAAM,SAAS,OAAO,CAAA;AACtB,IAAA,IAAA,CAAK,IAAA,GAAO,oBAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AACF,CAAA;;;ACAO,SAAS,oBAAoB,KAAA,EAAuC;AACzE,EAAA,OAAO,OAAO,KAAA,EAAO;AAAA,IACnB,GAAA,EAAK,IAAA;AAAA,IACL,eAAA,EAAiB,IAAA;AAAA,IACjB,mBAAA,EAAqB,IAAA;AAAA,IACrB,QAAA,EAAU;AAAA,GACX,CAAA;AACH;AAEO,SAAS,oBAAoB,KAAA,EAA4B;AAC9D,EAAA,IAAI;AACF,IAAA,OAAO,OAAO,KAAA,EAAO;AAAA,MACnB,GAAG,gBAAA;AAAA,MACH,eAAA,EAAiB,IAAA;AAAA,MACjB,mBAAA,EAAqB,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAYrB,YAAA,EAAc,IAAA;AAAA,MACd,kBAAA,EAAoB,IAAA;AAAA,MACpB,eAAA,EAAiB,IAAA;AAAA,MACjB,YAAA,EAAc;AAAA,KACf,CAAA;AAAA,EACH,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,eAAe,KAAK,CAAA;AAAA,EAC5B;AACF;AAEA,SAAS,eAAe,KAAA,EAAoC;AAC1D,EAAA,MAAM,UAAU,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACrE,EAAA,MAAM,KAAA,GAAQ,QAAQ,WAAA,EAAY;AAUlC,EAAA,MAAM,eAAe,KAAA,CAAM,QAAA,CAAS,WAAW,CAAA,IAAK,KAAA,CAAM,SAAS,YAAY,CAAA;AAC/E,EAAA,MAAM,MAAA,GAAS,YAAA,GACX,CAAA,6DAAA,EAAgE,OAAO,CAAA,CAAA,GACvE,OAAA;AACJ,EAAA,OAAO,IAAI,mBAAmB,gBAAA,EAAkB,CAAA,oBAAA,EAAuB,MAAM,CAAA,CAAA,EAAI,EAAE,OAAO,CAAA;AAC5F;;;AChEO,SAAS,SAAA,CAAU,GAAe,CAAA,EAAwB;AAC/D,EAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,MAAA,EAAQ,OAAO,KAAA;AAClC,EAAA,IAAI,IAAA,GAAO,CAAA;AAIX,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAA,EAAA,EAAK,IAAA,IAAS,CAAA,CAAE,CAAC,CAAA,GAAgB,CAAA,CAAE,CAAC,CAAA;AAClE,EAAA,OAAO,IAAA,KAAS,CAAA;AAClB;ACKA,IAAM,WAAA,GAAc,CAAA;AACpB,IAAM,WAAA,GAAc,CAAA;AACpB,IAAM,aAAA,GAAgB,EAAA;AAEtB,SAAS,cAAA,CAAe,QAAmC,MAAA,EAAsB;AAC/E,EAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AACvB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,EAAG,MAAM,CAAA,6DAAA,CAA4D,CAAA;AAAA,EACvF;AACA,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,CAAO,QAAQ,CAAA,EAAA,EAAK;AACtC,IAAA,MAAM,IAAA,GAAO,OAAO,CAAC,CAAA;AACrB,IAAA,IAAI,EAAE,IAAA,YAAgB,UAAA,CAAA,IAAe,IAAA,CAAK,WAAW,aAAA,EAAe;AAClE,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,EAAG,MAAM,CAAA,OAAA,EAAU,CAAC,CAAA,uBAAA,EAA0B,aAAa,CAAA,cAAA,EACzD,IAAA,YAAgB,UAAA,GAAa,IAAA,CAAK,MAAA,GAAS,gBAC7C,CAAA;AAAA,OACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,kBAAkB,MAAA,EAA+C;AAC/E,EAAA,cAAA,CAAe,QAAQ,mBAAmB,CAAA;AAC1C,EAAA,OAAO,YAAA,CAAa,MAAA,EAAQ,CAAA,EAAG,MAAA,CAAO,MAAM,CAAA;AAC9C;AA+EA,SAAS,cAAc,CAAA,EAAmB;AACxC,EAAA,IAAI,CAAA,GAAI,CAAA;AACR,EAAA,OAAO,CAAA,GAAI,CAAA,GAAI,CAAA,EAAG,CAAA,IAAK,CAAA;AACvB,EAAA,OAAO,CAAA;AACT;AAEA,SAAS,SAAS,CAAA,EAA2B;AAC3C,EAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,CAAA,GAAI,EAAE,MAAM,CAAA;AACvC,EAAA,GAAA,CAAI,CAAC,CAAA,GAAI,WAAA;AACT,EAAA,GAAA,CAAI,GAAA,CAAI,GAAG,CAAC,CAAA;AACZ,EAAA,OAAO,OAAO,GAAG,CAAA;AACnB;AAEA,SAAS,QAAA,CAAS,MAAkB,KAAA,EAA+B;AACjE,EAAA,MAAM,MAAM,IAAI,UAAA,CAAW,IAAI,IAAA,CAAK,MAAA,GAAS,MAAM,MAAM,CAAA;AACzD,EAAA,GAAA,CAAI,CAAC,CAAA,GAAI,WAAA;AACT,EAAA,GAAA,CAAI,GAAA,CAAI,MAAM,CAAC,CAAA;AACf,EAAA,GAAA,CAAI,GAAA,CAAI,KAAA,EAAO,CAAA,GAAI,IAAA,CAAK,MAAM,CAAA;AAC9B,EAAA,OAAO,OAAO,GAAG,CAAA;AACnB;AAEA,SAAS,YAAA,CAAa,MAAA,EAAmC,KAAA,EAAe,GAAA,EAAyB;AAC/F,EAAA,MAAM,IAAI,GAAA,GAAM,KAAA;AAChB,EAAA,IAAI,MAAM,CAAA,EAAG;AACX,IAAA,OAAO,QAAA,CAAS,MAAA,CAAO,KAAK,CAAe,CAAA;AAAA,EAC7C;AACA,EAAA,MAAM,CAAA,GAAI,cAAc,CAAC,CAAA;AACzB,EAAA,MAAM,IAAA,GAAO,YAAA,CAAa,MAAA,EAAQ,KAAA,EAAO,QAAQ,CAAC,CAAA;AAClD,EAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,MAAA,EAAQ,KAAA,GAAQ,GAAG,GAAG,CAAA;AACjD,EAAA,OAAO,QAAA,CAAS,MAAM,KAAK,CAAA;AAC7B;;;AC9HO,IAAM,qBAAA,GAAwB;AACrC,IAAM,gBAAA,GAAmB,gBAAA;AACzB,IAAMA,cAAAA,GAAgB,EAAA;AACtB,IAAM,kBAAA,mBAAqB,IAAI,GAAA,CAAY,CAAC,qBAAqB,CAAC,CAAA;AAQ3D,IAAM,qBAAA,GAAN,cAAoC,KAAA,CAAM;AAAA,EACtC,IAAA;AAAA,EACT,WAAA,CAAY,MAAiC,OAAA,EAAkB;AAC7D,IAAA,KAAA,CAAM,UAAU,CAAA,EAAG,IAAI,CAAA,EAAA,EAAK,OAAO,KAAK,IAAI,CAAA;AAC5C,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,uBAAA;AAAA,EACd;AACF;AAiBO,SAAS,iBAAiB,IAAA,EAAwC;AACvE,EAAA,IAAI,EAAE,IAAA,CAAK,IAAA,YAAgB,eAAe,IAAA,CAAK,IAAA,CAAK,WAAWA,cAAAA,EAAe;AAC5E,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,gCAAA;AAAA,MACA,6BAA6BA,cAAa,CAAA,CAAA;AAAA,KAC5C;AAAA,EACF;AACA,EAAA,IAAI,IAAA,CAAK,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG;AAC1B,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,gCAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,aAA2B,EAAC;AAClC,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,MAAA,CAAO,QAAQ,CAAA,EAAA,EAAK;AAC3C,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA;AAC1B,IAAA,IAAI,EAAE,IAAA,YAAgB,UAAA,CAAA,IAAe,IAAA,CAAK,WAAWA,cAAAA,EAAe;AAClE,MAAA,MAAM,IAAI,qBAAA;AAAA,QACR,gCAAA;AAAA,QACA,CAAA,OAAA,EAAU,CAAC,CAAA,uBAAA,EAA0BA,cAAa,CAAA,CAAA;AAAA,OACpD;AAAA,IACF;AACA,IAAA,UAAA,CAAW,KAAK,IAAI,CAAA;AAAA,EACtB;AACA,EAAA,IAAI,KAAK,OAAA,KAAY,MAAA,IAAa,OAAO,IAAA,CAAK,YAAY,QAAA,EAAU;AAClE,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,gCAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,GAAA,GAA+B;AAAA,IACnC,MAAA,EAAQ,qBAAA;AAAA,IACR,QAAA,EAAU,gBAAA;AAAA,IACV,MAAM,IAAA,CAAK,IAAA;AAAA,IACX,MAAA,EAAQ,UAAA;AAAA,IACR,YAAY,UAAA,CAAW;AAAA,GACzB;AACA,EAAA,IAAI,IAAA,CAAK,YAAY,MAAA,EAAW;AAC9B,IAAA,GAAA,CAAI,UAAU,IAAI,IAAA,CAAK,OAAA;AAAA,EACzB;AACA,EAAA,OAAO,oBAAoB,GAAY,CAAA;AACzC;AAEO,SAAS,iBAAiB,KAAA,EAAsC;AACrE,EAAA,MAAM,OAAA,GAAU,oBAAoB,KAAK,CAAA;AACzC,EAAA,IAAI,OAAO,YAAY,QAAA,IAAY,OAAA,KAAY,QAAQ,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC7E,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,gCAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,CAAA,GAAI,OAAA;AAEV,EAAA,MAAM,MAAA,GAAS,EAAE,QAAQ,CAAA;AACzB,EAAA,IAAI,OAAO,WAAW,QAAA,EAAU;AAC9B,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,gCAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACA,EAAA,IAAI,CAAC,kBAAA,CAAmB,GAAA,CAAI,MAAM,CAAA,EAAG;AACnC,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,yCAAA;AAAA,MACA,WAAW,MAAM,CAAA,8BAAA;AAAA,KACnB;AAAA,EACF;AAEA,EAAA,MAAM,OAAA,GAAU,EAAE,UAAU,CAAA;AAC5B,EAAA,IAAI,YAAY,gBAAA,EAAkB;AAChC,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,gCAAA;AAAA,MACA,CAAA,UAAA,EAAa,MAAA,CAAO,OAAO,CAAC,aAAa,gBAAgB,CAAA,CAAA;AAAA,KAC3D;AAAA,EACF;AAEA,EAAA,MAAM,IAAA,GAAO,EAAE,MAAM,CAAA;AACrB,EAAA,IAAI,EAAE,IAAA,YAAgB,UAAA,CAAA,IAAe,IAAA,CAAK,WAAWA,cAAAA,EAAe;AAClE,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,gCAAA;AAAA,MACA,kBAAkBA,cAAa,CAAA,iBAAA;AAAA,KACjC;AAAA,EACF;AAEA,EAAA,MAAM,SAAA,GAAY,EAAE,QAAQ,CAAA;AAC5B,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,SAAS,CAAA,IAAK,SAAA,CAAU,SAAS,CAAA,EAAG;AACrD,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,gCAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,SAAuB,EAAC;AAC9B,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,SAAA,CAAU,QAAQ,CAAA,EAAA,EAAK;AACzC,IAAA,MAAM,IAAA,GAAO,UAAU,CAAC,CAAA;AACxB,IAAA,IAAI,EAAE,IAAA,YAAgB,UAAA,CAAA,IAAe,IAAA,CAAK,WAAWA,cAAAA,EAAe;AAClE,MAAA,MAAM,IAAI,qBAAA;AAAA,QACR,gCAAA;AAAA,QACA,CAAA,OAAA,EAAU,CAAC,CAAA,YAAA,EAAeA,cAAa,CAAA,iBAAA;AAAA,OACzC;AAAA,IACF;AACA,IAAA,MAAA,CAAO,KAAK,IAAI,CAAA;AAAA,EAClB;AAEA,EAAA,MAAM,YAAA,GAAe,EAAE,YAAY,CAAA;AACnC,EAAA,IAAI,SAAA;AACJ,EAAA,IAAI,OAAO,iBAAiB,QAAA,IAAY,MAAA,CAAO,UAAU,YAAY,CAAA,IAAK,gBAAgB,CAAA,EAAG;AAC3F,IAAA,SAAA,GAAY,YAAA;AAAA,EACd,CAAA,MAAA,IAAW,OAAO,YAAA,KAAiB,QAAA,IAAY,gBAAgB,EAAA,EAAI;AACjE,IAAA,IAAI,YAAA,GAAe,MAAA,CAAO,MAAA,CAAO,gBAAgB,CAAA,EAAG;AAClD,MAAA,MAAM,IAAI,qBAAA;AAAA,QACR,gCAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AACA,IAAA,SAAA,GAAY,OAAO,YAAY,CAAA;AAAA,EACjC,CAAA,MAAO;AACL,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,gCAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACA,EAAA,IAAI,MAAA,CAAO,WAAW,SAAA,EAAW;AAC/B,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,mCAAA;AAAA,MACA,CAAA,eAAA,EAAkB,MAAA,CAAO,MAAM,CAAA,iBAAA,EAAoB,SAAS,CAAA,CAAA;AAAA,KAC9D;AAAA,EACF;AAEA,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI,CAAA,CAAE,UAAU,CAAA,KAAM,MAAA,EAAW;AAC/B,IAAA,IAAI,OAAO,CAAA,CAAE,UAAU,CAAA,KAAM,QAAA,EAAU;AACrC,MAAA,MAAM,IAAI,qBAAA;AAAA,QACR,gCAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AACA,IAAA,OAAA,GAAU,EAAE,UAAU,CAAA;AAAA,EACxB;AAEA,EAAA,MAAM,UAAA,GAAa,kBAAkB,MAAM,CAAA;AAC3C,EAAA,IAAI,CAAC,SAAA,CAAU,UAAA,EAAY,IAAI,CAAA,EAAG;AAChC,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,sBAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAEA,EAAA,MAAM,GAAA,GAAyB;AAAA,IAC7B,MAAA,EAAQ,qBAAA;AAAA,IACR,OAAA,EAAS,gBAAA;AAAA,IACT,IAAA;AAAA,IACA,MAAA;AAAA,IACA,SAAA;AAAA,IACA,GAAI,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,KAAY;AAAC,GAC7C;AACA,EAAA,OAAO,GAAA;AACT","file":"merkle.js","sourcesContent":["// Every canonical-CBOR decode violation collapses to the single public CIP-309\n// taxonomy code MALFORMED_CBOR: indefinite-length (streaming) items, duplicate\n// keys, unsorted keys, non-minimal integer encodings, and invalid UTF-8 in text\n// strings. The taxonomy intentionally has one code for all of these; the\n// specific cause survives in the human-readable error message, not as a\n// separate code.\nexport type CanonicalCborErrorCode = 'MALFORMED_CBOR';\n\nexport class CanonicalCborError extends Error {\n readonly code: CanonicalCborErrorCode;\n\n constructor(code: CanonicalCborErrorCode, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = 'CanonicalCborError';\n this.code = code;\n }\n}\n","import { cdeDecodeOptions, decode, encode } from 'cbor2';\nimport { sortCoreDeterministic } from 'cbor2/sorts';\n\nimport { CanonicalCborError } from './errors';\n\nexport type CanonicalCborValue =\n | null\n | boolean\n | number\n | bigint\n | string\n | Uint8Array\n | readonly CanonicalCborValue[]\n | { readonly [key: string]: CanonicalCborValue }\n | ReadonlyMap<string | number, CanonicalCborValue>;\n\nexport function encodeCanonicalCbor(value: CanonicalCborValue): Uint8Array {\n return encode(value, {\n cde: true,\n collapseBigInts: true,\n rejectDuplicateKeys: true,\n sortKeys: sortCoreDeterministic,\n });\n}\n\nexport function decodeCanonicalCbor(bytes: Uint8Array): unknown {\n try {\n return decode(bytes, {\n ...cdeDecodeOptions,\n rejectStreaming: true,\n rejectDuplicateKeys: true,\n // A CIP-309 record carries integers, byte/text strings, arrays, maps and\n // `null` — and nothing else. Without these rejections the major-type-7\n // surface leaks into the decoder: a float16/32/64 that happens to hold an\n // integral value (e.g. 1.0) silently decodes to the integer 1 and passes\n // a `z.literal(1)` / Number.isInteger schema check, so two byte strings\n // that are NOT byte-identical canonicalise to the same record. That\n // breaks the cross-implementation parity invariant (the Python twin\n // already rejects non-integer `v` / `enc.scheme` outright). Reject the\n // whole non-record surface — floats, negative zero, undefined, and\n // non-{true,false,null} simple values — so any such input surfaces as\n // MALFORMED_CBOR via mapDecodeError rather than decoding to a look-alike.\n rejectFloats: true,\n rejectNegativeZero: true,\n rejectUndefined: true,\n rejectSimple: true,\n });\n } catch (cause) {\n throw mapDecodeError(cause);\n }\n}\n\nfunction mapDecodeError(cause: unknown): CanonicalCborError {\n const message = cause instanceof Error ? cause.message : String(cause);\n const lower = message.toLowerCase();\n // Every canonical-decode violation collapses to the single public taxonomy\n // code MALFORMED_CBOR: indefinite-length (streaming) items, duplicate keys,\n // non-canonical (unsorted) key ordering, non-minimal integer encodings, and\n // invalid UTF-8 in text strings. cbor2 raises the SAME \"Duplicate or out of\n // order key\" message for both true duplicates AND distinct-but-unsorted keys,\n // so the two are indistinguishable by message — and per the CIP-309 taxonomy\n // both belong under MALFORMED_CBOR anyway. The specific cause survives in the\n // human-readable message below; for indefinite-length we state it explicitly\n // so the diagnostic is not lost when the code is collapsed.\n const isIndefinite = lower.includes('streaming') || lower.includes('indefinite');\n const detail = isIndefinite\n ? `indefinite-length items are not permitted in canonical CBOR: ${message}`\n : message;\n return new CanonicalCborError('MALFORMED_CBOR', `cbor decode failed: ${detail}`, { cause });\n}\n","// Isomorphic constant-time byte-equality. crypto-core is browser-safe by\n// design, so we cannot import `node:crypto.timingSafeEqual` — webpack rejects\n// the `node:` scheme in the browser bundle. A pure-JS XOR loop is constant-time\n// for equal-length inputs; length mismatch is a deliberate early-return (the\n// API surface itself leaks length, same as node's timingSafeEqual which throws).\nexport function compareCt(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n // Lengths are equal and `i` stays in-bounds, so both indexes are always\n // defined — no nullish guard is needed (and one would read as a guard for\n // an impossible case).\n for (let i = 0; i < a.length; i++) diff |= (a[i] as number) ^ (b[i] as number);\n return diff === 0;\n}\n","// RFC 9162 §2.1.1 binary Merkle tree under SHA-256.\n// This implements the algorithm tier identified on the wire as the\n// `rfc9162-sha256` OPT-INFO; the record's `merkle[]` field carries the proof.\n//\n// Construction (RFC 9162 §2.1.1):\n// - Single leaf: MTH({d_0}) = SHA-256(0x00 || d_0)\n// - Internal node: MTH(L) = SHA-256(0x01 || MTH(L[0:k]) || MTH(L[k:n]))\n// where k = largest power of 2 strictly less than n.\n// - Empty trees (n == 0) are FORBIDDEN.\n// - The 0x00 leaf / 0x01 internal prefixes prevent the CVE-2012-2459\n// leaf-vs-internal collision family.\n\nimport { sha256 } from '@noble/hashes/sha2.js';\n\nimport { compareCt } from '../util/compare-ct';\n\nexport const MERKLE_ALG_ID = 'rfc9162-sha256' as const;\n\nconst LEAF_PREFIX = 0x00;\nconst NODE_PREFIX = 0x01;\nconst DIGEST_LENGTH = 32;\n\nfunction validateLeaves(leaves: ReadonlyArray<Uint8Array>, fnName: string): void {\n if (leaves.length === 0) {\n throw new Error(`${fnName}: empty leaf list (n == 0 is forbidden by RFC 9162 §2.1.1)`);\n }\n for (let i = 0; i < leaves.length; i++) {\n const leaf = leaves[i];\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {\n throw new Error(\n `${fnName}: leaf[${i}] must be a Uint8Array(${DIGEST_LENGTH}); got length ${\n leaf instanceof Uint8Array ? leaf.length : 'non-Uint8Array'\n }`,\n );\n }\n }\n}\n\nexport function merkleSha2256Root(leaves: ReadonlyArray<Uint8Array>): Uint8Array {\n validateLeaves(leaves, 'merkleSha2256Root');\n return mthRecursive(leaves, 0, leaves.length);\n}\n\nexport function merkleSha2256InclusionProof(\n leaves: ReadonlyArray<Uint8Array>,\n index: number,\n): Uint8Array[] {\n validateLeaves(leaves, 'merkleSha2256InclusionProof');\n if (!Number.isInteger(index) || index < 0 || index >= leaves.length) {\n throw new Error(\n `merkleSha2256InclusionProof: index ${index} out of range [0, ${leaves.length})`,\n );\n }\n return auditPath(leaves, index, 0, leaves.length);\n}\n\n/**\n * Verify an inclusion proof per RFC 9162 §2.1.3.2 (iterative form).\n *\n * `proof` is ordered leaf-to-root: `proof[0]` is the sibling at the leaf\n * level, `proof[m-1]` is the top-level sibling. The fold uses the\n * `sn`/`fn` tracking from RFC 9162: `sn` is the leaf index within the\n * current subtree, `fn` is (subtree_size - 1). At each step, `sn` odd\n * OR `sn == fn` means the current node is a right child (sibling on\n * the left); otherwise it is a left child (sibling on the right).\n * Both shift right by one each iteration. This handles non-power-of-2\n * sizes including the \"promote a lone right subtree\" cases.\n */\nexport function merkleSha2256VerifyInclusion(\n leaf: Uint8Array,\n index: number,\n treeSize: number,\n proof: ReadonlyArray<Uint8Array>,\n root: Uint8Array,\n): boolean {\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) return false;\n if (!(root instanceof Uint8Array) || root.length !== DIGEST_LENGTH) return false;\n if (\n !Number.isInteger(index) ||\n !Number.isInteger(treeSize) ||\n treeSize < 1 ||\n index < 0 ||\n index >= treeSize\n ) {\n return false;\n }\n for (let i = 0; i < proof.length; i++) {\n const sibling = proof[i];\n if (!(sibling instanceof Uint8Array) || sibling.length !== DIGEST_LENGTH) {\n return false;\n }\n }\n\n if (treeSize === 1) {\n if (proof.length !== 0 || index !== 0) return false;\n return compareCt(hashLeaf(leaf), root);\n }\n\n let h = hashLeaf(leaf);\n let sn = index;\n let fn = treeSize - 1;\n for (let i = 0; i < proof.length; i++) {\n if (fn === 0) return false;\n const sibling = proof[i] as Uint8Array;\n if ((sn & 1) === 1 || sn === fn) {\n h = hashNode(sibling, h);\n while ((sn & 1) === 0 && sn !== 0) {\n sn >>>= 1;\n fn >>>= 1;\n }\n } else {\n h = hashNode(h, sibling);\n }\n sn >>>= 1;\n fn >>>= 1;\n }\n if (fn !== 0) return false;\n return compareCt(h, root);\n}\n\nfunction largestPow2Lt(n: number): number {\n let k = 1;\n while (k * 2 < n) k *= 2;\n return k;\n}\n\nfunction hashLeaf(d: Uint8Array): Uint8Array {\n const buf = new Uint8Array(1 + d.length);\n buf[0] = LEAF_PREFIX;\n buf.set(d, 1);\n return sha256(buf);\n}\n\nfunction hashNode(left: Uint8Array, right: Uint8Array): Uint8Array {\n const buf = new Uint8Array(1 + left.length + right.length);\n buf[0] = NODE_PREFIX;\n buf.set(left, 1);\n buf.set(right, 1 + left.length);\n return sha256(buf);\n}\n\nfunction mthRecursive(leaves: ReadonlyArray<Uint8Array>, start: number, end: number): Uint8Array {\n const n = end - start;\n if (n === 1) {\n return hashLeaf(leaves[start] as Uint8Array);\n }\n const k = largestPow2Lt(n);\n const left = mthRecursive(leaves, start, start + k);\n const right = mthRecursive(leaves, start + k, end);\n return hashNode(left, right);\n}\n\nfunction auditPath(\n leaves: ReadonlyArray<Uint8Array>,\n i: number,\n start: number,\n end: number,\n): Uint8Array[] {\n const n = end - start;\n if (n === 1) return [];\n const k = largestPow2Lt(n);\n if (i < k) {\n const subPath = auditPath(leaves, i, start, start + k);\n subPath.push(mthRecursive(leaves, start + k, end));\n return subPath;\n }\n const subPath = auditPath(leaves, i - k, start + k, end);\n subPath.push(mthRecursive(leaves, start, start + k));\n return subPath;\n}\n","// Canonical-CBOR codec for the off-chain Merkle leaves-list artefact.\n// The on-chain `merkle[]` field binds to this file via `uris[]` / `leaf_count`;\n// the file itself carries the full leaf set. Canonical CBOR is RFC 8949 §4.2.1.\n//\n// CDDL:\n//\n// leaves-list = {\n// \"format\": \"cardano-poe-merkle-leaves-v1\",\n// \"tree_alg\": \"rfc9162-sha256\",\n// \"root\": bytes .size 32,\n// \"leaves\": [ + bytes .size 32 ],\n// \"leaf_count\": uint,\n// ? \"leaf_alg\": tstr,\n// }\n//\n// Canonical ordering is bytewise-lexicographic on encoded map keys (RFC 8949\n// §4.2.1) so the wire-key order is fixed by `cde:true` regardless of insertion\n// order: root (4B) < format (6B) < leaves (6B) < leaf_alg (8B) < tree_alg (8B)\n// < leaf_count (10B).\n\nimport { decodeCanonicalCbor, encodeCanonicalCbor } from '../cbor/canonical';\nimport { compareCt } from '../util/compare-ct';\nimport { merkleSha2256Root } from '../hash/merkle-sha2-256';\n\nexport const LEAVES_LIST_FORMAT_V1 = 'cardano-poe-merkle-leaves-v1' as const;\nconst TREE_ALG_RFC9162 = 'rfc9162-sha256' as const;\nconst DIGEST_LENGTH = 32;\nconst REGISTERED_FORMATS = new Set<string>([LEAVES_LIST_FORMAT_V1]);\n\nexport type MerkleLeavesListErrorCode =\n | 'SCHEMA_MERKLE_LEAVES_FORMAT_UNSUPPORTED'\n | 'SCHEMA_MERKLE_LEAVES_MALFORMED'\n | 'SCHEMA_MERKLE_LEAF_COUNT_MISMATCH'\n | 'MERKLE_ROOT_MISMATCH';\n\nexport class MerkleLeavesListError extends Error {\n readonly code: MerkleLeavesListErrorCode;\n constructor(code: MerkleLeavesListErrorCode, message?: string) {\n super(message ? `${code}: ${message}` : code);\n this.code = code;\n this.name = 'MerkleLeavesListError';\n }\n}\n\nexport interface EncodeLeavesListArgs {\n readonly leaves: ReadonlyArray<Uint8Array>;\n readonly root: Uint8Array;\n readonly leafAlg?: string;\n}\n\nexport interface DecodedLeavesList {\n readonly format: typeof LEAVES_LIST_FORMAT_V1;\n readonly treeAlg: typeof TREE_ALG_RFC9162;\n readonly root: Uint8Array;\n readonly leaves: Uint8Array[];\n readonly leafCount: number;\n readonly leafAlg?: string;\n}\n\nexport function encodeLeavesList(args: EncodeLeavesListArgs): Uint8Array {\n if (!(args.root instanceof Uint8Array) || args.root.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `root must be a Uint8Array(${DIGEST_LENGTH})`,\n );\n }\n if (args.leaves.length < 1) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaves array must be non-empty',\n );\n }\n const leavesCopy: Uint8Array[] = [];\n for (let i = 0; i < args.leaves.length; i++) {\n const leaf = args.leaves[i];\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `leaves[${i}] must be a Uint8Array(${DIGEST_LENGTH})`,\n );\n }\n leavesCopy.push(leaf);\n }\n if (args.leafAlg !== undefined && typeof args.leafAlg !== 'string') {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_alg must be a string when present',\n );\n }\n const map: Record<string, unknown> = {\n format: LEAVES_LIST_FORMAT_V1,\n tree_alg: TREE_ALG_RFC9162,\n root: args.root,\n leaves: leavesCopy,\n leaf_count: leavesCopy.length,\n };\n if (args.leafAlg !== undefined) {\n map['leaf_alg'] = args.leafAlg;\n }\n return encodeCanonicalCbor(map as never);\n}\n\nexport function decodeLeavesList(bytes: Uint8Array): DecodedLeavesList {\n const decoded = decodeCanonicalCbor(bytes);\n if (typeof decoded !== 'object' || decoded === null || Array.isArray(decoded)) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaves-list MUST be a CBOR map',\n );\n }\n const m = decoded as Record<string, unknown>;\n\n const format = m['format'];\n if (typeof format !== 'string') {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'format must be a text string',\n );\n }\n if (!REGISTERED_FORMATS.has(format)) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_FORMAT_UNSUPPORTED',\n `format '${format}' is not in the registered set`,\n );\n }\n\n const treeAlg = m['tree_alg'];\n if (treeAlg !== TREE_ALG_RFC9162) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `tree_alg '${String(treeAlg)}' is not '${TREE_ALG_RFC9162}'`,\n );\n }\n\n const root = m['root'];\n if (!(root instanceof Uint8Array) || root.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `root must be a ${DIGEST_LENGTH}-byte byte string`,\n );\n }\n\n const leavesRaw = m['leaves'];\n if (!Array.isArray(leavesRaw) || leavesRaw.length < 1) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaves must be a non-empty array',\n );\n }\n const leaves: Uint8Array[] = [];\n for (let i = 0; i < leavesRaw.length; i++) {\n const leaf = leavesRaw[i];\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `leaves[${i}] must be a ${DIGEST_LENGTH}-byte byte string`,\n );\n }\n leaves.push(leaf);\n }\n\n const leafCountRaw = m['leaf_count'];\n let leafCount: number;\n if (typeof leafCountRaw === 'number' && Number.isInteger(leafCountRaw) && leafCountRaw >= 0) {\n leafCount = leafCountRaw;\n } else if (typeof leafCountRaw === 'bigint' && leafCountRaw >= 0n) {\n if (leafCountRaw > BigInt(Number.MAX_SAFE_INTEGER)) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_count exceeds Number.MAX_SAFE_INTEGER',\n );\n }\n leafCount = Number(leafCountRaw);\n } else {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_count must be a non-negative CBOR uint',\n );\n }\n if (leaves.length !== leafCount) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAF_COUNT_MISMATCH',\n `leaves.length (${leaves.length}) != leaf_count (${leafCount})`,\n );\n }\n\n let leafAlg: string | undefined;\n if (m['leaf_alg'] !== undefined) {\n if (typeof m['leaf_alg'] !== 'string') {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_alg must be a text string when present',\n );\n }\n leafAlg = m['leaf_alg'];\n }\n\n const recomputed = merkleSha2256Root(leaves);\n if (!compareCt(recomputed, root)) {\n throw new MerkleLeavesListError(\n 'MERKLE_ROOT_MISMATCH',\n 'leaves recompute does not match declared root',\n );\n }\n\n const out: DecodedLeavesList = {\n format: LEAVES_LIST_FORMAT_V1,\n treeAlg: TREE_ALG_RFC9162,\n root,\n leaves,\n leafCount,\n ...(leafAlg !== undefined ? { leafAlg } : {}),\n };\n return out;\n}\n"]}
1
+ {"version":3,"sources":["../src/cbor/errors.ts","../src/cbor/canonical.ts","../src/util/compare-ct.ts","../src/hash/merkle-sha2-256.ts","../src/merkle/leaves-list.ts"],"names":["DIGEST_LENGTH"],"mappings":"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","file":"merkle.js","sourcesContent":["// Every canonical-CBOR decode violation collapses to the single public Label 309\n// taxonomy code MALFORMED_CBOR: indefinite-length (streaming) items, duplicate\n// keys, unsorted keys, non-minimal integer encodings, and invalid UTF-8 in text\n// strings. The taxonomy intentionally has one code for all of these; the\n// specific cause survives in the human-readable error message, not as a\n// separate code.\nexport type CanonicalCborErrorCode = 'MALFORMED_CBOR';\n\nexport class CanonicalCborError extends Error {\n readonly code: CanonicalCborErrorCode;\n\n constructor(code: CanonicalCborErrorCode, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = 'CanonicalCborError';\n this.code = code;\n }\n}\n","import { cdeDecodeOptions, decode, encode } from 'cbor2';\nimport { sortCoreDeterministic } from 'cbor2/sorts';\n\nimport { CanonicalCborError } from './errors';\n\nexport type CanonicalCborValue =\n | null\n | boolean\n | number\n | bigint\n | string\n | Uint8Array\n | readonly CanonicalCborValue[]\n | { readonly [key: string]: CanonicalCborValue }\n | ReadonlyMap<string | number, CanonicalCborValue>;\n\nexport function encodeCanonicalCbor(value: CanonicalCborValue): Uint8Array {\n return encode(value, {\n cde: true,\n collapseBigInts: true,\n rejectDuplicateKeys: true,\n sortKeys: sortCoreDeterministic,\n });\n}\n\nexport function decodeCanonicalCbor(bytes: Uint8Array): unknown {\n try {\n return decode(bytes, {\n ...cdeDecodeOptions,\n rejectStreaming: true,\n rejectDuplicateKeys: true,\n // A Label 309 record carries integers, byte/text strings, arrays, maps and\n // `null` — and nothing else. Without these rejections the major-type-7\n // surface leaks into the decoder: a float16/32/64 that happens to hold an\n // integral value (e.g. 1.0) silently decodes to the integer 1 and passes\n // a `z.literal(1)` / Number.isInteger schema check, so two byte strings\n // that are NOT byte-identical canonicalise to the same record. That\n // breaks the cross-implementation parity invariant (the Python twin\n // already rejects non-integer `v` / `enc.scheme` outright). Reject the\n // whole non-record surface — floats, negative zero, undefined, and\n // non-{true,false,null} simple values — so any such input surfaces as\n // MALFORMED_CBOR via mapDecodeError rather than decoding to a look-alike.\n rejectFloats: true,\n rejectNegativeZero: true,\n rejectUndefined: true,\n rejectSimple: true,\n });\n } catch (cause) {\n throw mapDecodeError(cause);\n }\n}\n\nfunction mapDecodeError(cause: unknown): CanonicalCborError {\n const message = cause instanceof Error ? cause.message : String(cause);\n const lower = message.toLowerCase();\n // Every canonical-decode violation collapses to the single public taxonomy\n // code MALFORMED_CBOR: indefinite-length (streaming) items, duplicate keys,\n // non-canonical (unsorted) key ordering, non-minimal integer encodings, and\n // invalid UTF-8 in text strings. cbor2 raises the SAME \"Duplicate or out of\n // order key\" message for both true duplicates AND distinct-but-unsorted keys,\n // so the two are indistinguishable by message — and per the Label 309 taxonomy\n // both belong under MALFORMED_CBOR anyway. The specific cause survives in the\n // human-readable message below; for indefinite-length we state it explicitly\n // so the diagnostic is not lost when the code is collapsed.\n const isIndefinite = lower.includes('streaming') || lower.includes('indefinite');\n const detail = isIndefinite\n ? `indefinite-length items are not permitted in canonical CBOR: ${message}`\n : message;\n return new CanonicalCborError('MALFORMED_CBOR', `cbor decode failed: ${detail}`, { cause });\n}\n","// Isomorphic constant-time byte-equality. crypto-core is browser-safe by\n// design, so we cannot import `node:crypto.timingSafeEqual` — webpack rejects\n// the `node:` scheme in the browser bundle. A pure-JS XOR loop is constant-time\n// for equal-length inputs; length mismatch is a deliberate early-return (the\n// API surface itself leaks length, same as node's timingSafeEqual which throws).\nexport function compareCt(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n // Lengths are equal and `i` stays in-bounds, so both indexes are always\n // defined — no nullish guard is needed (and one would read as a guard for\n // an impossible case).\n for (let i = 0; i < a.length; i++) diff |= (a[i] as number) ^ (b[i] as number);\n return diff === 0;\n}\n","// RFC 9162 §2.1.1 binary Merkle tree under SHA-256.\n// This implements the algorithm tier identified on the wire as the\n// `rfc9162-sha256` OPT-INFO; the record's `merkle[]` field carries the proof.\n//\n// Construction (RFC 9162 §2.1.1):\n// - Single leaf: MTH({d_0}) = SHA-256(0x00 || d_0)\n// - Internal node: MTH(L) = SHA-256(0x01 || MTH(L[0:k]) || MTH(L[k:n]))\n// where k = largest power of 2 strictly less than n.\n// - Empty trees (n == 0) are FORBIDDEN.\n// - The 0x00 leaf / 0x01 internal prefixes prevent the CVE-2012-2459\n// leaf-vs-internal collision family.\n\nimport { sha256 } from '@noble/hashes/sha2.js';\n\nimport { compareCt } from '../util/compare-ct';\n\nexport const MERKLE_ALG_ID = 'rfc9162-sha256' as const;\n\nconst LEAF_PREFIX = 0x00;\nconst NODE_PREFIX = 0x01;\nconst DIGEST_LENGTH = 32;\n\nfunction validateLeaves(leaves: ReadonlyArray<Uint8Array>, fnName: string): void {\n if (leaves.length === 0) {\n throw new Error(`${fnName}: empty leaf list (n == 0 is forbidden by RFC 9162 §2.1.1)`);\n }\n for (let i = 0; i < leaves.length; i++) {\n const leaf = leaves[i];\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {\n throw new Error(\n `${fnName}: leaf[${i}] must be a Uint8Array(${DIGEST_LENGTH}); got length ${\n leaf instanceof Uint8Array ? leaf.length : 'non-Uint8Array'\n }`,\n );\n }\n }\n}\n\nexport function merkleSha2256Root(leaves: ReadonlyArray<Uint8Array>): Uint8Array {\n validateLeaves(leaves, 'merkleSha2256Root');\n return mthRecursive(leaves, 0, leaves.length);\n}\n\nexport function merkleSha2256InclusionProof(\n leaves: ReadonlyArray<Uint8Array>,\n index: number,\n): Uint8Array[] {\n validateLeaves(leaves, 'merkleSha2256InclusionProof');\n if (!Number.isInteger(index) || index < 0 || index >= leaves.length) {\n throw new Error(\n `merkleSha2256InclusionProof: index ${index} out of range [0, ${leaves.length})`,\n );\n }\n return auditPath(leaves, index, 0, leaves.length);\n}\n\n/**\n * Verify an inclusion proof per RFC 9162 §2.1.3.2 (iterative form).\n *\n * `proof` is ordered leaf-to-root: `proof[0]` is the sibling at the leaf\n * level, `proof[m-1]` is the top-level sibling. The fold uses the\n * `sn`/`fn` tracking from RFC 9162: `sn` is the leaf index within the\n * current subtree, `fn` is (subtree_size - 1). At each step, `sn` odd\n * OR `sn == fn` means the current node is a right child (sibling on\n * the left); otherwise it is a left child (sibling on the right).\n * Both shift right by one each iteration. This handles non-power-of-2\n * sizes including the \"promote a lone right subtree\" cases.\n */\nexport function merkleSha2256VerifyInclusion(\n leaf: Uint8Array,\n index: number,\n treeSize: number,\n proof: ReadonlyArray<Uint8Array>,\n root: Uint8Array,\n): boolean {\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) return false;\n if (!(root instanceof Uint8Array) || root.length !== DIGEST_LENGTH) return false;\n if (\n !Number.isInteger(index) ||\n !Number.isInteger(treeSize) ||\n treeSize < 1 ||\n index < 0 ||\n index >= treeSize\n ) {\n return false;\n }\n for (let i = 0; i < proof.length; i++) {\n const sibling = proof[i];\n if (!(sibling instanceof Uint8Array) || sibling.length !== DIGEST_LENGTH) {\n return false;\n }\n }\n\n if (treeSize === 1) {\n if (proof.length !== 0 || index !== 0) return false;\n return compareCt(hashLeaf(leaf), root);\n }\n\n let h = hashLeaf(leaf);\n let sn = index;\n let fn = treeSize - 1;\n for (let i = 0; i < proof.length; i++) {\n if (fn === 0) return false;\n const sibling = proof[i] as Uint8Array;\n if ((sn & 1) === 1 || sn === fn) {\n h = hashNode(sibling, h);\n while ((sn & 1) === 0 && sn !== 0) {\n sn >>>= 1;\n fn >>>= 1;\n }\n } else {\n h = hashNode(h, sibling);\n }\n sn >>>= 1;\n fn >>>= 1;\n }\n if (fn !== 0) return false;\n return compareCt(h, root);\n}\n\nfunction largestPow2Lt(n: number): number {\n let k = 1;\n while (k * 2 < n) k *= 2;\n return k;\n}\n\nfunction hashLeaf(d: Uint8Array): Uint8Array {\n const buf = new Uint8Array(1 + d.length);\n buf[0] = LEAF_PREFIX;\n buf.set(d, 1);\n return sha256(buf);\n}\n\nfunction hashNode(left: Uint8Array, right: Uint8Array): Uint8Array {\n const buf = new Uint8Array(1 + left.length + right.length);\n buf[0] = NODE_PREFIX;\n buf.set(left, 1);\n buf.set(right, 1 + left.length);\n return sha256(buf);\n}\n\nfunction mthRecursive(leaves: ReadonlyArray<Uint8Array>, start: number, end: number): Uint8Array {\n const n = end - start;\n if (n === 1) {\n return hashLeaf(leaves[start] as Uint8Array);\n }\n const k = largestPow2Lt(n);\n const left = mthRecursive(leaves, start, start + k);\n const right = mthRecursive(leaves, start + k, end);\n return hashNode(left, right);\n}\n\nfunction auditPath(\n leaves: ReadonlyArray<Uint8Array>,\n i: number,\n start: number,\n end: number,\n): Uint8Array[] {\n const n = end - start;\n if (n === 1) return [];\n const k = largestPow2Lt(n);\n if (i < k) {\n const subPath = auditPath(leaves, i, start, start + k);\n subPath.push(mthRecursive(leaves, start + k, end));\n return subPath;\n }\n const subPath = auditPath(leaves, i - k, start + k, end);\n subPath.push(mthRecursive(leaves, start, start + k));\n return subPath;\n}\n","// Canonical-CBOR codec for the off-chain Merkle leaves-list artefact.\n// The on-chain `merkle[]` field binds to this file via `uris[]` / `leaf_count`;\n// the file itself carries the full leaf set. Canonical CBOR is RFC 8949 §4.2.1.\n//\n// CDDL:\n//\n// leaves-list = {\n// \"format\": \"cardano-poe-merkle-leaves-v1\",\n// \"tree_alg\": \"rfc9162-sha256\",\n// \"root\": bytes .size 32,\n// \"leaves\": [ + bytes .size 32 ],\n// \"leaf_count\": uint,\n// ? \"leaf_alg\": tstr,\n// }\n//\n// Canonical ordering is bytewise-lexicographic on encoded map keys (RFC 8949\n// §4.2.1) so the wire-key order is fixed by `cde:true` regardless of insertion\n// order: root (4B) < format (6B) < leaves (6B) < leaf_alg (8B) < tree_alg (8B)\n// < leaf_count (10B).\n\nimport { decodeCanonicalCbor, encodeCanonicalCbor } from '../cbor/canonical';\nimport { compareCt } from '../util/compare-ct';\nimport { merkleSha2256Root } from '../hash/merkle-sha2-256';\n\nexport const LEAVES_LIST_FORMAT_V1 = 'cardano-poe-merkle-leaves-v1' as const;\nconst TREE_ALG_RFC9162 = 'rfc9162-sha256' as const;\nconst DIGEST_LENGTH = 32;\nconst REGISTERED_FORMATS = new Set<string>([LEAVES_LIST_FORMAT_V1]);\n\nexport type MerkleLeavesListErrorCode =\n | 'SCHEMA_MERKLE_LEAVES_FORMAT_UNSUPPORTED'\n | 'SCHEMA_MERKLE_LEAVES_MALFORMED'\n | 'SCHEMA_MERKLE_LEAF_COUNT_MISMATCH'\n | 'MERKLE_ROOT_MISMATCH';\n\nexport class MerkleLeavesListError extends Error {\n readonly code: MerkleLeavesListErrorCode;\n constructor(code: MerkleLeavesListErrorCode, message?: string) {\n super(message ? `${code}: ${message}` : code);\n this.code = code;\n this.name = 'MerkleLeavesListError';\n }\n}\n\nexport interface EncodeLeavesListArgs {\n readonly leaves: ReadonlyArray<Uint8Array>;\n readonly root: Uint8Array;\n readonly leafAlg?: string;\n}\n\nexport interface DecodedLeavesList {\n readonly format: typeof LEAVES_LIST_FORMAT_V1;\n readonly treeAlg: typeof TREE_ALG_RFC9162;\n readonly root: Uint8Array;\n readonly leaves: Uint8Array[];\n readonly leafCount: number;\n readonly leafAlg?: string;\n}\n\nexport function encodeLeavesList(args: EncodeLeavesListArgs): Uint8Array {\n if (!(args.root instanceof Uint8Array) || args.root.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `root must be a Uint8Array(${DIGEST_LENGTH})`,\n );\n }\n if (args.leaves.length < 1) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaves array must be non-empty',\n );\n }\n const leavesCopy: Uint8Array[] = [];\n for (let i = 0; i < args.leaves.length; i++) {\n const leaf = args.leaves[i];\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `leaves[${i}] must be a Uint8Array(${DIGEST_LENGTH})`,\n );\n }\n leavesCopy.push(leaf);\n }\n if (args.leafAlg !== undefined && typeof args.leafAlg !== 'string') {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_alg must be a string when present',\n );\n }\n const map: Record<string, unknown> = {\n format: LEAVES_LIST_FORMAT_V1,\n tree_alg: TREE_ALG_RFC9162,\n root: args.root,\n leaves: leavesCopy,\n leaf_count: leavesCopy.length,\n };\n if (args.leafAlg !== undefined) {\n map['leaf_alg'] = args.leafAlg;\n }\n return encodeCanonicalCbor(map as never);\n}\n\nexport function decodeLeavesList(bytes: Uint8Array): DecodedLeavesList {\n const decoded = decodeCanonicalCbor(bytes);\n if (typeof decoded !== 'object' || decoded === null || Array.isArray(decoded)) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaves-list MUST be a CBOR map',\n );\n }\n const m = decoded as Record<string, unknown>;\n\n const format = m['format'];\n if (typeof format !== 'string') {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'format must be a text string',\n );\n }\n if (!REGISTERED_FORMATS.has(format)) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_FORMAT_UNSUPPORTED',\n `format '${format}' is not in the registered set`,\n );\n }\n\n const treeAlg = m['tree_alg'];\n if (treeAlg !== TREE_ALG_RFC9162) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `tree_alg '${String(treeAlg)}' is not '${TREE_ALG_RFC9162}'`,\n );\n }\n\n const root = m['root'];\n if (!(root instanceof Uint8Array) || root.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `root must be a ${DIGEST_LENGTH}-byte byte string`,\n );\n }\n\n const leavesRaw = m['leaves'];\n if (!Array.isArray(leavesRaw) || leavesRaw.length < 1) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaves must be a non-empty array',\n );\n }\n const leaves: Uint8Array[] = [];\n for (let i = 0; i < leavesRaw.length; i++) {\n const leaf = leavesRaw[i];\n if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n `leaves[${i}] must be a ${DIGEST_LENGTH}-byte byte string`,\n );\n }\n leaves.push(leaf);\n }\n\n const leafCountRaw = m['leaf_count'];\n let leafCount: number;\n if (typeof leafCountRaw === 'number' && Number.isInteger(leafCountRaw) && leafCountRaw >= 0) {\n leafCount = leafCountRaw;\n } else if (typeof leafCountRaw === 'bigint' && leafCountRaw >= 0n) {\n if (leafCountRaw > BigInt(Number.MAX_SAFE_INTEGER)) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_count exceeds Number.MAX_SAFE_INTEGER',\n );\n }\n leafCount = Number(leafCountRaw);\n } else {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_count must be a non-negative CBOR uint',\n );\n }\n if (leaves.length !== leafCount) {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAF_COUNT_MISMATCH',\n `leaves.length (${leaves.length}) != leaf_count (${leafCount})`,\n );\n }\n\n let leafAlg: string | undefined;\n if (m['leaf_alg'] !== undefined) {\n if (typeof m['leaf_alg'] !== 'string') {\n throw new MerkleLeavesListError(\n 'SCHEMA_MERKLE_LEAVES_MALFORMED',\n 'leaf_alg must be a text string when present',\n );\n }\n leafAlg = m['leaf_alg'];\n }\n\n const recomputed = merkleSha2256Root(leaves);\n if (!compareCt(recomputed, root)) {\n throw new MerkleLeavesListError(\n 'MERKLE_ROOT_MISMATCH',\n 'leaves recompute does not match declared root',\n );\n }\n\n const out: DecodedLeavesList = {\n format: LEAVES_LIST_FORMAT_V1,\n treeAlg: TREE_ALG_RFC9162,\n root,\n leaves,\n leafCount,\n ...(leafAlg !== undefined ? { leafAlg } : {}),\n };\n return out;\n}\n"]}