@kindgi/crypto 0.0.0-bootstrap.0 → 0.1.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ed25519.js","sourceRoot":"","sources":["../src/ed25519.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * DER envelope + PEM wrapping helpers for Ed25519 keys.
3
+ *
4
+ * Ed25519 keys have a fixed 32-byte raw form. Interop with Node's
5
+ * `crypto.createPublicKey` / `createPrivateKey` requires wrapping the raw
6
+ * bytes in a standard envelope (SPKI for public, PKCS8 for private) —
7
+ * fixed-prefix DER structures whose only variable part is the 32-byte key
8
+ * material itself. Doing this by hand keeps the package free of ASN.1
9
+ * dependencies and portable across Node / Bun / Deno.
10
+ *
11
+ * The prefixes below encode the AlgorithmIdentifier for Ed25519
12
+ * (OID 1.3.101.112, no parameters) plus the outer SEQUENCE / OCTET STRING
13
+ * wrappers that SPKI (RFC 5280) and PKCS8 (RFC 5958) require. They are
14
+ * constant for Ed25519.
15
+ */
16
+ import type { Result } from '@kindgi/types';
17
+ import type { MalformedKeyError, UnsupportedFormatError } from './errors.js';
18
+ export declare const ED25519_RAW_KEY_BYTES = 32;
19
+ export declare const ED25519_SPKI_LENGTH: number;
20
+ export declare const ED25519_PKCS8_LENGTH: number;
21
+ /** Wrap a raw 32-byte Ed25519 public key in a DER SPKI envelope. */
22
+ export declare function wrapPublicKeySpki(rawPublicKey: Uint8Array): Uint8Array;
23
+ /** Wrap a raw 32-byte Ed25519 private key seed in a DER PKCS8 envelope. */
24
+ export declare function wrapPrivateKeyPkcs8(rawPrivateKey: Uint8Array): Uint8Array;
25
+ /** Strip the DER SPKI envelope off an Ed25519 public key, returning the 32 raw bytes. */
26
+ export declare function unwrapPublicKeySpki(spki: Uint8Array): Result<Uint8Array, MalformedKeyError>;
27
+ /** Strip the DER PKCS8 envelope off an Ed25519 private key, returning the 32 raw seed bytes. */
28
+ export declare function unwrapPrivateKeyPkcs8(pkcs8: Uint8Array): Result<Uint8Array, MalformedKeyError>;
29
+ /** Encode bytes as standard base64. */
30
+ export declare function toBase64(bytes: Uint8Array): string;
31
+ /** Decode a base64 string into bytes. Returns a typed error on invalid input. */
32
+ export declare function fromBase64(b64: string): Result<Uint8Array, MalformedKeyError>;
33
+ /** Wrap DER bytes in a PEM envelope with the given label. */
34
+ export declare function toPem(derBytes: Uint8Array, label: string): string;
35
+ /**
36
+ * Parse a PEM envelope for the given label. Rejects unexpected labels,
37
+ * missing header/footer, or base64 payload that does not decode cleanly.
38
+ */
39
+ export declare function fromPem(pem: string, expectedLabel: string): Result<Uint8Array, MalformedKeyError | UnsupportedFormatError>;
40
+ //# sourceMappingURL=encoding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding.d.ts","sourceRoot":"","sources":["../src/encoding.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAE7E,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAOxC,eAAO,MAAM,mBAAmB,QAAqD,CAAC;AAOtF,eAAO,MAAM,oBAAoB,QAAsD,CAAC;AAExF,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,UAAU,GAAG,UAAU,CAUtE;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,UAAU,GAAG,UAAU,CAUzE;AAED,yFAAyF;AACzF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAY3F;AAED,gGAAgG;AAChG,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAY9F;AAID,uCAAuC;AACvC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAElD;AAED,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAkB7E;AAID,6DAA6D;AAC7D,wBAAgB,KAAK,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKjE;AAED;;;GAGG;AACH,wBAAgB,OAAO,CACrB,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,GACpB,MAAM,CAAC,UAAU,EAAE,iBAAiB,GAAG,sBAAsB,CAAC,CAwBhE"}
@@ -0,0 +1,123 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+ import { malformedKey, unsupportedFormat } from './errors.js';
4
+ export const ED25519_RAW_KEY_BYTES = 32;
5
+ // SPKI (SubjectPublicKeyInfo) prefix for Ed25519 (12 bytes). Full envelope
6
+ // is 44 bytes: prefix (12) + raw public key (32).
7
+ const ED25519_SPKI_PREFIX = new Uint8Array([
8
+ 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00,
9
+ ]);
10
+ export const ED25519_SPKI_LENGTH = ED25519_SPKI_PREFIX.length + ED25519_RAW_KEY_BYTES; // 44
11
+ // PKCS8 (PrivateKeyInfo) prefix for Ed25519 (16 bytes). Full envelope is
12
+ // 48 bytes: prefix (16) + raw private key seed (32).
13
+ const ED25519_PKCS8_PREFIX = new Uint8Array([
14
+ 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,
15
+ ]);
16
+ export const ED25519_PKCS8_LENGTH = ED25519_PKCS8_PREFIX.length + ED25519_RAW_KEY_BYTES; // 48
17
+ /** Wrap a raw 32-byte Ed25519 public key in a DER SPKI envelope. */
18
+ export function wrapPublicKeySpki(rawPublicKey) {
19
+ if (rawPublicKey.length !== ED25519_RAW_KEY_BYTES) {
20
+ throw new Error(`Ed25519 raw public key must be ${ED25519_RAW_KEY_BYTES} bytes, got ${rawPublicKey.length}`);
21
+ }
22
+ const out = new Uint8Array(ED25519_SPKI_LENGTH);
23
+ out.set(ED25519_SPKI_PREFIX, 0);
24
+ out.set(rawPublicKey, ED25519_SPKI_PREFIX.length);
25
+ return out;
26
+ }
27
+ /** Wrap a raw 32-byte Ed25519 private key seed in a DER PKCS8 envelope. */
28
+ export function wrapPrivateKeyPkcs8(rawPrivateKey) {
29
+ if (rawPrivateKey.length !== ED25519_RAW_KEY_BYTES) {
30
+ throw new Error(`Ed25519 raw private key must be ${ED25519_RAW_KEY_BYTES} bytes, got ${rawPrivateKey.length}`);
31
+ }
32
+ const out = new Uint8Array(ED25519_PKCS8_LENGTH);
33
+ out.set(ED25519_PKCS8_PREFIX, 0);
34
+ out.set(rawPrivateKey, ED25519_PKCS8_PREFIX.length);
35
+ return out;
36
+ }
37
+ /** Strip the DER SPKI envelope off an Ed25519 public key, returning the 32 raw bytes. */
38
+ export function unwrapPublicKeySpki(spki) {
39
+ if (spki.length !== ED25519_SPKI_LENGTH) {
40
+ return malformedKey(`Ed25519 SPKI envelope must be ${ED25519_SPKI_LENGTH} bytes, got ${spki.length}`);
41
+ }
42
+ for (let i = 0; i < ED25519_SPKI_PREFIX.length; i++) {
43
+ if (spki[i] !== ED25519_SPKI_PREFIX[i]) {
44
+ return malformedKey('Ed25519 SPKI prefix mismatch — not an Ed25519 public key envelope');
45
+ }
46
+ }
47
+ return { kind: 'ok', value: spki.slice(ED25519_SPKI_PREFIX.length) };
48
+ }
49
+ /** Strip the DER PKCS8 envelope off an Ed25519 private key, returning the 32 raw seed bytes. */
50
+ export function unwrapPrivateKeyPkcs8(pkcs8) {
51
+ if (pkcs8.length !== ED25519_PKCS8_LENGTH) {
52
+ return malformedKey(`Ed25519 PKCS8 envelope must be ${ED25519_PKCS8_LENGTH} bytes, got ${pkcs8.length}`);
53
+ }
54
+ for (let i = 0; i < ED25519_PKCS8_PREFIX.length; i++) {
55
+ if (pkcs8[i] !== ED25519_PKCS8_PREFIX[i]) {
56
+ return malformedKey('Ed25519 PKCS8 prefix mismatch — not an Ed25519 private key envelope');
57
+ }
58
+ }
59
+ return { kind: 'ok', value: pkcs8.slice(ED25519_PKCS8_PREFIX.length) };
60
+ }
61
+ // ============ base64 ============
62
+ /** Encode bytes as standard base64. */
63
+ export function toBase64(bytes) {
64
+ return Buffer.from(bytes).toString('base64');
65
+ }
66
+ /** Decode a base64 string into bytes. Returns a typed error on invalid input. */
67
+ export function fromBase64(b64) {
68
+ if (typeof b64 !== 'string' || b64.length === 0) {
69
+ return malformedKey('base64 input must be a non-empty string');
70
+ }
71
+ // Buffer.from is lenient about non-base64 characters; validate strictly.
72
+ if (!/^[A-Za-z0-9+/]+={0,2}$/.test(b64)) {
73
+ return malformedKey('base64 input contains characters outside the standard alphabet');
74
+ }
75
+ try {
76
+ const buf = Buffer.from(b64, 'base64');
77
+ // Round-trip to detect padding / silent truncation.
78
+ if (buf.toString('base64').replace(/=+$/u, '') !== b64.replace(/=+$/u, '')) {
79
+ return malformedKey('base64 input did not round-trip cleanly');
80
+ }
81
+ return { kind: 'ok', value: new Uint8Array(buf) };
82
+ }
83
+ catch (cause) {
84
+ return malformedKey('base64 decode failed', cause);
85
+ }
86
+ }
87
+ // ============ PEM ============
88
+ /** Wrap DER bytes in a PEM envelope with the given label. */
89
+ export function toPem(derBytes, label) {
90
+ const b64 = toBase64(derBytes);
91
+ const lines = [];
92
+ for (let i = 0; i < b64.length; i += 64)
93
+ lines.push(b64.slice(i, i + 64));
94
+ return `-----BEGIN ${label}-----\n${lines.join('\n')}\n-----END ${label}-----\n`;
95
+ }
96
+ /**
97
+ * Parse a PEM envelope for the given label. Rejects unexpected labels,
98
+ * missing header/footer, or base64 payload that does not decode cleanly.
99
+ */
100
+ export function fromPem(pem, expectedLabel) {
101
+ if (typeof pem !== 'string' || pem.length === 0) {
102
+ return malformedKey('PEM input must be a non-empty string');
103
+ }
104
+ const trimmed = pem.trim();
105
+ const header = `-----BEGIN ${expectedLabel}-----`;
106
+ const footer = `-----END ${expectedLabel}-----`;
107
+ const startIdx = trimmed.indexOf(header);
108
+ const endIdx = trimmed.indexOf(footer);
109
+ if (startIdx === -1 || endIdx === -1) {
110
+ // Detect a *different* PEM label (e.g., someone passed an RSA key here).
111
+ const otherLabelMatch = /-----BEGIN ([^-]+)-----/.exec(trimmed);
112
+ if (otherLabelMatch && otherLabelMatch[1] !== expectedLabel) {
113
+ return unsupportedFormat(`PEM label mismatch — expected "${expectedLabel}", got "${otherLabelMatch[1]}"`);
114
+ }
115
+ return malformedKey(`PEM envelope missing "${expectedLabel}" header/footer`);
116
+ }
117
+ if (endIdx < startIdx) {
118
+ return malformedKey('PEM footer appears before header');
119
+ }
120
+ const body = trimmed.slice(startIdx + header.length, endIdx).replace(/\s+/g, '');
121
+ return fromBase64(body);
122
+ }
123
+ //# sourceMappingURL=encoding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding.js","sourceRoot":"","sources":["../src/encoding.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Typed errors returned from the crypto primitives. Cryptographic operations
3
+ * fall into two buckets: (a) caller-recoverable errors — malformed key
4
+ * material, wrong-shape input, bad PEM headers — and (b) system-level errors
5
+ * from the underlying primitive that a caller cannot recover from. The
6
+ * former return as `Result<T, CryptoError>`; the latter throw. This file
7
+ * declares (a).
8
+ */
9
+ export type CryptoError = MalformedKeyError | MalformedSignatureError | UnsupportedFormatError;
10
+ /**
11
+ * Key material could not be parsed. Covers malformed PEM headers, wrong
12
+ * base64, DER envelopes with unexpected OIDs, and byte-length mismatches
13
+ * for the declared algorithm.
14
+ */
15
+ export interface MalformedKeyError {
16
+ readonly code: 'malformed-key';
17
+ readonly message: string;
18
+ readonly cause?: unknown;
19
+ }
20
+ /**
21
+ * Signature bytes are the wrong length or shape for the declared algorithm.
22
+ * Returned by verify when the caller-supplied signature can be rejected
23
+ * without invoking the underlying primitive.
24
+ */
25
+ export interface MalformedSignatureError {
26
+ readonly code: 'malformed-signature';
27
+ readonly message: string;
28
+ readonly cause?: unknown;
29
+ }
30
+ /**
31
+ * A serialization format was requested that this package does not support
32
+ * (e.g., a PEM label the parser does not recognize).
33
+ */
34
+ export interface UnsupportedFormatError {
35
+ readonly code: 'unsupported-format';
36
+ readonly message: string;
37
+ readonly cause?: unknown;
38
+ }
39
+ export declare function malformedKey(message: string, cause?: unknown): {
40
+ readonly kind: 'err';
41
+ readonly error: MalformedKeyError;
42
+ };
43
+ export declare function malformedSignature(message: string, cause?: unknown): {
44
+ readonly kind: 'err';
45
+ readonly error: MalformedSignatureError;
46
+ };
47
+ export declare function unsupportedFormat(message: string, cause?: unknown): {
48
+ readonly kind: 'err';
49
+ readonly error: UnsupportedFormatError;
50
+ };
51
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,uBAAuB,GAAG,sBAAsB,CAAC;AAE/F;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,GACd;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAA;CAAE,CAQ7D;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,GACd;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,uBAAuB,CAAA;CAAE,CAQnE;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,GACd;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAA;CAAE,CAQlE"}
package/dist/errors.js ADDED
@@ -0,0 +1,27 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+ export function malformedKey(message, cause) {
4
+ return {
5
+ kind: 'err',
6
+ error: cause === undefined
7
+ ? { code: 'malformed-key', message }
8
+ : { code: 'malformed-key', message, cause },
9
+ };
10
+ }
11
+ export function malformedSignature(message, cause) {
12
+ return {
13
+ kind: 'err',
14
+ error: cause === undefined
15
+ ? { code: 'malformed-signature', message }
16
+ : { code: 'malformed-signature', message, cause },
17
+ };
18
+ }
19
+ export function unsupportedFormat(message, cause) {
20
+ return {
21
+ kind: 'err',
22
+ error: cause === undefined
23
+ ? { code: 'unsupported-format', message }
24
+ : { code: 'unsupported-format', message, cause },
25
+ };
26
+ }
27
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AA6CjC,MAAM,UAAU,YAAY,CAC1B,OAAe,EACf,KAAe;IAEf,OAAO;QACL,IAAI,EAAE,KAAK;QACX,KAAK,EACH,KAAK,KAAK,SAAS;YACjB,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE;YACpC,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE;KAChD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,KAAe;IAEf,OAAO;QACL,IAAI,EAAE,KAAK;QACX,KAAK,EACH,KAAK,KAAK,SAAS;YACjB,CAAC,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE;YAC1C,CAAC,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,KAAK,EAAE;KACtD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,OAAe,EACf,KAAe;IAEf,OAAO;QACL,IAAI,EAAE,KAAK;QACX,KAAK,EACH,KAAK,KAAK,SAAS;YACjB,CAAC,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE;YACzC,CAAC,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,KAAK,EAAE;KACrD,CAAC;AACJ,CAAC"}
package/dist/hmac.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Compute the HMAC-SHA256 of `message` under `secret`. Returns the
3
+ * signature hex-encoded (lowercase) — the wire form of the common
4
+ * "hex HMAC over the raw body" webhook convention.
5
+ */
6
+ export declare function hmacSha256Sign(secret: string | Uint8Array, message: string | Uint8Array): string;
7
+ /**
8
+ * Verify an HMAC-SHA256 signature in constant time. Returns `false` on
9
+ * length mismatch or hex-decode failure (both are shape errors the caller
10
+ * should treat as authentication failure — a well-formed signer would
11
+ * never produce them, so distinguishing them from a genuine mismatch
12
+ * offers no operational value and leaks structural information to an
13
+ * attacker probing the endpoint).
14
+ */
15
+ export declare function hmacSha256Verify(secret: string | Uint8Array, message: string | Uint8Array, signature: string): boolean;
16
+ //# sourceMappingURL=hmac.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hmac.d.ts","sourceRoot":"","sources":["../src/hmac.ts"],"names":[],"mappings":"AAeA;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAKhG;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,OAAO,EAAE,MAAM,GAAG,UAAU,EAC5B,SAAS,EAAE,MAAM,GAChB,OAAO,CAiBT"}
package/dist/hmac.js ADDED
@@ -0,0 +1,59 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+ /**
4
+ * HMAC-SHA256 message authentication. Symmetric: sender and receiver share
5
+ * a secret. Suited to signing webhook-style payloads — the receiver
6
+ * verifies with the same secret. Follows the common webhook convention
7
+ * of a hex-encoded signature over the raw payload bytes.
8
+ *
9
+ * Verify uses a constant-time comparison to prevent timing side channels
10
+ * from leaking the correct signature byte-by-byte.
11
+ */
12
+ import { createHmac, timingSafeEqual } from 'node:crypto';
13
+ /**
14
+ * Compute the HMAC-SHA256 of `message` under `secret`. Returns the
15
+ * signature hex-encoded (lowercase) — the wire form of the common
16
+ * "hex HMAC over the raw body" webhook convention.
17
+ */
18
+ export function hmacSha256Sign(secret, message) {
19
+ const secretBuf = normalizeSecret(secret);
20
+ const mac = createHmac('sha256', secretBuf);
21
+ mac.update(message);
22
+ return mac.digest('hex');
23
+ }
24
+ /**
25
+ * Verify an HMAC-SHA256 signature in constant time. Returns `false` on
26
+ * length mismatch or hex-decode failure (both are shape errors the caller
27
+ * should treat as authentication failure — a well-formed signer would
28
+ * never produce them, so distinguishing them from a genuine mismatch
29
+ * offers no operational value and leaks structural information to an
30
+ * attacker probing the endpoint).
31
+ */
32
+ export function hmacSha256Verify(secret, message, signature) {
33
+ if (typeof signature !== 'string')
34
+ return false;
35
+ const expected = hmacSha256Sign(secret, message);
36
+ // Length mismatch is a definitive no; short-circuit before allocating.
37
+ if (expected.length !== signature.length)
38
+ return false;
39
+ // timingSafeEqual requires equal-length buffers. Both are 64-char hex
40
+ // (32-byte SHA-256), pre-verified above.
41
+ let expectedBuf;
42
+ let providedBuf;
43
+ try {
44
+ expectedBuf = Buffer.from(expected, 'hex');
45
+ providedBuf = Buffer.from(signature, 'hex');
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ if (expectedBuf.length !== providedBuf.length)
51
+ return false;
52
+ return timingSafeEqual(expectedBuf, providedBuf);
53
+ }
54
+ function normalizeSecret(secret) {
55
+ if (typeof secret === 'string')
56
+ return Buffer.from(secret, 'utf-8');
57
+ return Buffer.from(secret);
58
+ }
59
+ //# sourceMappingURL=hmac.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hmac.js","sourceRoot":"","sources":["../src/hmac.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE1D;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAA2B,EAAE,OAA4B;IACtF,MAAM,SAAS,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC5C,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACpB,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAA2B,EAC3B,OAA4B,EAC5B,SAAiB;IAEjB,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAChD,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjD,uEAAuE;IACvE,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACvD,sEAAsE;IACtE,yCAAyC;IACzC,IAAI,WAAmB,CAAC;IACxB,IAAI,WAAmB,CAAC;IACxB,IAAI,CAAC;QACH,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC3C,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,WAAW,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC5D,OAAO,eAAe,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,eAAe,CAAC,MAA2B;IAClD,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpE,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,10 @@
1
+ export { ED25519_SIGNATURE_BYTES, generateEd25519KeyPair, parsePrivateKeyBase64, parsePrivateKeyPem, parsePublicKeyBase64, parsePublicKeyPem, serializePrivateKeyBase64, serializePrivateKeyPem, serializePublicKeyBase64, serializePublicKeyPem, signEd25519, verifyEd25519, } from './ed25519.js';
2
+ export type { Ed25519KeyPair } from './ed25519.js';
3
+ export { hmacSha256Sign, hmacSha256Verify } from './hmac.js';
4
+ export { DEFAULT_WEBHOOK_TOLERANCE_SECONDS, generateWebhookSecret, isStrongWebhookSecret, signWebhook, verifyWebhook, WEBHOOK_HEADERS, WEBHOOK_SECRET_MIN_BYTES, WEBHOOK_SECRET_PREFIX, webhookHeaders, } from './webhook.js';
5
+ export type { SignWebhookInput, VerifyWebhookFailure, VerifyWebhookInput, VerifyWebhookResult, WebhookRequestHeaders, } from './webhook.js';
6
+ export { createInMemorySigningKeyBinding } from './binding.js';
7
+ export type { SigningAlgorithm, SigningKeyBinding, SigningKeyDescriptor } from './binding.js';
8
+ export type { CryptoError, MalformedKeyError, MalformedSignatureError, UnsupportedFormatError, } from './errors.js';
9
+ export { ED25519_RAW_KEY_BYTES } from './encoding.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,EACxB,qBAAqB,EACrB,WAAW,EACX,aAAa,GACd,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7D,OAAO,EACL,iCAAiC,EACjC,qBAAqB,EACrB,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,eAAe,EACf,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAC;AAC/D,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAE9F,YAAY,EACV,WAAW,EACX,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+ export { ED25519_SIGNATURE_BYTES, generateEd25519KeyPair, parsePrivateKeyBase64, parsePrivateKeyPem, parsePublicKeyBase64, parsePublicKeyPem, serializePrivateKeyBase64, serializePrivateKeyPem, serializePublicKeyBase64, serializePublicKeyPem, signEd25519, verifyEd25519, } from './ed25519.js';
4
+ export { hmacSha256Sign, hmacSha256Verify } from './hmac.js';
5
+ export { DEFAULT_WEBHOOK_TOLERANCE_SECONDS, generateWebhookSecret, isStrongWebhookSecret, signWebhook, verifyWebhook, WEBHOOK_HEADERS, WEBHOOK_SECRET_MIN_BYTES, WEBHOOK_SECRET_PREFIX, webhookHeaders, } from './webhook.js';
6
+ export { createInMemorySigningKeyBinding } from './binding.js';
7
+ export { ED25519_RAW_KEY_BYTES } from './encoding.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,EACxB,qBAAqB,EACrB,WAAW,EACX,aAAa,GACd,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7D,OAAO,EACL,iCAAiC,EACjC,qBAAqB,EACrB,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,eAAe,EACf,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AAStB,OAAO,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAC;AAU/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,74 @@
1
+ import type { Result } from '@kindgi/types';
2
+ import type { MalformedKeyError } from './errors.js';
3
+ /** Prefix of a webhook signing secret. */
4
+ export declare const WEBHOOK_SECRET_PREFIX = "whsec_";
5
+ /** The three request headers a signed webhook carries. */
6
+ export declare const WEBHOOK_HEADERS: {
7
+ readonly id: "webhook-id";
8
+ readonly timestamp: "webhook-timestamp";
9
+ readonly signature: "webhook-signature";
10
+ };
11
+ /** How far a message's timestamp may be from the receiver's clock (seconds). */
12
+ export declare const DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
13
+ /** The fewest random bytes a webhook secret may have (Standard Webhooks: 24–64). */
14
+ export declare const WEBHOOK_SECRET_MIN_BYTES = 24;
15
+ /** A new random signing secret: `whsec_` + base64 of 32 random bytes. */
16
+ export declare function generateWebhookSecret(): string;
17
+ /**
18
+ * Whether a secret is usable for signing: `whsec_` + base64 (or the bare
19
+ * base64) of at least {@link WEBHOOK_SECRET_MIN_BYTES} bytes.
20
+ */
21
+ export declare function isStrongWebhookSecret(secret: string): boolean;
22
+ export interface SignWebhookInput {
23
+ /**
24
+ * The endpoint's secret. During a rotation, pass the new and the
25
+ * previous secret: the header then carries one signature per secret,
26
+ * and a receiver holding either one accepts the message.
27
+ */
28
+ readonly secret: string | readonly string[];
29
+ /** The message id; stays the same across retries of one message. */
30
+ readonly id: string;
31
+ /** Unix seconds. */
32
+ readonly timestamp: number;
33
+ /** The raw request body, exactly as sent. */
34
+ readonly body: string | Uint8Array;
35
+ }
36
+ /**
37
+ * The `webhook-signature` header value: `v1,<base64>` per secret,
38
+ * space-separated. A malformed secret is a `malformed-key` error. An
39
+ * empty secret list or a timestamp that isn't a non-negative integer is
40
+ * a caller bug and throws.
41
+ */
42
+ export declare function signWebhook(input: SignWebhookInput): Result<string, MalformedKeyError>;
43
+ /** The three signed headers for a message, ready to send. */
44
+ export declare function webhookHeaders(input: SignWebhookInput): Result<Readonly<Record<string, string>>, MalformedKeyError>;
45
+ /** Request headers as a `Headers` object or a plain (Node-style) record. */
46
+ export type WebhookRequestHeaders = {
47
+ get(name: string): string | null;
48
+ } | Readonly<Record<string, string | readonly string[] | undefined>>;
49
+ export interface VerifyWebhookInput {
50
+ readonly secret: string;
51
+ readonly headers: WebhookRequestHeaders;
52
+ /** The raw request body, exactly as received — not a re-serialized JSON value. */
53
+ readonly body: string | Uint8Array;
54
+ /** Default {@link DEFAULT_WEBHOOK_TOLERANCE_SECONDS}. */
55
+ readonly toleranceSeconds?: number;
56
+ /** Clock in milliseconds; default `Date.now`. */
57
+ readonly now?: () => number;
58
+ }
59
+ export type VerifyWebhookFailure = 'missing-headers' | 'invalid-timestamp' | 'timestamp-out-of-tolerance' | 'invalid-secret' | 'no-matching-signature';
60
+ export type VerifyWebhookResult = {
61
+ readonly kind: 'ok';
62
+ readonly id: string;
63
+ readonly timestamp: number;
64
+ } | {
65
+ readonly kind: 'err';
66
+ readonly reason: VerifyWebhookFailure;
67
+ };
68
+ /**
69
+ * Verify a received webhook: the timestamp is within tolerance and at
70
+ * least one `v1` signature in the header matches. Comparison is
71
+ * constant-time. On `ok`, deduplicate on `id` — delivery is at least once.
72
+ */
73
+ export declare function verifyWebhook(input: VerifyWebhookInput): VerifyWebhookResult;
74
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,0CAA0C;AAC1C,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAE9C,0DAA0D;AAC1D,eAAO,MAAM,eAAe;;;;CAIlB,CAAC;AAEX,gFAAgF;AAChF,eAAO,MAAM,iCAAiC,MAAM,CAAC;AAKrD,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C,yEAAyE;AACzE,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAG7D;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAC5C,oEAAoE;IACpE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,oBAAoB;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;CACpC;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CActF;AAED,6DAA6D;AAC7D,wBAAgB,cAAc,CAC5B,KAAK,EAAE,gBAAgB,GACtB,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAW7D;AAED,4EAA4E;AAC5E,MAAM,MAAM,qBAAqB,GAC7B;IAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,GACpC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;AAErE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IACnC,yDAAyD;IACzD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,iDAAiD;IACjD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,oBAAoB,GAC5B,iBAAiB,GACjB,mBAAmB,GACnB,4BAA4B,GAC5B,gBAAgB,GAChB,uBAAuB,CAAC;AAE5B,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACxE;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAAC;AAEpE;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,mBAAmB,CA2B5E"}
@@ -0,0 +1,149 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+ /**
4
+ * Webhook signatures in the Standard Webhooks format
5
+ * (https://www.standardwebhooks.com), symmetric variant `v1`:
6
+ *
7
+ * webhook-id: the message id (the same on every retry)
8
+ * webhook-timestamp: Unix seconds when the message was signed
9
+ * webhook-signature: `v1,<base64 HMAC-SHA256>`, space-separated when
10
+ * several secrets sign (secret rotation)
11
+ *
12
+ * The signed content is `${id}.${timestamp}.${body}`, with `body` the raw
13
+ * request body exactly as sent. Secrets are `whsec_` + base64 of the key
14
+ * bytes. A receiver can verify with this module or with any Standard
15
+ * Webhooks library.
16
+ */
17
+ import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
18
+ import { malformedKey } from './errors.js';
19
+ /** Prefix of a webhook signing secret. */
20
+ export const WEBHOOK_SECRET_PREFIX = 'whsec_';
21
+ /** The three request headers a signed webhook carries. */
22
+ export const WEBHOOK_HEADERS = {
23
+ id: 'webhook-id',
24
+ timestamp: 'webhook-timestamp',
25
+ signature: 'webhook-signature',
26
+ };
27
+ /** How far a message's timestamp may be from the receiver's clock (seconds). */
28
+ export const DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
29
+ const SIGNATURE_VERSION = 'v1';
30
+ const SECRET_BYTES = 32;
31
+ /** The fewest random bytes a webhook secret may have (Standard Webhooks: 24–64). */
32
+ export const WEBHOOK_SECRET_MIN_BYTES = 24;
33
+ /** A new random signing secret: `whsec_` + base64 of 32 random bytes. */
34
+ export function generateWebhookSecret() {
35
+ return `${WEBHOOK_SECRET_PREFIX}${randomBytes(SECRET_BYTES).toString('base64')}`;
36
+ }
37
+ /**
38
+ * Whether a secret is usable for signing: `whsec_` + base64 (or the bare
39
+ * base64) of at least {@link WEBHOOK_SECRET_MIN_BYTES} bytes.
40
+ */
41
+ export function isStrongWebhookSecret(secret) {
42
+ const key = parseSecret(secret);
43
+ return key !== null && key.length >= WEBHOOK_SECRET_MIN_BYTES;
44
+ }
45
+ /**
46
+ * The `webhook-signature` header value: `v1,<base64>` per secret,
47
+ * space-separated. A malformed secret is a `malformed-key` error. An
48
+ * empty secret list or a timestamp that isn't a non-negative integer is
49
+ * a caller bug and throws.
50
+ */
51
+ export function signWebhook(input) {
52
+ if (!Number.isSafeInteger(input.timestamp) || input.timestamp < 0) {
53
+ throw new TypeError('signWebhook: timestamp must be a non-negative integer (Unix seconds)');
54
+ }
55
+ const secrets = typeof input.secret === 'string' ? [input.secret] : input.secret;
56
+ if (secrets.length === 0)
57
+ throw new TypeError('signWebhook: at least one secret is required');
58
+ const content = signedContent(input.id, String(input.timestamp), input.body);
59
+ const signatures = [];
60
+ for (const secret of secrets) {
61
+ const key = parseSecret(secret);
62
+ if (key === null)
63
+ return malformedKey('webhook secret must be `whsec_` + base64 key bytes');
64
+ signatures.push(`${SIGNATURE_VERSION},${hmac(key, content).toString('base64')}`);
65
+ }
66
+ return { kind: 'ok', value: signatures.join(' ') };
67
+ }
68
+ /** The three signed headers for a message, ready to send. */
69
+ export function webhookHeaders(input) {
70
+ const signature = signWebhook(input);
71
+ if (signature.kind === 'err')
72
+ return signature;
73
+ return {
74
+ kind: 'ok',
75
+ value: {
76
+ [WEBHOOK_HEADERS.id]: input.id,
77
+ [WEBHOOK_HEADERS.timestamp]: String(input.timestamp),
78
+ [WEBHOOK_HEADERS.signature]: signature.value,
79
+ },
80
+ };
81
+ }
82
+ /**
83
+ * Verify a received webhook: the timestamp is within tolerance and at
84
+ * least one `v1` signature in the header matches. Comparison is
85
+ * constant-time. On `ok`, deduplicate on `id` — delivery is at least once.
86
+ */
87
+ export function verifyWebhook(input) {
88
+ const id = readHeader(input.headers, WEBHOOK_HEADERS.id);
89
+ const timestampRaw = readHeader(input.headers, WEBHOOK_HEADERS.timestamp);
90
+ const signatureHeader = readHeader(input.headers, WEBHOOK_HEADERS.signature);
91
+ if (id === undefined || timestampRaw === undefined || signatureHeader === undefined) {
92
+ return { kind: 'err', reason: 'missing-headers' };
93
+ }
94
+ if (!/^\d{1,15}$/.test(timestampRaw))
95
+ return { kind: 'err', reason: 'invalid-timestamp' };
96
+ const timestamp = Number(timestampRaw);
97
+ const tolerance = input.toleranceSeconds ?? DEFAULT_WEBHOOK_TOLERANCE_SECONDS;
98
+ const nowSeconds = Math.floor((input.now ?? Date.now)() / 1000);
99
+ if (Math.abs(nowSeconds - timestamp) > tolerance) {
100
+ return { kind: 'err', reason: 'timestamp-out-of-tolerance' };
101
+ }
102
+ const key = parseSecret(input.secret);
103
+ if (key === null)
104
+ return { kind: 'err', reason: 'invalid-secret' };
105
+ const expected = hmac(key, signedContent(id, timestampRaw, input.body));
106
+ for (const part of signatureHeader.split(' ')) {
107
+ const comma = part.indexOf(',');
108
+ if (comma < 0 || part.slice(0, comma) !== SIGNATURE_VERSION)
109
+ continue;
110
+ const provided = Buffer.from(part.slice(comma + 1), 'base64');
111
+ if (provided.length === expected.length && timingSafeEqual(provided, expected)) {
112
+ return { kind: 'ok', id, timestamp };
113
+ }
114
+ }
115
+ return { kind: 'err', reason: 'no-matching-signature' };
116
+ }
117
+ function signedContent(id, timestamp, body) {
118
+ const prefix = Buffer.from(`${id}.${timestamp}.`, 'utf-8');
119
+ const bodyBytes = typeof body === 'string' ? Buffer.from(body, 'utf-8') : Buffer.from(body);
120
+ return Buffer.concat([prefix, bodyBytes]);
121
+ }
122
+ function hmac(key, content) {
123
+ return createHmac('sha256', key).update(content).digest();
124
+ }
125
+ /** Key bytes of a `whsec_…` (or bare base64) secret; `null` when malformed. */
126
+ function parseSecret(secret) {
127
+ const encoded = secret.startsWith(WEBHOOK_SECRET_PREFIX)
128
+ ? secret.slice(WEBHOOK_SECRET_PREFIX.length)
129
+ : secret;
130
+ if (encoded.length === 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(encoded))
131
+ return null;
132
+ const key = Buffer.from(encoded, 'base64');
133
+ return key.length === 0 ? null : key;
134
+ }
135
+ function readHeader(headers, name) {
136
+ if (typeof headers.get === 'function') {
137
+ const value = headers.get(name);
138
+ return value === null || value.length === 0 ? undefined : value;
139
+ }
140
+ const record = headers;
141
+ for (const [key, value] of Object.entries(record)) {
142
+ if (key.toLowerCase() !== name)
143
+ continue;
144
+ const first = typeof value === 'string' ? value : value?.[0];
145
+ return first === undefined || first.length === 0 ? undefined : first;
146
+ }
147
+ return undefined;
148
+ }
149
+ //# sourceMappingURL=webhook.js.map