@attestplane/attestplane 0.0.1 → 0.0.4-alpha
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/README.md +23 -9
- package/dist/adapter_conformance.d.ts +46 -0
- package/dist/adapter_conformance.d.ts.map +1 -0
- package/dist/adapter_conformance.js +160 -0
- package/dist/adapter_conformance.js.map +1 -0
- package/dist/adapters/langfuse.d.ts +51 -0
- package/dist/adapters/langfuse.d.ts.map +1 -0
- package/dist/adapters/langfuse.js +157 -0
- package/dist/adapters/langfuse.js.map +1 -0
- package/dist/adapters/langsmith.d.ts +53 -0
- package/dist/adapters/langsmith.d.ts.map +1 -0
- package/dist/adapters/langsmith.js +173 -0
- package/dist/adapters/langsmith.js.map +1 -0
- package/dist/adapters.d.ts +88 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +109 -0
- package/dist/adapters.js.map +1 -0
- package/dist/anchoring.d.ts +119 -0
- package/dist/anchoring.d.ts.map +1 -0
- package/dist/anchoring.js +340 -0
- package/dist/anchoring.js.map +1 -0
- package/dist/canonical.d.ts +11 -2
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +44 -31
- package/dist/canonical.js.map +1 -1
- package/dist/canonical_text.d.ts +30 -0
- package/dist/canonical_text.d.ts.map +1 -0
- package/dist/canonical_text.js +100 -0
- package/dist/canonical_text.js.map +1 -0
- package/dist/der.d.ts +55 -0
- package/dist/der.d.ts.map +1 -0
- package/dist/der.js +200 -0
- package/dist/der.js.map +1 -0
- package/dist/event_payloads.d.ts +118 -0
- package/dist/event_payloads.d.ts.map +1 -0
- package/dist/event_payloads.js +348 -0
- package/dist/event_payloads.js.map +1 -0
- package/dist/event_types.d.ts +47 -0
- package/dist/event_types.d.ts.map +1 -0
- package/dist/event_types.js +63 -0
- package/dist/event_types.js.map +1 -0
- package/dist/hashchain.d.ts +1 -0
- package/dist/hashchain.d.ts.map +1 -1
- package/dist/hashchain.js +25 -1
- package/dist/hashchain.js.map +1 -1
- package/dist/index.d.ts +23 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -2
- package/dist/index.js.map +1 -1
- package/dist/index_version.d.ts +9 -0
- package/dist/index_version.d.ts.map +1 -0
- package/dist/index_version.js +11 -0
- package/dist/index_version.js.map +1 -0
- package/dist/intoto.d.ts +48 -0
- package/dist/intoto.d.ts.map +1 -0
- package/dist/intoto.js +106 -0
- package/dist/intoto.js.map +1 -0
- package/dist/obligations.d.ts +41 -0
- package/dist/obligations.d.ts.map +1 -0
- package/dist/obligations.js +312 -0
- package/dist/obligations.js.map +1 -0
- package/dist/proof_bundle.d.ts +186 -0
- package/dist/proof_bundle.d.ts.map +1 -0
- package/dist/proof_bundle.js +299 -0
- package/dist/proof_bundle.js.map +1 -0
- package/dist/reason_codes.d.ts +38 -0
- package/dist/reason_codes.d.ts.map +1 -0
- package/dist/reason_codes.js +97 -0
- package/dist/reason_codes.js.map +1 -0
- package/dist/replay_verifier.d.ts +43 -0
- package/dist/replay_verifier.d.ts.map +1 -0
- package/dist/replay_verifier.js +98 -0
- package/dist/replay_verifier.js.map +1 -0
- package/dist/rfc3161.d.ts +52 -0
- package/dist/rfc3161.d.ts.map +1 -0
- package/dist/rfc3161.js +640 -0
- package/dist/rfc3161.js.map +1 -0
- package/dist/settlement_verifier.d.ts +34 -0
- package/dist/settlement_verifier.d.ts.map +1 -0
- package/dist/settlement_verifier.js +139 -0
- package/dist/settlement_verifier.js.map +1 -0
- package/dist/signing/base.d.ts +101 -0
- package/dist/signing/base.d.ts.map +1 -0
- package/dist/signing/base.js +144 -0
- package/dist/signing/base.js.map +1 -0
- package/dist/signing/providers.d.ts +113 -0
- package/dist/signing/providers.d.ts.map +1 -0
- package/dist/signing/providers.js +230 -0
- package/dist/signing/providers.js.map +1 -0
- package/dist/signing/signer.d.ts +66 -0
- package/dist/signing/signer.d.ts.map +1 -0
- package/dist/signing/signer.js +146 -0
- package/dist/signing/signer.js.map +1 -0
- package/dist/signing/trust_roots.d.ts +71 -0
- package/dist/signing/trust_roots.d.ts.map +1 -0
- package/dist/signing/trust_roots.js +267 -0
- package/dist/signing/trust_roots.js.map +1 -0
- package/dist/signing/verifier_ext.d.ts +77 -0
- package/dist/signing/verifier_ext.d.ts.map +1 -0
- package/dist/signing/verifier_ext.js +340 -0
- package/dist/signing/verifier_ext.js.map +1 -0
- package/dist/verifier.d.ts +39 -0
- package/dist/verifier.d.ts.map +1 -0
- package/dist/verifier.js +374 -0
- package/dist/verifier.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 The Attestplane Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Concrete `KeyProvider` implementations — TypeScript mirror of
|
|
5
|
+
* `sdk/python/src/attestplane/signing/providers.py` (architect plan § 1 D
|
|
6
|
+
* + T6 review decision 4).
|
|
7
|
+
*
|
|
8
|
+
* Four providers:
|
|
9
|
+
*
|
|
10
|
+
* - `InMemoryKeyProvider` — tests / dev; optional 32-byte seed for
|
|
11
|
+
* deterministic keys.
|
|
12
|
+
* - `FileKeyProvider` — PKCS#8 PEM file on disk.
|
|
13
|
+
* - `EnvKeyProvider` — PKCS#8 PEM bytes from `process.env`.
|
|
14
|
+
* - `MultiSignerProvider` — plurality (any-of-n) composite per architect
|
|
15
|
+
* plan § 1 H. NOT a `KeyProvider` subclass: returns an array.
|
|
16
|
+
*
|
|
17
|
+
* Ed25519 recipe (load-bearing R1 risk mitigation):
|
|
18
|
+
*
|
|
19
|
+
* - **Seed → KeyObject**: PKCS#8 DER wrap path (RFC 8410). Node 22's
|
|
20
|
+
* JWK importer requires the public `x` alongside `d`, which would
|
|
21
|
+
* force us to recompute the point first — the PKCS#8 path wraps the
|
|
22
|
+
* raw 32-byte seed with a constant 16-byte prefix, byte-equal to the
|
|
23
|
+
* blob `cryptography`'s `Ed25519PrivateKey.from_private_bytes(seed)`
|
|
24
|
+
* produces when serialised. This deviates from the architect's
|
|
25
|
+
* initial JWK recipe per the T6 implementation revision dated
|
|
26
|
+
* 2026-05-17 (see `docs/architecture/adr_0005_t6_review_20260517.md`).
|
|
27
|
+
* - **SPKI DER export**: via the intermediate
|
|
28
|
+
* `createPublicKey(privateKey).export({format:'der', type:'spki'})` so
|
|
29
|
+
* the public-key bytes are byte-equal to Python's
|
|
30
|
+
* `Ed25519PrivateKey.public_key().public_bytes(DER, SubjectPublicKeyInfo)`.
|
|
31
|
+
*/
|
|
32
|
+
import { createPrivateKey, createPublicKey, generateKeyPairSync, } from 'node:crypto';
|
|
33
|
+
import { readFileSync } from 'node:fs';
|
|
34
|
+
import { KeyProvider, KeyProviderError, SIGNATURE_SCHEMA_VERSION, deriveKeyId, } from './base.js';
|
|
35
|
+
const ED25519_SEED_LEN = 32;
|
|
36
|
+
/**
|
|
37
|
+
* RFC 8410 Ed25519 PKCS#8 wrapper — constant 16-byte prefix concatenated
|
|
38
|
+
* with the 32-byte seed produces a valid 48-byte PKCS#8 DER blob. Used
|
|
39
|
+
* because Node 22's JWK importer requires the public `x` field alongside
|
|
40
|
+
* `d`, which would force us to recompute the public point first — the
|
|
41
|
+
* PKCS#8 path sidesteps that entirely and is byte-equal to the recipe
|
|
42
|
+
* used by Python `cryptography`'s `Ed25519PrivateKey.from_private_bytes()`.
|
|
43
|
+
*
|
|
44
|
+
* Decode: `SEQUENCE { INTEGER 0, AlgorithmIdentifier {OID 1.3.101.112},
|
|
45
|
+
* OCTET STRING { OCTET STRING <32 byte seed> } }`.
|
|
46
|
+
*/
|
|
47
|
+
const PKCS8_ED25519_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
|
|
48
|
+
export function seedToPrivateKey(seed) {
|
|
49
|
+
if (seed.length !== ED25519_SEED_LEN) {
|
|
50
|
+
throw new KeyProviderError(`Ed25519 seed must be exactly ${ED25519_SEED_LEN} bytes, got ${seed.length}`);
|
|
51
|
+
}
|
|
52
|
+
const pkcs8 = Buffer.concat([PKCS8_ED25519_PREFIX, Buffer.from(seed)]);
|
|
53
|
+
return createPrivateKey({ key: pkcs8, format: 'der', type: 'pkcs8' });
|
|
54
|
+
}
|
|
55
|
+
export function exportPublicKeyDer(privateKey) {
|
|
56
|
+
const pub = createPublicKey(privateKey);
|
|
57
|
+
return new Uint8Array(pub.export({ format: 'der', type: 'spki' }));
|
|
58
|
+
}
|
|
59
|
+
function loadPemPrivateKey(pemBytes, passphrase, context) {
|
|
60
|
+
let key;
|
|
61
|
+
try {
|
|
62
|
+
key = createPrivateKey(passphrase !== undefined
|
|
63
|
+
? { key: pemBytes, format: 'pem', passphrase }
|
|
64
|
+
: { key: pemBytes, format: 'pem' });
|
|
65
|
+
}
|
|
66
|
+
catch (exc) {
|
|
67
|
+
throw new KeyProviderError(`${context}: failed to load PEM key: ${exc.message}`);
|
|
68
|
+
}
|
|
69
|
+
if (key.asymmetricKeyType !== 'ed25519') {
|
|
70
|
+
throw new KeyProviderError(`${context}: key is not Ed25519 (got asymmetricKeyType=${String(key.asymmetricKeyType)})`);
|
|
71
|
+
}
|
|
72
|
+
return key;
|
|
73
|
+
}
|
|
74
|
+
function materialFromPrivateKey(privateKey) {
|
|
75
|
+
const publicKeyDer = exportPublicKeyDer(privateKey);
|
|
76
|
+
return {
|
|
77
|
+
privateKey,
|
|
78
|
+
publicKeyDer,
|
|
79
|
+
signingCertChain: [],
|
|
80
|
+
keyId: deriveKeyId(publicKeyDer),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Holds an Ed25519 keypair in process memory.
|
|
85
|
+
*
|
|
86
|
+
* Suitable for tests, ephemeral substrates, and deployments where the
|
|
87
|
+
* key is generated at startup and never persisted.
|
|
88
|
+
*
|
|
89
|
+
* When `seed` is supplied, the same seed always produces the same
|
|
90
|
+
* Ed25519 key — required for conformance vectors. When `seed` is
|
|
91
|
+
* absent, a fresh random key is generated via Node `crypto`
|
|
92
|
+
* `generateKeyPairSync('ed25519')`.
|
|
93
|
+
*/
|
|
94
|
+
export class InMemoryKeyProvider extends KeyProvider {
|
|
95
|
+
provider_id;
|
|
96
|
+
schema_version = SIGNATURE_SCHEMA_VERSION;
|
|
97
|
+
_privateKey;
|
|
98
|
+
constructor(options = {}) {
|
|
99
|
+
super();
|
|
100
|
+
const providerId = options.provider_id ?? 'in-memory';
|
|
101
|
+
if (!providerId) {
|
|
102
|
+
throw new Error('InMemoryKeyProvider provider_id must be non-empty');
|
|
103
|
+
}
|
|
104
|
+
this.provider_id = providerId;
|
|
105
|
+
if (options.seed !== undefined) {
|
|
106
|
+
this._privateKey = seedToPrivateKey(options.seed);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const { privateKey } = generateKeyPairSync('ed25519');
|
|
110
|
+
this._privateKey = privateKey;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
getSigningMaterial() {
|
|
114
|
+
return materialFromPrivateKey(this._privateKey);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Loads an Ed25519 private key from a PKCS#8 PEM file on disk.
|
|
119
|
+
*
|
|
120
|
+
* The file is read on EVERY `getSigningMaterial()` call; intentional
|
|
121
|
+
* design so file rotation (deployer replaces the file out-of-band) is
|
|
122
|
+
* picked up on the next signing call.
|
|
123
|
+
*/
|
|
124
|
+
export class FileKeyProvider extends KeyProvider {
|
|
125
|
+
provider_id;
|
|
126
|
+
schema_version = SIGNATURE_SCHEMA_VERSION;
|
|
127
|
+
_path;
|
|
128
|
+
_passphrase;
|
|
129
|
+
constructor(path, options = {}) {
|
|
130
|
+
super();
|
|
131
|
+
if (!path) {
|
|
132
|
+
throw new Error('FileKeyProvider path must be non-empty');
|
|
133
|
+
}
|
|
134
|
+
this._path = path;
|
|
135
|
+
this._passphrase =
|
|
136
|
+
options.passphrase !== undefined ? Buffer.from(options.passphrase) : undefined;
|
|
137
|
+
this.provider_id = options.provider_id ?? `file:${path}`;
|
|
138
|
+
}
|
|
139
|
+
getSigningMaterial() {
|
|
140
|
+
let pemBytes;
|
|
141
|
+
try {
|
|
142
|
+
pemBytes = readFileSync(this._path);
|
|
143
|
+
}
|
|
144
|
+
catch (exc) {
|
|
145
|
+
const err = exc;
|
|
146
|
+
if (err.code === 'ENOENT') {
|
|
147
|
+
throw new KeyProviderError(`FileKeyProvider: key file not found at ${this._path}`);
|
|
148
|
+
}
|
|
149
|
+
throw new KeyProviderError(`FileKeyProvider: cannot read ${this._path}: ${err.message}`);
|
|
150
|
+
}
|
|
151
|
+
const key = loadPemPrivateKey(pemBytes, this._passphrase, `FileKeyProvider(${this._path})`);
|
|
152
|
+
return materialFromPrivateKey(key);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Loads an Ed25519 private key from `process.env[envVar]`.
|
|
157
|
+
*
|
|
158
|
+
* The env var must contain a PEM-encoded PKCS#8 key (typical when a
|
|
159
|
+
* secret manager injects the key at runtime).
|
|
160
|
+
*/
|
|
161
|
+
export class EnvKeyProvider extends KeyProvider {
|
|
162
|
+
provider_id;
|
|
163
|
+
schema_version = SIGNATURE_SCHEMA_VERSION;
|
|
164
|
+
_envVar;
|
|
165
|
+
_passphrase;
|
|
166
|
+
constructor(envVar, options = {}) {
|
|
167
|
+
super();
|
|
168
|
+
if (!envVar) {
|
|
169
|
+
throw new Error('EnvKeyProvider envVar must be non-empty');
|
|
170
|
+
}
|
|
171
|
+
this._envVar = envVar;
|
|
172
|
+
this._passphrase =
|
|
173
|
+
options.passphrase !== undefined ? Buffer.from(options.passphrase) : undefined;
|
|
174
|
+
this.provider_id = options.provider_id ?? `env:${envVar}`;
|
|
175
|
+
}
|
|
176
|
+
getSigningMaterial() {
|
|
177
|
+
const pemText = process.env[this._envVar];
|
|
178
|
+
if (pemText === undefined) {
|
|
179
|
+
throw new KeyProviderError(`EnvKeyProvider: env var ${JSON.stringify(this._envVar)} is not set`);
|
|
180
|
+
}
|
|
181
|
+
if (pemText.trim().length === 0) {
|
|
182
|
+
throw new KeyProviderError(`EnvKeyProvider: env var ${JSON.stringify(this._envVar)} is empty`);
|
|
183
|
+
}
|
|
184
|
+
const key = loadPemPrivateKey(pemText, this._passphrase, `EnvKeyProvider(${this._envVar})`);
|
|
185
|
+
return materialFromPrivateKey(key);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// ----- MultiSignerProvider -----
|
|
189
|
+
/**
|
|
190
|
+
* Plurality (any-of-n) composite per architect plan § 1 H.
|
|
191
|
+
*
|
|
192
|
+
* Holds N `KeyProvider` instances and exposes their signing materials
|
|
193
|
+
* as a list. The `Signer` iterates and produces one `SignatureRecord`
|
|
194
|
+
* per provider per signed event.
|
|
195
|
+
*
|
|
196
|
+
* Intentionally NOT a `KeyProvider` subclass: a `KeyProvider` returns
|
|
197
|
+
* one `SigningMaterial`; this composite returns a list.
|
|
198
|
+
*
|
|
199
|
+
* Verification semantics: any valid signature from any trust-rooted
|
|
200
|
+
* `key_id` counts as "segment signed". Plurality, NOT k-of-n threshold
|
|
201
|
+
* (architect plan § 1 H — threshold explicitly out of scope).
|
|
202
|
+
*/
|
|
203
|
+
export class MultiSignerProvider {
|
|
204
|
+
_providers;
|
|
205
|
+
constructor(providers) {
|
|
206
|
+
if (providers.length === 0) {
|
|
207
|
+
throw new Error('MultiSignerProvider requires at least one provider');
|
|
208
|
+
}
|
|
209
|
+
const seen = new Set();
|
|
210
|
+
for (const p of providers) {
|
|
211
|
+
if (seen.has(p.provider_id)) {
|
|
212
|
+
throw new Error('MultiSignerProvider providers must have distinct provider_id values');
|
|
213
|
+
}
|
|
214
|
+
seen.add(p.provider_id);
|
|
215
|
+
if (p.schema_version !== SIGNATURE_SCHEMA_VERSION) {
|
|
216
|
+
throw new Error(`provider ${JSON.stringify(p.provider_id)} has schema_version=` +
|
|
217
|
+
`${p.schema_version}; this composite handles only ` +
|
|
218
|
+
`SIGNATURE_SCHEMA_VERSION=${SIGNATURE_SCHEMA_VERSION}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
this._providers = [...providers];
|
|
222
|
+
}
|
|
223
|
+
get provider_ids() {
|
|
224
|
+
return this._providers.map((p) => p.provider_id);
|
|
225
|
+
}
|
|
226
|
+
getSigningMaterials() {
|
|
227
|
+
return this._providers.map((p) => p.getSigningMaterial());
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/signing/providers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signer — TypeScript mirror of
|
|
3
|
+
* `sdk/python/src/attestplane/signing/signer.py` per T6 review § 1
|
|
4
|
+
* decision 5: sync-only API. Background-worker mode is explicitly
|
|
5
|
+
* deferred (TS `anchoring.ts` already lacks the worker pattern; no
|
|
6
|
+
* need to break parity here).
|
|
7
|
+
*
|
|
8
|
+
* Two payload builders are exposed (matching Python):
|
|
9
|
+
*
|
|
10
|
+
* - `buildSegmentHeadPayload(chain_id, head)` — canonical-JSON over a
|
|
11
|
+
* fixed 5-key object `{chain_id, event_hash, schema_version, seq,
|
|
12
|
+
* signature_schema_version}`. Cross-language byte-stable.
|
|
13
|
+
* - `buildPerEventPayload(event)` — canonical AuditEvent bytes
|
|
14
|
+
* bytes. Byte-identical to what `hashEvent()` hashes; already locked
|
|
15
|
+
* by v0.0.1-alpha `vectors.json`.
|
|
16
|
+
*/
|
|
17
|
+
import type { AuditEvent, ChainHead, ChainedEvent } from '../types.js';
|
|
18
|
+
import { type KeyProvider, type SignatureRecord } from './base.js';
|
|
19
|
+
import { MultiSignerProvider } from './providers.js';
|
|
20
|
+
/**
|
|
21
|
+
* Construct the canonical-JSON bytes signed in segment-head mode.
|
|
22
|
+
*
|
|
23
|
+
* Locked recipe per architect review § 3:
|
|
24
|
+
*
|
|
25
|
+
* ```
|
|
26
|
+
* { "chain_id": "<str>",
|
|
27
|
+
* "event_hash": "<lowercase hex>",
|
|
28
|
+
* "schema_version": 1,
|
|
29
|
+
* "seq": <int>,
|
|
30
|
+
* "signature_schema_version": 1 }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Cross-language byte-stability gate (T6 conformance assertion #1).
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildSegmentHeadPayload(chainId: string, head: ChainHead): Uint8Array;
|
|
36
|
+
/**
|
|
37
|
+
* Per-event mode signs the canonical bytes of the AuditEvent. Identical
|
|
38
|
+
* to `hashEvent`'s canonicalization call → already cross-language byte
|
|
39
|
+
* stable via `vectors.json`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildPerEventPayload(event: AuditEvent): Uint8Array;
|
|
42
|
+
export interface SignerOptions {
|
|
43
|
+
readonly chain_id: string;
|
|
44
|
+
readonly key_provider: KeyProvider | MultiSignerProvider;
|
|
45
|
+
readonly now?: () => Date;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Event-signing producer (sync-only TS variant).
|
|
49
|
+
*
|
|
50
|
+
* Methods:
|
|
51
|
+
* - `signEvent(event)` — per-event mode; signs canonical AuditEvent bytes.
|
|
52
|
+
* - `signSegmentHead(head)` — segment-head mode; signs the locked
|
|
53
|
+
* 5-key payload.
|
|
54
|
+
*
|
|
55
|
+
* Each method returns `SignatureRecord[]` (one entry per signing
|
|
56
|
+
* material, supporting `MultiSignerProvider` plurality).
|
|
57
|
+
*/
|
|
58
|
+
export declare class Signer {
|
|
59
|
+
private readonly _chainId;
|
|
60
|
+
private readonly _keyProvider;
|
|
61
|
+
private readonly _now;
|
|
62
|
+
constructor(options: SignerOptions);
|
|
63
|
+
signEvent(event: ChainedEvent): SignatureRecord[];
|
|
64
|
+
signSegmentHead(head: ChainHead): SignatureRecord[];
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=signer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../../src/signing/signer.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,EACL,KAAK,WAAW,EAGhB,KAAK,eAAe,EAGrB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAUrD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,UAAU,CAWpF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAElE;AAqCD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,WAAW,GAAG,mBAAmB,CAAC;IACzD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoC;IACjE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;gBAEtB,OAAO,EAAE,aAAa;IASlC,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,eAAe,EAAE;IAkBjD,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,EAAE;CAuBpD"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 The Attestplane Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Signer — TypeScript mirror of
|
|
5
|
+
* `sdk/python/src/attestplane/signing/signer.py` per T6 review § 1
|
|
6
|
+
* decision 5: sync-only API. Background-worker mode is explicitly
|
|
7
|
+
* deferred (TS `anchoring.ts` already lacks the worker pattern; no
|
|
8
|
+
* need to break parity here).
|
|
9
|
+
*
|
|
10
|
+
* Two payload builders are exposed (matching Python):
|
|
11
|
+
*
|
|
12
|
+
* - `buildSegmentHeadPayload(chain_id, head)` — canonical-JSON over a
|
|
13
|
+
* fixed 5-key object `{chain_id, event_hash, schema_version, seq,
|
|
14
|
+
* signature_schema_version}`. Cross-language byte-stable.
|
|
15
|
+
* - `buildPerEventPayload(event)` — canonical AuditEvent bytes
|
|
16
|
+
* bytes. Byte-identical to what `hashEvent()` hashes; already locked
|
|
17
|
+
* by v0.0.1-alpha `vectors.json`.
|
|
18
|
+
*/
|
|
19
|
+
import { sign as ed25519Sign } from 'node:crypto';
|
|
20
|
+
import { canonicalize } from '../canonical.js';
|
|
21
|
+
import { SCHEMA_VERSION as CHAIN_SCHEMA_VERSION, canonicalizeAuditEvent } from '../hashchain.js';
|
|
22
|
+
import { SIGNATURE_SCHEMA_VERSION, SigningError, } from './base.js';
|
|
23
|
+
import { MultiSignerProvider } from './providers.js';
|
|
24
|
+
function bytesToHex(bytes) {
|
|
25
|
+
let out = '';
|
|
26
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
27
|
+
out += bytes[i].toString(16).padStart(2, '0');
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Construct the canonical-JSON bytes signed in segment-head mode.
|
|
33
|
+
*
|
|
34
|
+
* Locked recipe per architect review § 3:
|
|
35
|
+
*
|
|
36
|
+
* ```
|
|
37
|
+
* { "chain_id": "<str>",
|
|
38
|
+
* "event_hash": "<lowercase hex>",
|
|
39
|
+
* "schema_version": 1,
|
|
40
|
+
* "seq": <int>,
|
|
41
|
+
* "signature_schema_version": 1 }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* Cross-language byte-stability gate (T6 conformance assertion #1).
|
|
45
|
+
*/
|
|
46
|
+
export function buildSegmentHeadPayload(chainId, head) {
|
|
47
|
+
if (!chainId) {
|
|
48
|
+
throw new SigningError('segment-head signing requires non-empty chain_id');
|
|
49
|
+
}
|
|
50
|
+
return canonicalize({
|
|
51
|
+
chain_id: chainId,
|
|
52
|
+
event_hash: bytesToHex(head.event_hash),
|
|
53
|
+
schema_version: CHAIN_SCHEMA_VERSION,
|
|
54
|
+
seq: head.seq,
|
|
55
|
+
signature_schema_version: SIGNATURE_SCHEMA_VERSION,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Per-event mode signs the canonical bytes of the AuditEvent. Identical
|
|
60
|
+
* to `hashEvent`'s canonicalization call → already cross-language byte
|
|
61
|
+
* stable via `vectors.json`.
|
|
62
|
+
*/
|
|
63
|
+
export function buildPerEventPayload(event) {
|
|
64
|
+
return canonicalizeAuditEvent(event);
|
|
65
|
+
}
|
|
66
|
+
function resolveMaterials(provider) {
|
|
67
|
+
if (provider instanceof MultiSignerProvider) {
|
|
68
|
+
return provider.getSigningMaterials();
|
|
69
|
+
}
|
|
70
|
+
return [provider.getSigningMaterial()];
|
|
71
|
+
}
|
|
72
|
+
function makeSignatureRecord(mat, args) {
|
|
73
|
+
// Ed25519 in node:crypto: algorithm=null per Node docs.
|
|
74
|
+
const signature = new Uint8Array(ed25519Sign(null, Buffer.from(args.signed_payload), mat.privateKey));
|
|
75
|
+
return {
|
|
76
|
+
signature_schema_version: SIGNATURE_SCHEMA_VERSION,
|
|
77
|
+
signed_seq: args.signed_seq,
|
|
78
|
+
signed_event_hash: args.signed_event_hash,
|
|
79
|
+
signature,
|
|
80
|
+
key_id: mat.keyId,
|
|
81
|
+
public_key_der: mat.publicKeyDer,
|
|
82
|
+
signing_cert_chain: mat.signingCertChain,
|
|
83
|
+
signed_at: args.signed_at,
|
|
84
|
+
signature_mode: args.signature_mode,
|
|
85
|
+
signed_payload: args.signed_payload,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Event-signing producer (sync-only TS variant).
|
|
90
|
+
*
|
|
91
|
+
* Methods:
|
|
92
|
+
* - `signEvent(event)` — per-event mode; signs canonical AuditEvent bytes.
|
|
93
|
+
* - `signSegmentHead(head)` — segment-head mode; signs the locked
|
|
94
|
+
* 5-key payload.
|
|
95
|
+
*
|
|
96
|
+
* Each method returns `SignatureRecord[]` (one entry per signing
|
|
97
|
+
* material, supporting `MultiSignerProvider` plurality).
|
|
98
|
+
*/
|
|
99
|
+
export class Signer {
|
|
100
|
+
_chainId;
|
|
101
|
+
_keyProvider;
|
|
102
|
+
_now;
|
|
103
|
+
constructor(options) {
|
|
104
|
+
if (!options.chain_id) {
|
|
105
|
+
throw new Error('Signer chain_id must be non-empty');
|
|
106
|
+
}
|
|
107
|
+
this._chainId = options.chain_id;
|
|
108
|
+
this._keyProvider = options.key_provider;
|
|
109
|
+
this._now = options.now ?? (() => new Date());
|
|
110
|
+
}
|
|
111
|
+
signEvent(event) {
|
|
112
|
+
const materials = resolveMaterials(this._keyProvider);
|
|
113
|
+
const signedAt = this._now();
|
|
114
|
+
if (Number.isNaN(signedAt.getTime())) {
|
|
115
|
+
throw new SigningError('Signer.signEvent requires a valid Date for now()');
|
|
116
|
+
}
|
|
117
|
+
const payload = buildPerEventPayload(event.event);
|
|
118
|
+
return materials.map((mat) => makeSignatureRecord(mat, {
|
|
119
|
+
signed_seq: event.seq,
|
|
120
|
+
signed_event_hash: event.event_hash,
|
|
121
|
+
signed_payload: payload,
|
|
122
|
+
signature_mode: 'per_event',
|
|
123
|
+
signed_at: signedAt,
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
signSegmentHead(head) {
|
|
127
|
+
if (head.seq < 0) {
|
|
128
|
+
throw new SigningError('Signer.signSegmentHead requires a real chain head (seq >= 0); ' +
|
|
129
|
+
'refusing to sign genesis sentinel');
|
|
130
|
+
}
|
|
131
|
+
const materials = resolveMaterials(this._keyProvider);
|
|
132
|
+
const signedAt = this._now();
|
|
133
|
+
if (Number.isNaN(signedAt.getTime())) {
|
|
134
|
+
throw new SigningError('Signer.signSegmentHead requires a valid Date for now()');
|
|
135
|
+
}
|
|
136
|
+
const payload = buildSegmentHeadPayload(this._chainId, head);
|
|
137
|
+
return materials.map((mat) => makeSignatureRecord(mat, {
|
|
138
|
+
signed_seq: head.seq,
|
|
139
|
+
signed_event_hash: head.event_hash,
|
|
140
|
+
signed_payload: payload,
|
|
141
|
+
signature_mode: 'segment_head',
|
|
142
|
+
signed_at: signedAt,
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=signer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.js","sourceRoot":"","sources":["../../src/signing/signer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TrustRoots loader — TypeScript mirror of
|
|
3
|
+
* `sdk/python/src/attestplane/signing/trust_roots.py`.
|
|
4
|
+
*
|
|
5
|
+
* Per T6 review § 1 decision 3: **JSON only** in TS. The Python loader
|
|
6
|
+
* accepts YAML for operator convenience; the TS SDK consumes JSON. Same
|
|
7
|
+
* schema fields exactly. Operators convert YAML→JSON via `yq` if
|
|
8
|
+
* needed. This avoids adding a ~50 KB yaml dep to the TS package whose
|
|
9
|
+
* only runtime dep today is `uuid`.
|
|
10
|
+
*
|
|
11
|
+
* Schema:
|
|
12
|
+
*
|
|
13
|
+
* ```json
|
|
14
|
+
* {
|
|
15
|
+
* "version": 1,
|
|
16
|
+
* "keys": [
|
|
17
|
+
* {
|
|
18
|
+
* "key_id": "<32 lowercase hex chars>",
|
|
19
|
+
* "public_key_der_b64": "<base64 SPKI>",
|
|
20
|
+
* "valid_from": "2026-05-17T00:00:00Z",
|
|
21
|
+
* "valid_until": "2027-05-17T00:00:00Z",
|
|
22
|
+
* "provider_id": "<optional>",
|
|
23
|
+
* "label": "<optional>"
|
|
24
|
+
* }
|
|
25
|
+
* ]
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Strict invariants (mirror Python):
|
|
30
|
+
* - `version` must equal 1.
|
|
31
|
+
* - `keys` must be a non-empty array.
|
|
32
|
+
* - Each entry's `key_id` must equal `deriveKeyId(b64decode(public_key_der_b64))`.
|
|
33
|
+
* - `valid_from < valid_until`, both UTC-aware ISO-8601.
|
|
34
|
+
* - Top-level + per-entry `additionalProperties` rejected.
|
|
35
|
+
* - File size > 1 MB rejected (DoS mitigation).
|
|
36
|
+
*/
|
|
37
|
+
import { SigningError } from './base.js';
|
|
38
|
+
export declare class TrustRootsError extends SigningError {
|
|
39
|
+
constructor(message: string);
|
|
40
|
+
}
|
|
41
|
+
export interface TrustRootEntry {
|
|
42
|
+
readonly key_id: string;
|
|
43
|
+
readonly public_key_der: Uint8Array;
|
|
44
|
+
readonly valid_from: Date;
|
|
45
|
+
readonly valid_until: Date;
|
|
46
|
+
readonly provider_id: string | null;
|
|
47
|
+
readonly label: string | null;
|
|
48
|
+
}
|
|
49
|
+
export declare class TrustRoots {
|
|
50
|
+
readonly version: number;
|
|
51
|
+
readonly entries: readonly TrustRootEntry[];
|
|
52
|
+
constructor(version: number, entries: readonly TrustRootEntry[]);
|
|
53
|
+
lookup(keyId: string): TrustRootEntry | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Load + validate a TrustRoots JSON file.
|
|
57
|
+
*
|
|
58
|
+
* - File size cap 1 MB.
|
|
59
|
+
* - Strict schema (extra fields rejected, missing required fields rejected).
|
|
60
|
+
* - Every entry's `key_id` cross-checked against `deriveKeyId()`.
|
|
61
|
+
* - Duplicate `key_id` rejected.
|
|
62
|
+
*/
|
|
63
|
+
export declare function loadTrustRoots(path: string): TrustRoots;
|
|
64
|
+
/**
|
|
65
|
+
* Validate an already-parsed JSON value as a `TrustRoots`. Used by
|
|
66
|
+
* `loadTrustRoots` and exposed so callers who fetched the JSON from
|
|
67
|
+
* a non-file source (HTTP, secret manager) can validate without an
|
|
68
|
+
* intermediate disk write.
|
|
69
|
+
*/
|
|
70
|
+
export declare function parseTrustRoots(raw: unknown, source?: string): TrustRoots;
|
|
71
|
+
//# sourceMappingURL=trust_roots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust_roots.d.ts","sourceRoot":"","sources":["../../src/signing/trust_roots.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAIH,OAAO,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AAQtD,qBAAa,eAAgB,SAAQ,YAAY;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,qBAAa,UAAU;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;gBAEhC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,cAAc,EAAE;IAK/D,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;CAM7C;AA0ID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CA8BvD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,SAAa,GAAG,UAAU,CAsD7E"}
|