@bolyra/receipts 0.8.0 → 0.9.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/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export { signReceipt, verifyReceipt, hashPayload } from './sign';
4
4
  export { GENESIS_PREV_RECEIPT_HASH, ReceiptChain, computeReceiptHash, verifyReceiptChain, } from './chain';
5
5
  export type { ReceiptChainIssue, ReceiptChainIssueCode, ChainVerifyOptions, ChainVerifyResult, } from './chain';
6
6
  export type { ReceiptPayload, ReceiptChainFields, SignedReceipt, ReceiptSignerConfig, AuthReceiptInput, CommerceReceiptInput, CommerceFields, } from './types';
7
+ export { parseSignerDiscovery, acceptedSigners, SignerDiscoveryError, type SignerDiscoveryDocument, type DiscoveredSigner, } from './signer-discovery';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.verifyReceiptChain = exports.computeReceiptHash = exports.ReceiptChain = exports.GENESIS_PREV_RECEIPT_HASH = exports.hashPayload = exports.verifyReceipt = exports.signReceipt = exports.createCommerceReceipt = exports.createAuthReceipt = exports.canonicalize = void 0;
3
+ exports.SignerDiscoveryError = exports.acceptedSigners = exports.parseSignerDiscovery = exports.verifyReceiptChain = exports.computeReceiptHash = exports.ReceiptChain = exports.GENESIS_PREV_RECEIPT_HASH = exports.hashPayload = exports.verifyReceipt = exports.signReceipt = exports.createCommerceReceipt = exports.createAuthReceipt = exports.canonicalize = void 0;
4
4
  var canonical_1 = require("./canonical");
5
5
  Object.defineProperty(exports, "canonicalize", { enumerable: true, get: function () { return canonical_1.canonicalize; } });
6
6
  var receipt_1 = require("./receipt");
