@finalbosstech/pqc-receipt-sdk 1.0.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.
@@ -0,0 +1,175 @@
1
+ /**
2
+ * PQC Receipt SDK - Independent Verification
3
+ */
4
+
5
+ import { verifySignature, hash, canonicalizePayload } from './crypto.js';
6
+ import { verifyLogChain } from './log.js';
7
+ import type { Receipt, LogEntry, VerificationResult, ChainVerificationResult } from './types.js';
8
+
9
+ /**
10
+ * Verify a single receipt
11
+ */
12
+ export async function verifyReceipt(
13
+ receipt: Receipt,
14
+ publicKey: Buffer
15
+ ): Promise<VerificationResult> {
16
+ return verifySignature(receipt, publicKey);
17
+ }
18
+
19
+ /**
20
+ * Verify receipt hash integrity (without signature verification)
21
+ */
22
+ export function verifyReceiptHash(receipt: Receipt): VerificationResult {
23
+ const receiptCopy = { ...receipt } as any;
24
+ delete receiptCopy.receipt_hash;
25
+
26
+ const expectedHash = hash(canonicalizePayload(receiptCopy));
27
+
28
+ if (receipt.receipt_hash !== expectedHash) {
29
+ return { valid: false, error: 'HASH_MISMATCH' };
30
+ }
31
+
32
+ return { valid: true };
33
+ }
34
+
35
+ /**
36
+ * Verify a chain of receipts
37
+ */
38
+ export async function verifyReceiptChain(
39
+ receipts: Receipt[],
40
+ publicKey: Buffer
41
+ ): Promise<{
42
+ valid: boolean;
43
+ verified: number;
44
+ failed: Array<{ index: number; receipt_id: string; error: string }>;
45
+ }> {
46
+ const failed: Array<{ index: number; receipt_id: string; error: string }> = [];
47
+
48
+ for (let i = 0; i < receipts.length; i++) {
49
+ const receipt = receipts[i];
50
+
51
+ // Verify signature
52
+ const sigResult = await verifySignature(receipt, publicKey);
53
+ if (!sigResult.valid) {
54
+ failed.push({
55
+ index: i,
56
+ receipt_id: receipt.receipt_id,
57
+ error: sigResult.error || 'UNKNOWN'
58
+ });
59
+ continue;
60
+ }
61
+
62
+ // Verify chain linkage
63
+ if (i > 0) {
64
+ const prevReceipt = receipts[i - 1];
65
+ if (receipt.chain.prev_receipt_hash !== prevReceipt.receipt_hash) {
66
+ failed.push({
67
+ index: i,
68
+ receipt_id: receipt.receipt_id,
69
+ error: 'CHAIN_BREAK'
70
+ });
71
+ }
72
+ } else {
73
+ // First receipt should have null prev_receipt_hash
74
+ if (receipt.chain.prev_receipt_hash !== null) {
75
+ failed.push({
76
+ index: i,
77
+ receipt_id: receipt.receipt_id,
78
+ error: 'GENESIS_INVALID'
79
+ });
80
+ }
81
+ }
82
+
83
+ // Verify sequence
84
+ if (receipt.chain.sequence !== i + 1) {
85
+ failed.push({
86
+ index: i,
87
+ receipt_id: receipt.receipt_id,
88
+ error: 'SEQUENCE_GAP'
89
+ });
90
+ }
91
+ }
92
+
93
+ return {
94
+ valid: failed.length === 0,
95
+ verified: receipts.length - failed.length,
96
+ failed
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Full verification: receipts + log entries
102
+ */
103
+ export async function verifyFull(
104
+ receipts: Receipt[],
105
+ logEntries: LogEntry[],
106
+ publicKey: Buffer
107
+ ): Promise<{
108
+ receipts: { valid: boolean; verified: number; failed: any[] };
109
+ log: ChainVerificationResult;
110
+ crossCheck: { valid: boolean; mismatches: number[] };
111
+ }> {
112
+ // Verify receipts
113
+ const receiptResult = await verifyReceiptChain(receipts, publicKey);
114
+
115
+ // Verify log
116
+ const logResult = verifyLogChain(logEntries);
117
+
118
+ // Cross-check: each log entry should reference corresponding receipt
119
+ const mismatches: number[] = [];
120
+ for (let i = 0; i < Math.min(receipts.length, logEntries.length); i++) {
121
+ if (logEntries[i].receipt_hash !== receipts[i].receipt_hash) {
122
+ mismatches.push(i);
123
+ }
124
+ }
125
+
126
+ return {
127
+ receipts: receiptResult,
128
+ log: logResult,
129
+ crossCheck: {
130
+ valid: mismatches.length === 0,
131
+ mismatches
132
+ }
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Verify an anchor exists on-chain
138
+ */
139
+ export async function verifyAnchor(
140
+ logRoot: string,
141
+ contractAddress: string,
142
+ rpcUrl: string
143
+ ): Promise<{
144
+ exists: boolean;
145
+ sequence?: number;
146
+ timestamp?: number;
147
+ error?: string;
148
+ }> {
149
+ try {
150
+ const { ethers } = await import('ethers');
151
+
152
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
153
+
154
+ const abi = [
155
+ 'function verify(bytes32 logRoot) view returns (bool exists, uint256 sequence, uint256 timestamp)'
156
+ ];
157
+
158
+ const contract = new ethers.Contract(contractAddress, abi, provider);
159
+
160
+ const [exists, sequence, timestamp] = await contract.verify(
161
+ '0x' + logRoot
162
+ );
163
+
164
+ return {
165
+ exists,
166
+ sequence: Number(sequence),
167
+ timestamp: Number(timestamp)
168
+ };
169
+ } catch (error: any) {
170
+ return {
171
+ exists: false,
172
+ error: error.message
173
+ };
174
+ }
175
+ }
package/test/demo.js ADDED
@@ -0,0 +1,159 @@
1
+ /**
2
+ * PQC Receipt SDK - Demonstration Script
3
+ *
4
+ * This script demonstrates the complete flow:
5
+ * 1. Generate ML-DSA-65 key pair
6
+ * 2. Create receipts with PQC signatures
7
+ * 3. Append to tamper-evident log
8
+ * 4. Verify independently
9
+ *
10
+ * Run: node test/demo.js
11
+ */
12
+
13
+ import {
14
+ generateKeyPair,
15
+ ReceiptGenerator,
16
+ AppendOnlyLog,
17
+ verifyReceipt,
18
+ verifyReceiptChain,
19
+ ALGORITHM,
20
+ VERSION,
21
+ PUBLIC_KEY_SIZE,
22
+ SECRET_KEY_SIZE,
23
+ SIGNATURE_SIZE
24
+ } from '../src/index.js';
25
+
26
+ console.log(`
27
+ ╔═══════════════════════════════════════════════════════════════╗
28
+ ║ PQC Receipt SDK Demo - ML-DSA-65 (FIPS 204) ║
29
+ ║ Version ${VERSION} ║
30
+ ║ Using @noble/post-quantum ║
31
+ ╚═══════════════════════════════════════════════════════════════╝
32
+ `);
33
+
34
+ async function runDemo() {
35
+ console.log('═══════════════════════════════════════════════════════════════');
36
+
37
+ // Step 1: Generate key pair
38
+ console.log('\n📐 Step 1: Generating ML-DSA-65 key pair...');
39
+ const startKey = Date.now();
40
+
41
+ const keyPair = await generateKeyPair();
42
+
43
+ console.log(` ✓ Key generated in ${Date.now() - startKey}ms`);
44
+ console.log(` ✓ Public key: ${keyPair.publicKey.length} bytes (expected: ${PUBLIC_KEY_SIZE})`);
45
+ console.log(` ✓ Secret key: ${keyPair.secretKey.length} bytes (expected: ${SECRET_KEY_SIZE})`);
46
+ console.log(` ✓ Key ID: ${keyPair.keyId.slice(0, 32)}...`);
47
+
48
+ // Step 2: Create receipt generator
49
+ console.log('\n📝 Step 2: Creating receipt generator...');
50
+ const generator = new ReceiptGenerator(keyPair);
51
+ console.log(' ✓ Generator initialized');
52
+
53
+ // Step 3: Generate receipts
54
+ console.log('\n🔏 Step 3: Generating PQC-signed receipts...');
55
+ const receipts = [];
56
+ const log = new AppendOnlyLog();
57
+
58
+ const operations = [
59
+ { type: 'intercept', method: 'POST', endpoint: '/api/v1/verify', req: { user: 'alice' }, res: { verified: true } },
60
+ { type: 'verify', method: 'GET', endpoint: '/api/v1/status', req: {}, res: { status: 'active' } },
61
+ { type: 'intercept', method: 'POST', endpoint: '/api/v1/consent', req: { purpose: 'marketing' }, res: { granted: true } },
62
+ { type: 'revoke', method: 'DELETE', endpoint: '/api/v1/consent/123', req: {}, res: { revoked: true } },
63
+ { type: 'anchor', method: 'POST', endpoint: '/api/v1/anchor', req: { checkpoint: true }, res: { anchored: true } }
64
+ ];
65
+
66
+ for (const op of operations) {
67
+ const startSign = Date.now();
68
+
69
+ const receipt = await generator.generate({
70
+ type: op.type,
71
+ method: op.method,
72
+ endpoint: op.endpoint,
73
+ requestBody: op.req,
74
+ responseBody: op.res,
75
+ actorId: 'demo-user',
76
+ orgId: 'demo-org'
77
+ });
78
+
79
+ receipts.push(receipt);
80
+ log.append(receipt);
81
+
82
+ const sigSize = Buffer.from(receipt.pqc_signature.signature, 'base64').length;
83
+ console.log(` ✓ Receipt #${receipt.chain.sequence}: ${op.type.padEnd(10)} ${op.endpoint.padEnd(25)} (${Date.now() - startSign}ms, sig: ${sigSize} bytes)`);
84
+ }
85
+
86
+ // Step 4: Verify receipts individually
87
+ console.log('\n✅ Step 4: Verifying receipts independently...');
88
+ let allValid = true;
89
+ for (const receipt of receipts) {
90
+ const result = await verifyReceipt(receipt, keyPair.publicKey);
91
+ const status = result.valid ? '✓' : '✗';
92
+ console.log(` ${status} Receipt #${receipt.chain.sequence}: ${result.valid ? 'VALID' : result.error}`);
93
+ if (!result.valid) allValid = false;
94
+ }
95
+
96
+ // Step 5: Verify chain
97
+ console.log('\n🔗 Step 5: Verifying receipt chain integrity...');
98
+ const chainResult = await verifyReceiptChain(receipts, keyPair.publicKey);
99
+ console.log(` Chain valid: ${chainResult.valid}`);
100
+ console.log(` Verified: ${chainResult.verified}/${receipts.length}`);
101
+ if (chainResult.failed.length > 0) {
102
+ console.log(` Failed: ${JSON.stringify(chainResult.failed)}`);
103
+ }
104
+
105
+ // Step 6: Verify log
106
+ console.log('\n📋 Step 6: Verifying append-only log...');
107
+ const logResult = log.verify();
108
+ console.log(` Log valid: ${logResult.valid}`);
109
+ console.log(` Entries: ${logResult.length}`);
110
+ console.log(` Log root: ${log.getLogRoot()?.slice(0, 32)}...`);
111
+ if (logResult.breaks.length > 0) {
112
+ console.log(` Breaks: ${JSON.stringify(logResult.breaks)}`);
113
+ }
114
+
115
+ // Step 7: Tamper detection test
116
+ console.log('\n🔍 Step 7: Testing tamper detection...');
117
+ const tamperedReceipt = JSON.parse(JSON.stringify(receipts[2]));
118
+ tamperedReceipt.operation.response_hash = 'tampered_hash_value';
119
+
120
+ const tamperResult = await verifyReceipt(tamperedReceipt, keyPair.publicKey);
121
+ console.log(` Tampered receipt verification: ${tamperResult.valid ? 'FAILED TO DETECT' : 'DETECTED ✓'}`);
122
+ console.log(` Error returned: ${tamperResult.error}`);
123
+
124
+ // Summary
125
+ const allPassed = allValid && chainResult.valid && logResult.valid && !tamperResult.valid;
126
+
127
+ console.log(`
128
+ ╔═══════════════════════════════════════════════════════════════╗
129
+ ║ DEMO COMPLETE ║
130
+ ╠═══════════════════════════════════════════════════════════════╣
131
+ ║ Algorithm: ${ALGORITHM.padEnd(43)}║
132
+ ║ Hash: SHA3-256 ║
133
+ ║ Receipts: ${receipts.length.toString().padEnd(43)}║
134
+ ║ All signatures: ${(allValid ? 'VALID' : 'INVALID').padEnd(43)}║
135
+ ║ Chain integrity: ${(chainResult.valid ? 'VERIFIED' : 'BROKEN').padEnd(43)}║
136
+ ║ Log integrity: ${(logResult.valid ? 'VERIFIED' : 'BROKEN').padEnd(43)}║
137
+ ║ Tamper detection: ${(!tamperResult.valid ? 'WORKING' : 'FAILED').padEnd(43)}║
138
+ ╠═══════════════════════════════════════════════════════════════╣
139
+ ║ OVERALL: ${(allPassed ? '✓ ALL TESTS PASSED' : '✗ SOME TESTS FAILED').padEnd(43)}║
140
+ ╚═══════════════════════════════════════════════════════════════╝
141
+ `);
142
+
143
+ // Output sample receipt
144
+ console.log('\n📄 Sample Receipt (JSON):');
145
+ console.log(JSON.stringify(receipts[0], null, 2));
146
+
147
+ // Output log entry
148
+ console.log('\n📜 Sample Log Entry:');
149
+ console.log(JSON.stringify(log.getEntries()[0], null, 2));
150
+
151
+ return allPassed;
152
+ }
153
+
154
+ runDemo()
155
+ .then(passed => process.exit(passed ? 0 : 1))
156
+ .catch(err => {
157
+ console.error('Demo failed:', err);
158
+ process.exit(1);
159
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "resolveJsonModule": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist", "test", "contracts"]
20
+ }