@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.
- package/LICENSE.md +31 -0
- package/LICENSES/FinalBoss-Commercial.txt +69 -0
- package/LICENSES/PolyForm-Noncommercial-1.0.0.txt +129 -0
- package/README.md +167 -0
- package/bin/pqc-verify.js +112 -0
- package/contracts/ReceiptAnchor.sol +160 -0
- package/package.json +40 -0
- package/src/crypto.js +153 -0
- package/src/crypto.ts +183 -0
- package/src/index.js +65 -0
- package/src/index.ts +78 -0
- package/src/log.js +193 -0
- package/src/log.ts +195 -0
- package/src/receipt.js +141 -0
- package/src/receipt.ts +148 -0
- package/src/types.ts +86 -0
- package/src/verifier.js +115 -0
- package/src/verifier.ts +175 -0
- package/test/demo.js +159 -0
- package/tsconfig.json +20 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# License
|
|
2
|
+
|
|
3
|
+
This project is dual-licensed.
|
|
4
|
+
|
|
5
|
+
## Non-Commercial Use
|
|
6
|
+
|
|
7
|
+
Permitted under [PolyForm Noncommercial License 1.0.0](./LICENSES/PolyForm-Noncommercial-1.0.0.txt).
|
|
8
|
+
|
|
9
|
+
You may use, modify, and distribute this software for any non-commercial purpose, including:
|
|
10
|
+
- Personal projects and research
|
|
11
|
+
- Academic and educational use
|
|
12
|
+
- Non-profit organizations
|
|
13
|
+
- Evaluation and testing
|
|
14
|
+
|
|
15
|
+
## Commercial Use
|
|
16
|
+
|
|
17
|
+
Requires a paid commercial license. See [LICENSES/FinalBoss-Commercial.txt](./LICENSES/FinalBoss-Commercial.txt).
|
|
18
|
+
|
|
19
|
+
Commercial use includes:
|
|
20
|
+
- Use in revenue-generating products or services
|
|
21
|
+
- Use by for-profit companies in production
|
|
22
|
+
- Integration into commercial software or SaaS
|
|
23
|
+
|
|
24
|
+
**To obtain a commercial license:**
|
|
25
|
+
|
|
26
|
+
- Email: abraham@finalbosstech.com
|
|
27
|
+
- Web: https://finalbosstech.com
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
Copyright (c) 2025 FinalBoss Technologies. All rights reserved.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
FinalBoss Commercial License Agreement
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 FinalBoss Technologies. All rights reserved.
|
|
4
|
+
|
|
5
|
+
COMMERCIAL LICENSE TERMS
|
|
6
|
+
|
|
7
|
+
This Commercial License Agreement ("Agreement") is entered into between
|
|
8
|
+
FinalBoss Technologies ("Licensor") and the entity or individual obtaining
|
|
9
|
+
this license ("Licensee").
|
|
10
|
+
|
|
11
|
+
1. GRANT OF LICENSE
|
|
12
|
+
|
|
13
|
+
Subject to payment of applicable license fees and compliance with this
|
|
14
|
+
Agreement, Licensor grants Licensee a non-exclusive, non-transferable,
|
|
15
|
+
worldwide license to:
|
|
16
|
+
|
|
17
|
+
a) Use the Software in commercial applications and services
|
|
18
|
+
b) Integrate the Software into products that generate revenue
|
|
19
|
+
c) Deploy the Software in production environments
|
|
20
|
+
d) Modify the Software for internal use
|
|
21
|
+
|
|
22
|
+
2. RESTRICTIONS
|
|
23
|
+
|
|
24
|
+
Licensee may NOT:
|
|
25
|
+
|
|
26
|
+
a) Sublicense, sell, or redistribute the Software as a standalone product
|
|
27
|
+
b) Remove or alter any proprietary notices
|
|
28
|
+
c) Use the Software to compete directly with Licensor's services
|
|
29
|
+
d) Reverse engineer the Software except as permitted by law
|
|
30
|
+
|
|
31
|
+
3. FEES
|
|
32
|
+
|
|
33
|
+
License fees are determined based on usage tier and deployment scale.
|
|
34
|
+
Contact abraham@finalbosstech.com for pricing.
|
|
35
|
+
|
|
36
|
+
4. SUPPORT
|
|
37
|
+
|
|
38
|
+
Commercial licensees receive:
|
|
39
|
+
- Priority email support
|
|
40
|
+
- Access to private documentation
|
|
41
|
+
- Security advisories
|
|
42
|
+
- Version update notifications
|
|
43
|
+
|
|
44
|
+
5. WARRANTY DISCLAIMER
|
|
45
|
+
|
|
46
|
+
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. LICENSOR
|
|
47
|
+
DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING MERCHANTABILITY
|
|
48
|
+
AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
49
|
+
|
|
50
|
+
6. LIMITATION OF LIABILITY
|
|
51
|
+
|
|
52
|
+
IN NO EVENT SHALL LICENSOR BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
|
|
53
|
+
OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THE SOFTWARE.
|
|
54
|
+
|
|
55
|
+
7. TERMINATION
|
|
56
|
+
|
|
57
|
+
This license terminates automatically if Licensee breaches any term.
|
|
58
|
+
Upon termination, Licensee must cease all use and destroy all copies.
|
|
59
|
+
|
|
60
|
+
8. GOVERNING LAW
|
|
61
|
+
|
|
62
|
+
This Agreement is governed by the laws of the State of California, USA.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
To obtain a Commercial License:
|
|
67
|
+
|
|
68
|
+
Email: abraham@finalbosstech.com
|
|
69
|
+
Web: https://finalbosstech.com
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# PolyForm Noncommercial License 1.0.0
|
|
2
|
+
|
|
3
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
In order to get any license under these terms, you must agree
|
|
8
|
+
to them as both strict obligations and conditions to all
|
|
9
|
+
your licenses.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a copyright license for the software
|
|
14
|
+
to do everything you might do with the software that would
|
|
15
|
+
otherwise infringe the licensor's copyright in it for any
|
|
16
|
+
permitted purpose. However, you may only distribute the
|
|
17
|
+
software according to [Distribution License](#distribution-license)
|
|
18
|
+
and make changes or new works based on the software according
|
|
19
|
+
to [Changes and New Works License](#changes-and-new-works-license).
|
|
20
|
+
|
|
21
|
+
## Distribution License
|
|
22
|
+
|
|
23
|
+
The licensor grants you an additional copyright license to
|
|
24
|
+
distribute copies of the software. Your license to distribute
|
|
25
|
+
covers distributing the software with changes and new works
|
|
26
|
+
permitted by [Changes and New Works License](#changes-and-new-works-license).
|
|
27
|
+
|
|
28
|
+
## Notices
|
|
29
|
+
|
|
30
|
+
You must ensure that anyone who gets a copy of any part of
|
|
31
|
+
the software from you also gets a copy of these terms or the
|
|
32
|
+
URL for them above, as well as copies of any plain-text lines
|
|
33
|
+
beginning with `Required Notice:` that the licensor provided
|
|
34
|
+
with the software. For example:
|
|
35
|
+
|
|
36
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
37
|
+
|
|
38
|
+
## Changes and New Works License
|
|
39
|
+
|
|
40
|
+
The licensor grants you an additional copyright license to
|
|
41
|
+
make changes and new works based on the software for any
|
|
42
|
+
permitted purpose.
|
|
43
|
+
|
|
44
|
+
## Patent License
|
|
45
|
+
|
|
46
|
+
The licensor grants you a patent license for the software that
|
|
47
|
+
covers patent claims the licensor can license, or becomes able
|
|
48
|
+
to license, that you would infringe by using the software.
|
|
49
|
+
|
|
50
|
+
## Noncommercial Purposes
|
|
51
|
+
|
|
52
|
+
Any noncommercial purpose is a permitted purpose.
|
|
53
|
+
|
|
54
|
+
## Personal Uses
|
|
55
|
+
|
|
56
|
+
Personal use for research, experiment, and testing for
|
|
57
|
+
the benefit of public knowledge, personal study, private
|
|
58
|
+
entertainment, hobby projects, amateur pursuits, or religious
|
|
59
|
+
observance, without any anticipated commercial application,
|
|
60
|
+
is use for a permitted purpose.
|
|
61
|
+
|
|
62
|
+
## Noncommercial Organizations
|
|
63
|
+
|
|
64
|
+
Use by any charitable organization, educational institution,
|
|
65
|
+
public research organization, public safety or health
|
|
66
|
+
organization, environmental protection organization,
|
|
67
|
+
or government institution is use for a permitted purpose
|
|
68
|
+
regardless of the source of funding or obligations resulting
|
|
69
|
+
from the funding.
|
|
70
|
+
|
|
71
|
+
## Fair Use
|
|
72
|
+
|
|
73
|
+
You may have "fair use" rights for the software under the
|
|
74
|
+
law. These terms do not limit them.
|
|
75
|
+
|
|
76
|
+
## No Other Rights
|
|
77
|
+
|
|
78
|
+
These terms do not allow you to sublicense or transfer any of
|
|
79
|
+
your licenses to anyone else, or prevent the licensor from
|
|
80
|
+
granting licenses to anyone else. These terms do not imply
|
|
81
|
+
any other licenses.
|
|
82
|
+
|
|
83
|
+
## Patent Defense
|
|
84
|
+
|
|
85
|
+
If you make any written claim that the software infringes or
|
|
86
|
+
contributes to infringement of any patent, your patent license
|
|
87
|
+
for the software granted under these terms ends immediately. If
|
|
88
|
+
your company makes such a claim, your patent license ends
|
|
89
|
+
immediately for work on behalf of your company.
|
|
90
|
+
|
|
91
|
+
## Violations
|
|
92
|
+
|
|
93
|
+
The first time you are notified in writing that you have
|
|
94
|
+
violated any of these terms, or done anything with the software
|
|
95
|
+
not covered by your licenses, your licenses can nonetheless
|
|
96
|
+
continue if you come into full compliance with these terms,
|
|
97
|
+
and take practical steps to correct past violations, within
|
|
98
|
+
32 days of receiving notice. Otherwise, all your licenses
|
|
99
|
+
end immediately.
|
|
100
|
+
|
|
101
|
+
## No Liability
|
|
102
|
+
|
|
103
|
+
***As far as the law allows, the software comes as is, without
|
|
104
|
+
any warranty or condition, and the licensor will not be liable
|
|
105
|
+
to you for any damages arising out of these terms or the use
|
|
106
|
+
or nature of the software, under any kind of legal claim.***
|
|
107
|
+
|
|
108
|
+
## Definitions
|
|
109
|
+
|
|
110
|
+
The **licensor** is the individual or entity offering these
|
|
111
|
+
terms, and the **software** is the software the licensor makes
|
|
112
|
+
available under these terms.
|
|
113
|
+
|
|
114
|
+
**You** refers to the individual or entity agreeing to these
|
|
115
|
+
terms.
|
|
116
|
+
|
|
117
|
+
**Your company** is any legal entity, sole proprietorship,
|
|
118
|
+
or other kind of organization that you work for, plus all
|
|
119
|
+
organizations that have control over, are under the control of,
|
|
120
|
+
or are under common control with that organization. **Control**
|
|
121
|
+
means ownership of substantially all the assets of an entity,
|
|
122
|
+
or the power to direct its management and policies by vote,
|
|
123
|
+
contract, or otherwise. Control can be direct or indirect.
|
|
124
|
+
|
|
125
|
+
**Your licenses** are all the licenses granted to you for the
|
|
126
|
+
software under these terms.
|
|
127
|
+
|
|
128
|
+
**Use** means anything you do with the software requiring one
|
|
129
|
+
of your licenses.
|
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# @finalboss/pqc-receipt-sdk
|
|
2
|
+
|
|
3
|
+
**Post-Quantum Cryptographic Receipt Generation SDK**
|
|
4
|
+
|
|
5
|
+
Generate tamper-evident, cryptographically signed receipts using ML-DSA-65 (FIPS 204 / CRYSTALS-Dilithium).
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **ML-DSA-65 Signatures** - NIST-standardized post-quantum algorithm
|
|
10
|
+
- **Hash Chaining** - Each receipt links to previous via SHA3-256
|
|
11
|
+
- **Append-Only Log** - Tamper-evident log with integrity verification
|
|
12
|
+
- **On-Chain Anchoring** - Optional Ethereum/Hardhat anchoring support
|
|
13
|
+
- **RFC 8785 Canonicalization** - Deterministic JSON serialization
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @finalboss/pqc-receipt-sdk @openforge-sh/liboqs-node
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
> **Note**: `@openforge-sh/liboqs-node` provides ML-DSA-65 via WASM.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import {
|
|
27
|
+
generateKeyPair,
|
|
28
|
+
ReceiptGenerator,
|
|
29
|
+
AppendOnlyLog,
|
|
30
|
+
verifyReceipt
|
|
31
|
+
} from '@finalboss/pqc-receipt-sdk';
|
|
32
|
+
|
|
33
|
+
// 1. Generate ML-DSA-65 key pair
|
|
34
|
+
const keyPair = await generateKeyPair();
|
|
35
|
+
|
|
36
|
+
// 2. Create receipt generator
|
|
37
|
+
const generator = new ReceiptGenerator(keyPair);
|
|
38
|
+
const log = new AppendOnlyLog();
|
|
39
|
+
|
|
40
|
+
// 3. Generate a signed receipt
|
|
41
|
+
const receipt = await generator.generate({
|
|
42
|
+
type: 'intercept',
|
|
43
|
+
method: 'POST',
|
|
44
|
+
endpoint: '/api/v1/verify',
|
|
45
|
+
requestBody: { user_id: 'alice' },
|
|
46
|
+
responseBody: { verified: true },
|
|
47
|
+
actorId: 'alice',
|
|
48
|
+
orgId: 'acme-corp'
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// 4. Append to log
|
|
52
|
+
log.append(receipt);
|
|
53
|
+
|
|
54
|
+
// 5. Verify independently
|
|
55
|
+
const result = await verifyReceipt(receipt, keyPair.publicKey);
|
|
56
|
+
console.log(result.valid); // true
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Receipt Schema
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"version": "1.0",
|
|
64
|
+
"receipt_id": "uuid",
|
|
65
|
+
"timestamp": "ISO8601",
|
|
66
|
+
"operation": {
|
|
67
|
+
"type": "intercept|verify|revoke|anchor",
|
|
68
|
+
"method": "POST",
|
|
69
|
+
"endpoint": "/api/...",
|
|
70
|
+
"request_hash": "sha3-256",
|
|
71
|
+
"response_hash": "sha3-256"
|
|
72
|
+
},
|
|
73
|
+
"actor": {
|
|
74
|
+
"id": "subject-id",
|
|
75
|
+
"org_id": "org-id",
|
|
76
|
+
"key_id": "public-key-fingerprint"
|
|
77
|
+
},
|
|
78
|
+
"chain": {
|
|
79
|
+
"sequence": 1,
|
|
80
|
+
"prev_receipt_hash": "sha3-256 or null"
|
|
81
|
+
},
|
|
82
|
+
"pqc_signature": {
|
|
83
|
+
"algorithm": "ML-DSA-65",
|
|
84
|
+
"public_key_id": "fingerprint",
|
|
85
|
+
"signature": "base64"
|
|
86
|
+
},
|
|
87
|
+
"receipt_hash": "sha3-256"
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## CLI Verification
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Verify single receipt
|
|
95
|
+
npx pqc-verify receipt --file receipt.json --pubkey key.pub
|
|
96
|
+
|
|
97
|
+
# Verify log chain
|
|
98
|
+
npx pqc-verify chain --log receipts.jsonl
|
|
99
|
+
|
|
100
|
+
# Verify on-chain anchor
|
|
101
|
+
npx pqc-verify anchor --log-root <hash> --contract <addr> --rpc <url>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## API Reference
|
|
105
|
+
|
|
106
|
+
### Key Management
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Generate new key pair
|
|
110
|
+
const keyPair = await generateKeyPair();
|
|
111
|
+
// { publicKey: Buffer, secretKey: Buffer, keyId: string }
|
|
112
|
+
|
|
113
|
+
// Compute key fingerprint
|
|
114
|
+
const keyId = computeKeyId(publicKey);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Receipt Generation
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// Stateful generator (maintains chain)
|
|
121
|
+
const generator = new ReceiptGenerator(keyPair);
|
|
122
|
+
const receipt = await generator.generate({ ... });
|
|
123
|
+
|
|
124
|
+
// Stateless creation
|
|
125
|
+
const receipt = await createReceipt({ ..., chain: { sequence: 1, prev_receipt_hash: null } }, keyPair);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Log Management
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const log = new AppendOnlyLog();
|
|
132
|
+
log.append(receipt);
|
|
133
|
+
|
|
134
|
+
const result = log.verify();
|
|
135
|
+
// { valid: boolean, length: number, breaks: [] }
|
|
136
|
+
|
|
137
|
+
const root = log.getLogRoot();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Verification
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// Verify single receipt
|
|
144
|
+
const result = await verifyReceipt(receipt, publicKey);
|
|
145
|
+
|
|
146
|
+
// Verify chain of receipts
|
|
147
|
+
const result = await verifyReceiptChain(receipts, publicKey);
|
|
148
|
+
|
|
149
|
+
// Verify on-chain anchor
|
|
150
|
+
const result = await verifyAnchor(logRoot, contractAddr, rpcUrl);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Security Notes
|
|
154
|
+
|
|
155
|
+
- **Experimental Library**: liboqs implements FIPS 204 draft. For production, use hardened implementation.
|
|
156
|
+
- **Key Storage**: Store secret keys encrypted. Consider HSM for production.
|
|
157
|
+
- **Clock Sync**: Use NTP-synchronized time sources.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
Apache-2.0
|
|
162
|
+
|
|
163
|
+
## Links
|
|
164
|
+
|
|
165
|
+
- [NIST FIPS 204 (ML-DSA)](https://csrc.nist.gov/pubs/fips/204/final)
|
|
166
|
+
- [liboqs](https://openquantumsafe.org/liboqs/)
|
|
167
|
+
- [RFC 8785 (JCS)](https://datatracker.ietf.org/doc/html/rfc8785)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PQC Receipt Verification CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* pqc-verify receipt --file receipt.json --pubkey key.pub
|
|
8
|
+
* pqc-verify chain --log receipts.jsonl
|
|
9
|
+
* pqc-verify anchor --log-root <hash> --contract <addr> --rpc <url>
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync } from 'fs';
|
|
13
|
+
import { verifyReceipt, verifyLogChain, verifyAnchor } from '../src/index.js';
|
|
14
|
+
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const command = args[0];
|
|
17
|
+
|
|
18
|
+
function parseArgs(args) {
|
|
19
|
+
const parsed = {};
|
|
20
|
+
for (let i = 1; i < args.length; i += 2) {
|
|
21
|
+
const key = args[i].replace(/^--/, '');
|
|
22
|
+
parsed[key] = args[i + 1];
|
|
23
|
+
}
|
|
24
|
+
return parsed;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function printResult(label, result) {
|
|
28
|
+
const status = result.valid ? '\x1b[32m✓ PASS\x1b[0m' : '\x1b[31m✗ FAIL\x1b[0m';
|
|
29
|
+
console.log(`\n${label}: ${status}`);
|
|
30
|
+
console.log(JSON.stringify(result, null, 2));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function main() {
|
|
34
|
+
switch (command) {
|
|
35
|
+
case 'receipt': {
|
|
36
|
+
const opts = parseArgs(args);
|
|
37
|
+
if (!opts.file || !opts.pubkey) {
|
|
38
|
+
console.error('Usage: pqc-verify receipt --file <receipt.json> --pubkey <key.pub>');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const receipt = JSON.parse(readFileSync(opts.file, 'utf8'));
|
|
43
|
+
const publicKey = Buffer.from(readFileSync(opts.pubkey, 'utf8'), 'base64');
|
|
44
|
+
|
|
45
|
+
const result = await verifyReceipt(receipt, publicKey);
|
|
46
|
+
printResult('Receipt Verification', result);
|
|
47
|
+
process.exit(result.valid ? 0 : 1);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
case 'chain': {
|
|
52
|
+
const opts = parseArgs(args);
|
|
53
|
+
if (!opts.log) {
|
|
54
|
+
console.error('Usage: pqc-verify chain --log <receipts.jsonl>');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const lines = readFileSync(opts.log, 'utf8').trim().split('\n');
|
|
59
|
+
const entries = lines.map(line => JSON.parse(line));
|
|
60
|
+
|
|
61
|
+
const result = verifyLogChain(entries);
|
|
62
|
+
printResult('Chain Verification', result);
|
|
63
|
+
process.exit(result.valid ? 0 : 3);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
case 'anchor': {
|
|
68
|
+
const opts = parseArgs(args);
|
|
69
|
+
if (!opts['log-root'] || !opts.contract || !opts.rpc) {
|
|
70
|
+
console.error('Usage: pqc-verify anchor --log-root <hash> --contract <addr> --rpc <url>');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const result = await verifyAnchor(opts['log-root'], opts.contract, opts.rpc);
|
|
75
|
+
printResult('Anchor Verification', result);
|
|
76
|
+
process.exit(result.exists ? 0 : 4);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
case 'help':
|
|
81
|
+
case '--help':
|
|
82
|
+
case '-h':
|
|
83
|
+
default:
|
|
84
|
+
console.log(`
|
|
85
|
+
PQC Receipt Verification CLI
|
|
86
|
+
|
|
87
|
+
Commands:
|
|
88
|
+
receipt Verify a single receipt's ML-DSA-65 signature
|
|
89
|
+
chain Verify log chain integrity
|
|
90
|
+
anchor Verify anchor exists on-chain
|
|
91
|
+
|
|
92
|
+
Examples:
|
|
93
|
+
pqc-verify receipt --file receipt.json --pubkey signing_key.pub
|
|
94
|
+
pqc-verify chain --log receipts.jsonl
|
|
95
|
+
pqc-verify anchor --log-root abc123... --contract 0x... --rpc http://localhost:8545
|
|
96
|
+
|
|
97
|
+
Exit Codes:
|
|
98
|
+
0 Verification passed
|
|
99
|
+
1 Signature invalid
|
|
100
|
+
2 Hash mismatch
|
|
101
|
+
3 Chain break detected
|
|
102
|
+
4 Anchor not found
|
|
103
|
+
5 Key mismatch
|
|
104
|
+
`);
|
|
105
|
+
process.exit(0);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
main().catch(err => {
|
|
110
|
+
console.error('Error:', err.message);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
pragma solidity ^0.8.19;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @title ReceiptAnchor
|
|
6
|
+
* @notice Anchors PQC receipt log roots to provide external timestamping proof
|
|
7
|
+
* @dev Used with append-only log to create verifiable anchoring points
|
|
8
|
+
*/
|
|
9
|
+
contract ReceiptAnchor {
|
|
10
|
+
/// @notice Emitted when a new log root is anchored
|
|
11
|
+
event Anchored(
|
|
12
|
+
bytes32 indexed logRoot,
|
|
13
|
+
uint256 indexed sequence,
|
|
14
|
+
address indexed anchor,
|
|
15
|
+
uint256 timestamp
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/// @notice Anchor record structure
|
|
19
|
+
struct Anchor {
|
|
20
|
+
bytes32 logRoot;
|
|
21
|
+
uint256 sequence;
|
|
22
|
+
uint256 timestamp;
|
|
23
|
+
address anchoredBy;
|
|
24
|
+
bool exists;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// @notice Mapping of log roots to their anchor records
|
|
28
|
+
mapping(bytes32 => Anchor) public anchors;
|
|
29
|
+
|
|
30
|
+
/// @notice Ordered history of all anchored roots
|
|
31
|
+
bytes32[] public anchorHistory;
|
|
32
|
+
|
|
33
|
+
/// @notice Mapping of authorized anchors (addresses allowed to anchor)
|
|
34
|
+
mapping(address => bool) public authorizedAnchors;
|
|
35
|
+
|
|
36
|
+
/// @notice Contract owner
|
|
37
|
+
address public owner;
|
|
38
|
+
|
|
39
|
+
modifier onlyOwner() {
|
|
40
|
+
require(msg.sender == owner, "Not owner");
|
|
41
|
+
_;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
modifier onlyAuthorized() {
|
|
45
|
+
require(authorizedAnchors[msg.sender] || msg.sender == owner, "Not authorized");
|
|
46
|
+
_;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
constructor() {
|
|
50
|
+
owner = msg.sender;
|
|
51
|
+
authorizedAnchors[msg.sender] = true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @notice Anchor a log root
|
|
56
|
+
* @param logRoot The SHA3-256 hash of the log entry being anchored
|
|
57
|
+
* @param sequence The monotonic sequence number in the log
|
|
58
|
+
*/
|
|
59
|
+
function anchor(bytes32 logRoot, uint256 sequence) external onlyAuthorized {
|
|
60
|
+
require(!anchors[logRoot].exists, "Already anchored");
|
|
61
|
+
require(logRoot != bytes32(0), "Invalid log root");
|
|
62
|
+
|
|
63
|
+
anchors[logRoot] = Anchor({
|
|
64
|
+
logRoot: logRoot,
|
|
65
|
+
sequence: sequence,
|
|
66
|
+
timestamp: block.timestamp,
|
|
67
|
+
anchoredBy: msg.sender,
|
|
68
|
+
exists: true
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
anchorHistory.push(logRoot);
|
|
72
|
+
|
|
73
|
+
emit Anchored(logRoot, sequence, msg.sender, block.timestamp);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @notice Verify if a log root has been anchored
|
|
78
|
+
* @param logRoot The log root to verify
|
|
79
|
+
* @return exists Whether the anchor exists
|
|
80
|
+
* @return sequence The sequence number if anchored
|
|
81
|
+
* @return timestamp The block timestamp when anchored
|
|
82
|
+
*/
|
|
83
|
+
function verify(bytes32 logRoot) external view returns (
|
|
84
|
+
bool exists,
|
|
85
|
+
uint256 sequence,
|
|
86
|
+
uint256 timestamp
|
|
87
|
+
) {
|
|
88
|
+
Anchor memory a = anchors[logRoot];
|
|
89
|
+
return (a.exists, a.sequence, a.timestamp);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @notice Get full anchor details
|
|
94
|
+
* @param logRoot The log root to query
|
|
95
|
+
* @return The complete Anchor struct
|
|
96
|
+
*/
|
|
97
|
+
function getAnchor(bytes32 logRoot) external view returns (Anchor memory) {
|
|
98
|
+
return anchors[logRoot];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @notice Get the total number of anchors
|
|
103
|
+
* @return The count of anchored log roots
|
|
104
|
+
*/
|
|
105
|
+
function getAnchorCount() external view returns (uint256) {
|
|
106
|
+
return anchorHistory.length;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @notice Get anchor at specific index in history
|
|
111
|
+
* @param index The index in the anchor history
|
|
112
|
+
* @return The log root at that index
|
|
113
|
+
*/
|
|
114
|
+
function getAnchorAt(uint256 index) external view returns (bytes32) {
|
|
115
|
+
require(index < anchorHistory.length, "Index out of bounds");
|
|
116
|
+
return anchorHistory[index];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @notice Get the most recent anchor
|
|
121
|
+
* @return logRoot The most recently anchored log root
|
|
122
|
+
* @return sequence The sequence number
|
|
123
|
+
* @return timestamp The timestamp
|
|
124
|
+
*/
|
|
125
|
+
function getLatestAnchor() external view returns (
|
|
126
|
+
bytes32 logRoot,
|
|
127
|
+
uint256 sequence,
|
|
128
|
+
uint256 timestamp
|
|
129
|
+
) {
|
|
130
|
+
require(anchorHistory.length > 0, "No anchors");
|
|
131
|
+
bytes32 latest = anchorHistory[anchorHistory.length - 1];
|
|
132
|
+
Anchor memory a = anchors[latest];
|
|
133
|
+
return (a.logRoot, a.sequence, a.timestamp);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @notice Authorize an address to anchor
|
|
138
|
+
* @param addr The address to authorize
|
|
139
|
+
*/
|
|
140
|
+
function authorize(address addr) external onlyOwner {
|
|
141
|
+
authorizedAnchors[addr] = true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @notice Revoke authorization from an address
|
|
146
|
+
* @param addr The address to revoke
|
|
147
|
+
*/
|
|
148
|
+
function revoke(address addr) external onlyOwner {
|
|
149
|
+
authorizedAnchors[addr] = false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @notice Transfer ownership
|
|
154
|
+
* @param newOwner The new owner address
|
|
155
|
+
*/
|
|
156
|
+
function transferOwnership(address newOwner) external onlyOwner {
|
|
157
|
+
require(newOwner != address(0), "Invalid address");
|
|
158
|
+
owner = newOwner;
|
|
159
|
+
}
|
|
160
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@finalbosstech/pqc-receipt-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Post-Quantum Cryptographic Receipt Generation SDK (ML-DSA-65)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"demo": "node test/demo.js",
|
|
9
|
+
"verify": "node bin/pqc-verify.js"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"pqc-verify": "./bin/pqc-verify.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@noble/post-quantum": "^0.5.2",
|
|
16
|
+
"@noble/hashes": "^1.7.0",
|
|
17
|
+
"canonicalize": "^2.0.0",
|
|
18
|
+
"uuid": "^9.0.1"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"ethers": "^6.9.0"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18.0.0"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"post-quantum",
|
|
28
|
+
"cryptography",
|
|
29
|
+
"ml-dsa",
|
|
30
|
+
"dilithium",
|
|
31
|
+
"receipts",
|
|
32
|
+
"audit",
|
|
33
|
+
"governance"
|
|
34
|
+
],
|
|
35
|
+
"license": "PolyForm-Noncommercial-1.0.0 OR LicenseRef-FinalBoss-Commercial",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/805-ai/pqc-receipt-sdk"
|
|
39
|
+
}
|
|
40
|
+
}
|