@@ -15,3 +15,7 @@ Object.defineProperty(exports, "GENESIS_PREV_RECEIPT_HASH", { enumerable: true,
15
15
  Object.defineProperty(exports, "ReceiptChain", { enumerable: true, get: function () { return chain_1.ReceiptChain; } });
16
16
  Object.defineProperty(exports, "computeReceiptHash", { enumerable: true, get: function () { return chain_1.computeReceiptHash; } });
17
17
  Object.defineProperty(exports, "verifyReceiptChain", { enumerable: true, get: function () { return chain_1.verifyReceiptChain; } });
18
+ var signer_discovery_1 = require("./signer-discovery");
19
+ Object.defineProperty(exports, "parseSignerDiscovery", { enumerable: true, get: function () { return signer_discovery_1.parseSignerDiscovery; } });
20
+ Object.defineProperty(exports, "acceptedSigners", { enumerable: true, get: function () { return signer_discovery_1.acceptedSigners; } });
21
+ Object.defineProperty(exports, "SignerDiscoveryError", { enumerable: true, get: function () { return signer_discovery_1.SignerDiscoveryError; } });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Receipt Signer Discovery v1 — canonical parser/validator.
3
+ * Spec: spec/receipt-signer-discovery-v1.md. Discovery is not endorsement:
4
+ * this module validates document SHAPE; deciding to trust the origin that
5
+ * served it stays with the consumer.
6
+ */
7
+ export declare class SignerDiscoveryError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ export interface DiscoveredSigner {
11
+ keyId: string;
12
+ alg: 'ES256K';
13
+ signer: string;
14
+ label?: string;
15
+ }
16
+ export interface SignerDiscoveryDocument {
17
+ v: 1;
18
+ issuer: string;
19
+ updatedAt: number;
20
+ signers: DiscoveredSigner[];
21
+ }
22
+ /**
23
+ * Validate an untrusted JSON value as a v1 signer discovery document.
24
+ * Throws SignerDiscoveryError on any spec violation (consumers MUST treat
25
+ * that as verification failure — fail closed). Unknown fields are ignored.
26
+ */
27
+ export declare function parseSignerDiscovery(input: unknown): SignerDiscoveryDocument;
28
+ /** Lowercased accepted signer addresses from a validated document. */
29
+ export declare function acceptedSigners(doc: SignerDiscoveryDocument): Set<string>;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * Receipt Signer Discovery v1 — canonical parser/validator.
4
+ * Spec: spec/receipt-signer-discovery-v1.md. Discovery is not endorsement:
5
+ * this module validates document SHAPE; deciding to trust the origin that
6
+ * served it stays with the consumer.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.SignerDiscoveryError = void 0;
10
+ exports.parseSignerDiscovery = parseSignerDiscovery;
11
+ exports.acceptedSigners = acceptedSigners;
12
+ const SIGNER_RE = /^0x[0-9a-fA-F]{40}$/;
13
+ class SignerDiscoveryError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = 'SignerDiscoveryError';
17
+ }
18
+ }
19
+ exports.SignerDiscoveryError = SignerDiscoveryError;
20
+ function bad(field, why) {
21
+ throw new SignerDiscoveryError(`signer discovery document invalid: ${field} ${why}`);
22
+ }
23
+ /**
24
+ * Validate an untrusted JSON value as a v1 signer discovery document.
25
+ * Throws SignerDiscoveryError on any spec violation (consumers MUST treat
26
+ * that as verification failure — fail closed). Unknown fields are ignored.
27
+ */
28
+ function parseSignerDiscovery(input) {
29
+ if (typeof input !== 'object' || input === null || Array.isArray(input)) {
30
+ bad('document', 'must be a JSON object');
31
+ }
32
+ const doc = input;
33
+ if (doc.v !== 1)
34
+ bad('v', 'must be the number 1');
35
+ if (typeof doc.issuer !== 'string' || doc.issuer.length === 0) {
36
+ bad('issuer', 'must be a non-empty string');
37
+ }
38
+ if (typeof doc.updatedAt !== 'number' || !Number.isFinite(doc.updatedAt)) {
39
+ bad('updatedAt', 'must be a number (unix seconds)');
40
+ }
41
+ if (!Array.isArray(doc.signers) || doc.signers.length === 0) {
42
+ bad('signers', 'must be a non-empty array');
43
+ }
44
+ const byKeyId = new Map();
45
+ const signers = doc.signers.map((raw, i) => {
46
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
47
+ bad(`signers[${i}]`, 'must be an object');
48
+ }
49
+ const e = raw;
50
+ if (typeof e.keyId !== 'string' || e.keyId.length === 0) {
51
+ bad(`signers[${i}].keyId`, 'must be a non-empty string');
52
+ }
53
+ if (e.alg !== 'ES256K') {
54
+ bad(`signers[${i}].alg`, 'must be "ES256K" in v1 (closed set)');
55
+ }
56
+ if (typeof e.signer !== 'string' || !SIGNER_RE.test(e.signer)) {
57
+ bad(`signers[${i}].signer`, 'must match ^0x[0-9a-fA-F]{40}$');
58
+ }
59
+ if (e.label !== undefined && typeof e.label !== 'string') {
60
+ bad(`signers[${i}].label`, 'must be a string when present');
61
+ }
62
+ const keyId = e.keyId;
63
+ const signer = e.signer.toLowerCase();
64
+ const seen = byKeyId.get(keyId);
65
+ if (seen !== undefined && seen !== signer) {
66
+ bad(`signers[${i}].keyId`, `"${keyId}" appears twice with conflicting signer values`);
67
+ }
68
+ byKeyId.set(keyId, signer);
69
+ return {
70
+ keyId,
71
+ alg: 'ES256K',
72
+ signer: e.signer,
73
+ ...(e.label !== undefined ? { label: e.label } : {}),
74
+ };
75
+ });
76
+ return { v: 1, issuer: doc.issuer, updatedAt: doc.updatedAt, signers };
77
+ }
78
+ /** Lowercased accepted signer addresses from a validated document. */
79
+ function acceptedSigners(doc) {
80
+ return new Set(doc.signers.map((s) => s.signer.toLowerCase()));
81
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bolyra/receipts",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Signed auth receipts for Bolyra ZKP verification decisions — canonical JSON, secp256k1 sign/verify, EVM-compatible r||s||v signatures.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -22,3 +22,10 @@ export type {
22
22
  CommerceReceiptInput,
23
23
  CommerceFields,
24
24
  } from './types';
25
+ export {
26
+ parseSignerDiscovery,
27
+ acceptedSigners,
28
+ SignerDiscoveryError,
29
+ type SignerDiscoveryDocument,
30
+ type DiscoveredSigner,
31
+ } from './signer-discovery';
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Receipt Signer Discovery v1 — canonical parser/validator.
3
+ * Spec: spec/receipt-signer-discovery-v1.md. Discovery is not endorsement:
4
+ * this module validates document SHAPE; deciding to trust the origin that
5
+ * served it stays with the consumer.
6
+ */
7
+
8
+ const SIGNER_RE = /^0x[0-9a-fA-F]{40}$/;
9
+
10
+ export class SignerDiscoveryError extends Error {
11
+ constructor(message: string) {
12
+ super(message);
13
+ this.name = 'SignerDiscoveryError';
14
+ }
15
+ }
16
+
17
+ export interface DiscoveredSigner {
18
+ keyId: string;
19
+ alg: 'ES256K';
20
+ signer: string;
21
+ label?: string;
22
+ }
23
+
24
+ export interface SignerDiscoveryDocument {
25
+ v: 1;
26
+ issuer: string;
27
+ updatedAt: number;
28
+ signers: DiscoveredSigner[];
29
+ }
30
+
31
+ function bad(field: string, why: string): never {
32
+ throw new SignerDiscoveryError(`signer discovery document invalid: ${field} ${why}`);
33
+ }
34
+
35
+ /**
36
+ * Validate an untrusted JSON value as a v1 signer discovery document.
37
+ * Throws SignerDiscoveryError on any spec violation (consumers MUST treat
38
+ * that as verification failure — fail closed). Unknown fields are ignored.
39
+ */
40
+ export function parseSignerDiscovery(input: unknown): SignerDiscoveryDocument {
41
+ if (typeof input !== 'object' || input === null || Array.isArray(input)) {
42
+ bad('document', 'must be a JSON object');
43
+ }
44
+ const doc = input as Record<string, unknown>;
45
+
46
+ if (doc.v !== 1) bad('v', 'must be the number 1');
47
+ if (typeof doc.issuer !== 'string' || doc.issuer.length === 0) {
48
+ bad('issuer', 'must be a non-empty string');
49
+ }
50
+ if (typeof doc.updatedAt !== 'number' || !Number.isFinite(doc.updatedAt)) {
51
+ bad('updatedAt', 'must be a number (unix seconds)');
52
+ }
53
+ if (!Array.isArray(doc.signers) || doc.signers.length === 0) {
54
+ bad('signers', 'must be a non-empty array');
55
+ }
56
+
57
+ const byKeyId = new Map<string, string>();
58
+ const signers: DiscoveredSigner[] = doc.signers.map((raw, i) => {
59
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
60
+ bad(`signers[${i}]`, 'must be an object');
61
+ }
62
+ const e = raw as Record<string, unknown>;
63
+ if (typeof e.keyId !== 'string' || e.keyId.length === 0) {
64
+ bad(`signers[${i}].keyId`, 'must be a non-empty string');
65
+ }
66
+ if (e.alg !== 'ES256K') {
67
+ bad(`signers[${i}].alg`, 'must be "ES256K" in v1 (closed set)');
68
+ }
69
+ if (typeof e.signer !== 'string' || !SIGNER_RE.test(e.signer)) {
70
+ bad(`signers[${i}].signer`, 'must match ^0x[0-9a-fA-F]{40}$');
71
+ }
72
+ if (e.label !== undefined && typeof e.label !== 'string') {
73
+ bad(`signers[${i}].label`, 'must be a string when present');
74
+ }
75
+ const keyId = e.keyId as string;
76
+ const signer = (e.signer as string).toLowerCase();
77
+ const seen = byKeyId.get(keyId);
78
+ if (seen !== undefined && seen !== signer) {
79
+ bad(`signers[${i}].keyId`, `"${keyId}" appears twice with conflicting signer values`);
80
+ }
81
+ byKeyId.set(keyId, signer);
82
+ return {
83
+ keyId,
84
+ alg: 'ES256K' as const,
85
+ signer: e.signer as string,
86
+ ...(e.label !== undefined ? { label: e.label as string } : {}),
87
+ };
88
+ });
89
+
90
+ return { v: 1, issuer: doc.issuer as string, updatedAt: doc.updatedAt as number, signers };
91
+ }
92
+
93
+ /** Lowercased accepted signer addresses from a validated document. */
94
+ export function acceptedSigners(doc: SignerDiscoveryDocument): Set<string> {
95
+ return new Set(doc.signers.map((s) => s.signer.toLowerCase()));
96
+ }