@charter-agreement-protocol/signer 0.1.1 → 0.2.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.
- package/CHANGELOG.md +44 -0
- package/NOTICE +4 -4
- package/README.md +74 -16
- package/dist/index.js +4 -3
- package/dist/signer.d.ts +6 -3
- package/dist/signer.js +105 -101
- package/package.json +11 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable public changes to `@charter-agreement-protocol/signer` are
|
|
4
|
+
documented here.
|
|
5
|
+
|
|
6
|
+
## [0.2.1] — 2026-09-24
|
|
7
|
+
|
|
8
|
+
Dependency-line release: this package now tracks the published verify
|
|
9
|
+
line - `@charter-agreement-protocol/verifier` `^0.5.0` (resolved 0.5.0).
|
|
10
|
+
No API change: verifier 0.5.0 added consumer-side surface only (the
|
|
11
|
+
capability probe, the signature registry identity, the descriptor
|
|
12
|
+
timestamp floor, the 104-case corpus); the producer surface this package
|
|
13
|
+
delegates to is unchanged. Typecheck strict, 18/18 tests against the
|
|
14
|
+
resolved 0.5.0. The producer-side capability alignment (mirrored
|
|
15
|
+
`capabilities()` with refuse-before-mint, declared minting metadata,
|
|
16
|
+
profile-bound signing) is the next feature release on this line.
|
|
17
|
+
|
|
18
|
+
## [0.2.0] — 2026-09-17
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **The R1–R3 honest-signer refusals now run pre-sign** (previously
|
|
23
|
+
deferred to post-sign verification with mislabeled errors; the `refused`
|
|
24
|
+
error was unreachable): `signAcceptance` / `signTermination` run the
|
|
25
|
+
verifier package's refusal checks after framing, before the key signs.
|
|
26
|
+
Breaking within 0.x: `signTermination`'s view requires `chain`,
|
|
27
|
+
`signAcceptance` enforces the guards over the `chain` it previously
|
|
28
|
+
ignored, and `signReceipt` requires its issuing view (the reference
|
|
29
|
+
takes a context, never none).
|
|
30
|
+
- Post-sign verification failures return the bare `verification_failed`
|
|
31
|
+
(the reference vocabulary); malformed claims are rejected pre-sign as
|
|
32
|
+
`invalid_input` through the producer claims gate — no claim-shape defect
|
|
33
|
+
burns a key operation.
|
|
34
|
+
- **All protocol logic is delegated to the verifier package's producer
|
|
35
|
+
surface (`^0.4.0`)**: framing, the claims gate, the refusal guards, and
|
|
36
|
+
assembly; this package keeps custody only (atomic snapshot, sign
|
|
37
|
+
callback, wrong-key guard, post-sign verification, closed errors).
|
|
38
|
+
|
|
39
|
+
## [0.1.x] — 2026-09-14
|
|
40
|
+
|
|
41
|
+
- Initial public releases: one signing function per artifact kind, the
|
|
42
|
+
key-handle contract with the atomic `keyIdentity` snapshot, the
|
|
43
|
+
wrong-key guard, and post-sign verification through
|
|
44
|
+
`@charter-agreement-protocol/verifier`.
|
package/NOTICE
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Charter Agreement Signer
|
|
1
|
+
Charter Agreement Signer (TypeScript)
|
|
2
2
|
Copyright 2026 BaseLabs
|
|
3
3
|
|
|
4
4
|
This product includes software developed by BaseLabs
|
|
5
5
|
(https://github.com/baselabs).
|
|
6
6
|
|
|
7
|
-
This product depends on the
|
|
8
|
-
(https://github.com/baselabs/
|
|
9
|
-
the Apache License, Version 2.0.
|
|
7
|
+
This product depends on the @charter-agreement-protocol/verifier package
|
|
8
|
+
(https://github.com/baselabs/charter_agreement_protocol_typescript), also
|
|
9
|
+
licensed under the Apache License, Version 2.0.
|
package/README.md
CHANGED
|
@@ -8,12 +8,14 @@ Holder-side companion signer for the
|
|
|
8
8
|
**CAP verifies; it never authorizes. This signer signs; it never takes
|
|
9
9
|
custody.** You keep the key (HSM, OS keychain, key server) behind a handle
|
|
10
10
|
object with two callbacks; the signer resolves ONE atomic `keyIdentity`
|
|
11
|
-
snapshot,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
snapshot, then delegates EVERYTHING pure to the verifier package's producer
|
|
12
|
+
surface — the exact RFC 7515 signing input, the producer claims gate, the
|
|
13
|
+
R1–R3 honest-signer refusal guards (claims-truth, no-equivocation,
|
|
14
|
+
ancestry/governing coverage), and assembly — **before the key signs**, with
|
|
15
|
+
exactly one implementation. What stays here is custody: the sign callback,
|
|
16
|
+
the wrong-key guard against the snapshot's public key, and the post-sign
|
|
17
|
+
verification of the assembled artifact through the
|
|
18
|
+
`@charter-agreement-protocol/verifier` package.
|
|
17
19
|
|
|
18
20
|
## Install
|
|
19
21
|
|
|
@@ -27,11 +29,15 @@ Requires Node >= 24.8 (ML-DSA support in the Node builtins).
|
|
|
27
29
|
|
|
28
30
|
```js
|
|
29
31
|
import { signDescriptor } from "@charter-agreement-protocol/signer";
|
|
32
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
33
|
+
|
|
34
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
35
|
+
const publicKeyBase64url = publicKey.export({ type: "spki", format: "der" }).subarray(-32).toString("base64url");
|
|
30
36
|
|
|
31
37
|
const handle = {
|
|
32
38
|
// ONE atomic snapshot — the kid and public key cannot disagree.
|
|
33
39
|
keyIdentity() {
|
|
34
|
-
return { kid: "my-key-001", publicKey }; // base64url raw public key
|
|
40
|
+
return { kid: "my-key-001", publicKey: publicKeyBase64url }; // base64url raw public key
|
|
35
41
|
},
|
|
36
42
|
// The holder's job: sign the exact message bytes with the held key.
|
|
37
43
|
sign(message) {
|
|
@@ -44,7 +50,7 @@ const result = await signDescriptor(
|
|
|
44
50
|
protocol_revision: 2,
|
|
45
51
|
descriptor_number: 1,
|
|
46
52
|
verification_keys: [
|
|
47
|
-
{ key_id: "my-key-001", algorithm: "Ed25519", public_key:
|
|
53
|
+
{ key_id: "my-key-001", algorithm: "Ed25519", public_key: publicKeyBase64url, status: "active" },
|
|
48
54
|
],
|
|
49
55
|
attestation_hints: [],
|
|
50
56
|
extensions: { critical: {}, optional: {} },
|
|
@@ -65,27 +71,79 @@ empty context — your handle signs the exact message bytes as with Ed25519).
|
|
|
65
71
|
| Export | What it does |
|
|
66
72
|
|---|---|
|
|
67
73
|
| `signDescriptor(claims, handle, opts?)` | Signs one Party Descriptor; post-verified via `verifyDescriptor` |
|
|
68
|
-
| `signReceipt(claims, handle, chain
|
|
69
|
-
| `signAcceptance(claims, handle, view, opts?)` | Signs one Acceptance against the caller's verified view |
|
|
70
|
-
| `signTermination(claims, handle, view, opts?)` | Signs one Termination against the caller's verified view |
|
|
74
|
+
| `signReceipt(claims, handle, chain, opts?)` | Signs one Receipt against the required issuing view; post-verified via `verifyReceipt` (the reference takes a context, never none) |
|
|
75
|
+
| `signAcceptance(claims, handle, view, opts?)` | Signs one Acceptance against the caller's verified view — the R1–R3 refusal guards run over `view.chain` before the key signs |
|
|
76
|
+
| `signTermination(claims, handle, view, opts?)` | Signs one Termination against the caller's verified view — the R1–R3 refusal guards run over `view.chain` before the key signs |
|
|
77
|
+
|
|
78
|
+
The acceptance/termination `view` is `{ revisionText, descriptorCompacts,
|
|
79
|
+
chain }`: the revision the claims name, the signing party's descriptor
|
|
80
|
+
chain, and the caller's full `ChainView` (`{ revisions, acceptances,
|
|
81
|
+
descriptors, terminations }`) the refusal guards verify and bind against.
|
|
71
82
|
|
|
72
83
|
Errors are closed: `{ ok: false, error: "invalid_handle" \| "signing_failed"
|
|
73
|
-
\| "invalid_input" \| "refused"
|
|
74
|
-
|
|
84
|
+
\| "invalid_input" \| "refused" \| "verification_failed" }` — `code` rides
|
|
85
|
+
only on `invalid_input` and `refused`; `verification_failed` is bare
|
|
86
|
+
(reference parity).
|
|
87
|
+
`"refused"` is the honest-signer refusal: the claims contradict the caller's
|
|
88
|
+
own verified view, and the handle's `sign` callback is never reached (the
|
|
89
|
+
atomic `keyIdentity` snapshot resolves first — the reference ordering).
|
|
90
|
+
`"verification_failed"` is the separate post-sign discipline: the assembled
|
|
91
|
+
artifact did not verify against the caller's view (for example a key that is
|
|
92
|
+
not in the party descriptor's `verification_keys`). Malformed claims are
|
|
93
|
+
rejected pre-sign as `"invalid_input"` through the verifier package's
|
|
94
|
+
producer claims gate — they never burn a key operation. A signing failure is
|
|
95
|
+
never a silent pass.
|
|
96
|
+
|
|
97
|
+
## The sibling verifier
|
|
98
|
+
|
|
99
|
+
This package is one of two independent TypeScript siblings implementing the
|
|
100
|
+
Charter Agreement Protocol — no Elixir code or dependency at runtime; the
|
|
101
|
+
Elixir reference is the certification oracle (the verifier's vendored
|
|
102
|
+
corpus is certified against it, and its CI cross-verifies
|
|
103
|
+
TypeScript-signed artifacts from raw bytes).
|
|
104
|
+
|
|
105
|
+
- **This package** — `@charter-agreement-protocol/signer`: key custody,
|
|
106
|
+
the wrong-key guard, post-sign verification, and the closed error
|
|
107
|
+
vocabulary — all protocol logic delegated to the verifier.
|
|
108
|
+
- [`@charter-agreement-protocol/verifier`](https://www.npmjs.com/package/@charter-agreement-protocol/verifier)
|
|
109
|
+
— verification and the pure producer surface (signing inputs, claims
|
|
110
|
+
gate, refusal guards, assembly) that this package signs through.
|
|
111
|
+
|
|
112
|
+
The pair releases together: this package depends on the verifier (`^0.4.0`),
|
|
113
|
+
so a verifier release stages first and the signer locks and releases
|
|
114
|
+
against the published version immediately after.
|
|
75
115
|
|
|
76
116
|
## Evidence
|
|
77
117
|
|
|
78
118
|
- The Elixir reference repository's gate verifies TypeScript-signed
|
|
79
119
|
artifacts from raw bytes (cross-implementation agreement, enforced in CI).
|
|
120
|
+
- Framing, the producer claims gate, refusal guards, and assembly are the
|
|
121
|
+
verifier package's producer surface (0.4.0) — the same single
|
|
122
|
+
implementation the independent verifier certifies. What remains here is
|
|
123
|
+
custody plus input policing: the issuing-view discipline composes the
|
|
124
|
+
verifier's own `verifyChain`, never a local reimplementation.
|
|
80
125
|
- Post-sign verification goes through the
|
|
81
126
|
[@charter-agreement-protocol/verifier](https://www.npmjs.com/package/@charter-agreement-protocol/verifier)
|
|
82
127
|
package — the certified dual-implementation-verified verifier.
|
|
83
128
|
|
|
129
|
+
## The verify side
|
|
130
|
+
|
|
131
|
+
This package tracks the published verify line: it depends on
|
|
132
|
+
`@charter-agreement-protocol/verifier` `^0.5.0`. Verifier 0.5.0 added
|
|
133
|
+
consumer-side surface only; the producer surface this package uses is
|
|
134
|
+
unchanged. The producer-side capability alignment (mirrored
|
|
135
|
+
`capabilities()` with refuse-before-mint, declared minting metadata,
|
|
136
|
+
profile-bound signing) is the next feature release on this line.
|
|
137
|
+
|
|
84
138
|
## SemVer
|
|
85
139
|
|
|
86
|
-
Package SemVer decoupled from the protocol's `protocol_revision
|
|
87
|
-
|
|
88
|
-
|
|
140
|
+
Package SemVer decoupled from the protocol's `protocol_revision`. While the
|
|
141
|
+
package is below 1.0, breaking public-API changes land in minor versions and
|
|
142
|
+
are named in the commit message (0.2.0 is one: `signTermination`'s view
|
|
143
|
+
requires `chain`, `signAcceptance` now enforces the refusal guards over the
|
|
144
|
+
`chain` it previously ignored, and `signReceipt` requires its issuing view);
|
|
145
|
+
at or above 1.0 a package major is owed when a shipped public API is removed
|
|
146
|
+
or changes behavior.
|
|
89
147
|
|
|
90
148
|
## License
|
|
91
149
|
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Public surface of the holder-side companion signer: one signing function
|
|
4
4
|
// per artifact kind, the key-handle contract, and the closed error
|
|
5
|
-
// vocabulary.
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// vocabulary. ALL protocol logic — framing, the producer claims gate, the
|
|
6
|
+
// R1-R3 refusal guards, assembly, and verification of the assembled
|
|
7
|
+
// artifact — goes through @charter-agreement-protocol/verifier: exactly
|
|
8
|
+
// one implementation. This package holds the custody half only.
|
|
8
9
|
export { signDescriptor, signReceipt, signAcceptance, signTermination, } from "./signer.js";
|
package/dist/signer.d.ts
CHANGED
|
@@ -16,14 +16,16 @@ export type SignError = {
|
|
|
16
16
|
} | {
|
|
17
17
|
error: "refused";
|
|
18
18
|
code: string;
|
|
19
|
+
} | {
|
|
20
|
+
error: "verification_failed";
|
|
19
21
|
};
|
|
20
22
|
export type SignOk<T> = {
|
|
21
23
|
ok: true;
|
|
22
24
|
result: T;
|
|
23
25
|
};
|
|
24
|
-
export type SignResult<T> = SignOk<T> | {
|
|
26
|
+
export type SignResult<T> = SignOk<T> | ({
|
|
25
27
|
ok: false;
|
|
26
|
-
} & SignError;
|
|
28
|
+
} & SignError);
|
|
27
29
|
export type ChainView = {
|
|
28
30
|
revisions: string[];
|
|
29
31
|
acceptances: string[];
|
|
@@ -35,7 +37,7 @@ export declare function signDescriptor(claims: Record<string, unknown>, keyHandl
|
|
|
35
37
|
}): Promise<SignResult<{
|
|
36
38
|
descriptor: string;
|
|
37
39
|
}>>;
|
|
38
|
-
export declare function signReceipt(claims: Record<string, unknown>, keyHandle: unknown, chain: ChainView
|
|
40
|
+
export declare function signReceipt(claims: Record<string, unknown>, keyHandle: unknown, chain: ChainView, opts?: {
|
|
39
41
|
algorithm?: string;
|
|
40
42
|
}): Promise<SignResult<{
|
|
41
43
|
receipt: string;
|
|
@@ -52,6 +54,7 @@ export declare function signAcceptance(claims: Record<string, unknown>, keyHandl
|
|
|
52
54
|
export declare function signTermination(claims: Record<string, unknown>, keyHandle: unknown, view: {
|
|
53
55
|
revisionText: string;
|
|
54
56
|
descriptorCompacts: string[];
|
|
57
|
+
chain: ChainView;
|
|
55
58
|
}, opts?: {
|
|
56
59
|
algorithm?: string;
|
|
57
60
|
}): Promise<SignResult<{
|
package/dist/signer.js
CHANGED
|
@@ -7,21 +7,15 @@
|
|
|
7
7
|
// keyIdentity(handle) -> { kid, publicKey } // ONE atomic snapshot
|
|
8
8
|
// sign(message, handle) -> signature bytes // the holder's job
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// persists, and never sees
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
descriptor: "cap+party",
|
|
20
|
-
acceptance: "cap+acceptance",
|
|
21
|
-
termination: "cap+termination",
|
|
22
|
-
receipt: "cap+receipt",
|
|
23
|
-
};
|
|
24
|
-
const MAX_INPUT_BYTES = 1_048_576;
|
|
10
|
+
// Framing, the producer claims gate, the honest-signer refusal guards
|
|
11
|
+
// (R1–R3), and assembly are ALL the verifier package's producer surface —
|
|
12
|
+
// exactly one implementation, exactly the reference signer's delegation.
|
|
13
|
+
// This package adds the custody half: the atomic key snapshot, the sign
|
|
14
|
+
// callback, the wrong-key guard against the snapshot's public key, and the
|
|
15
|
+
// post-sign verification of the assembled artifact. It never verifies
|
|
16
|
+
// third-party artifacts, never transports, never persists, and never sees
|
|
17
|
+
// a private key.
|
|
18
|
+
import { acceptanceSigningInput, algorithmRegistry, assembleCompact, descriptorSigningInput, receiptSigningInput, terminationSigningInput, verifyAcceptance, verifyChain, verifyDescriptor, verifyReceipt, verifySignature, verifyTermination, } from "@charter-agreement-protocol/verifier";
|
|
25
19
|
function registryRow(algorithm) {
|
|
26
20
|
return algorithmRegistry().find((row) => row.name === algorithm) ?? null;
|
|
27
21
|
}
|
|
@@ -44,66 +38,27 @@ async function resolveKeyIdentity(handle) {
|
|
|
44
38
|
return { error: "invalid_handle" };
|
|
45
39
|
}
|
|
46
40
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return { error: "invalid_input", code: "algorithm_unsupported" };
|
|
53
|
-
}
|
|
54
|
-
function claimsRevision(claims) {
|
|
55
|
-
const value = claims.protocol_revision;
|
|
56
|
-
return typeof value === "number" && Number.isInteger(value) ? value : null;
|
|
57
|
-
}
|
|
58
|
-
// The producer core: canonical claims, the closed protected header with the
|
|
59
|
-
// selected emission name and the SNAPSHOT kid, the size guard, and the
|
|
60
|
-
// provisional framing check over a zero signature — CAP's producer contract.
|
|
61
|
-
function frameSigningInput(kind, claims, kid, algorithm) {
|
|
62
|
-
if (!claims || typeof claims !== "object" || Array.isArray(claims)) {
|
|
63
|
-
return { error: "invalid_input", code: "invalid_type" };
|
|
64
|
-
}
|
|
65
|
-
if (claimsRevision(claims) !== emissions()[algorithm]) {
|
|
66
|
-
return { error: "invalid_input", code: "signing_input_invalid" };
|
|
67
|
-
}
|
|
68
|
-
if (typeof kid !== "string" || kid.length === 0 || kid.length > 128 || !/^[A-Za-z0-9._~-]+$/.test(kid)) {
|
|
69
|
-
return { error: "invalid_input", code: "signing_input_invalid" };
|
|
70
|
-
}
|
|
71
|
-
const row = registryRow(algorithm);
|
|
72
|
-
if (!row)
|
|
73
|
-
return { error: "invalid_input", code: "algorithm_unsupported" };
|
|
74
|
-
const payloadBytes = Buffer.from(canonical(claims), "utf8");
|
|
75
|
-
const protectedBytes = Buffer.from(canonical({ alg: algorithm, kid, typ: TYPES[kind] }), "utf8");
|
|
76
|
-
const protectedSegment = encodeBase64url(protectedBytes);
|
|
77
|
-
const payloadSegment = encodeBase64url(payloadBytes);
|
|
78
|
-
const message = Buffer.from(`${protectedSegment}.${payloadSegment}`, "utf8");
|
|
79
|
-
const signatureSegmentLength = Math.ceil((row.signatureBytes * 4) / 3);
|
|
80
|
-
if (message.length + 1 + signatureSegmentLength > MAX_INPUT_BYTES) {
|
|
81
|
-
return { error: "invalid_input", code: "signing_input_invalid" };
|
|
82
|
-
}
|
|
83
|
-
// The provisional check: a zero signature of the row's exact length must
|
|
84
|
-
// frame and bind (canonical bytes, registry binding, per-row length).
|
|
85
|
-
const zeroSignature = Buffer.alloc(row.signatureBytes);
|
|
86
|
-
const provisional = `${protectedSegment}.${payloadSegment}.${encodeBase64url(zeroSignature)}`;
|
|
87
|
-
const decoded = decodeArtifact(provisional);
|
|
88
|
-
if (!decoded.ok)
|
|
89
|
-
return { error: "invalid_input", code: "signing_input_invalid" };
|
|
90
|
-
return { protectedSegment, payloadSegment, message };
|
|
91
|
-
}
|
|
92
|
-
async function signCommon(kind, claims, keyHandle, opts) {
|
|
41
|
+
// The shared custody tail (the reference signer's): identity snapshot ->
|
|
42
|
+
// producer (framing, claims schema, R1-R3 refusals - one implementation,
|
|
43
|
+
// in the verifier package) -> sign via the handle -> wrong-key guard.
|
|
44
|
+
// A refusal or producer rejection never touches the key.
|
|
45
|
+
async function signProduced(keyHandle, produce, opts) {
|
|
93
46
|
const snapshot = await resolveKeyIdentity(keyHandle);
|
|
94
47
|
if ("error" in snapshot)
|
|
95
48
|
return snapshot;
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
49
|
+
const produced = produce(snapshot.kid, opts?.algorithm);
|
|
50
|
+
if (!produced.ok) {
|
|
51
|
+
// signing_refused is the honest-signer refusal; every other producer
|
|
52
|
+
// code stays caller input.
|
|
53
|
+
return produced.code === "signing_refused"
|
|
54
|
+
? { error: "refused", code: produced.code }
|
|
55
|
+
: { error: "invalid_input", code: produced.code };
|
|
56
|
+
}
|
|
102
57
|
// The holder signs the exact message; a fault or a wrong-length result is
|
|
103
58
|
// a signing failure, never a silent pass.
|
|
104
59
|
let signature;
|
|
105
60
|
try {
|
|
106
|
-
signature = await keyHandle.sign(
|
|
61
|
+
signature = await keyHandle.sign(produced.input.message, keyHandle);
|
|
107
62
|
}
|
|
108
63
|
catch (_fault) {
|
|
109
64
|
return { error: "signing_failed" };
|
|
@@ -111,66 +66,115 @@ async function signCommon(kind, claims, keyHandle, opts) {
|
|
|
111
66
|
if (!(signature instanceof Uint8Array) || signature.length === 0) {
|
|
112
67
|
return { error: "signing_failed" };
|
|
113
68
|
}
|
|
114
|
-
const row = registryRow(
|
|
69
|
+
const row = registryRow(produced.input.alg);
|
|
115
70
|
if (!row || signature.length !== row.signatureBytes) {
|
|
116
71
|
return { error: "signing_failed" };
|
|
117
72
|
}
|
|
118
73
|
// The wrong-key guard: the signature MUST verify against the snapshot's
|
|
119
|
-
// public key under the
|
|
120
|
-
if (!verifySignature(
|
|
74
|
+
// public key under the minted algorithm.
|
|
75
|
+
if (!verifySignature(produced.input.message, Buffer.from(signature), snapshot.publicKey, produced.input.alg)) {
|
|
121
76
|
return { error: "signing_failed" };
|
|
122
77
|
}
|
|
123
|
-
return {
|
|
78
|
+
return { input: produced.input, signature: Buffer.from(signature) };
|
|
124
79
|
}
|
|
125
|
-
|
|
126
|
-
|
|
80
|
+
// Assembly goes through the verifier's assembleCompact: registry-row
|
|
81
|
+
// length, framing re-decode, size gate - never a local string join.
|
|
82
|
+
function compactOrInvalid(signed) {
|
|
83
|
+
const assembled = assembleCompact(signed.input, signed.signature);
|
|
84
|
+
if (!assembled.ok)
|
|
85
|
+
return { error: "invalid_input", code: assembled.code };
|
|
86
|
+
return { compact: assembled.compact };
|
|
87
|
+
}
|
|
88
|
+
// The view's chain is THIS package's public contract (the producer surface
|
|
89
|
+
// validates its depth); the shallow shape is policed here so the closed
|
|
90
|
+
// error vocabulary holds regardless of which verifier revision resolves.
|
|
91
|
+
// A verifier crash past this gate stays loud - never a misleading error.
|
|
92
|
+
function chainViewOrInvalid(chain) {
|
|
93
|
+
if (!chain || typeof chain !== "object" || Array.isArray(chain)) {
|
|
94
|
+
return { error: "invalid_input", code: "signing_input_invalid" };
|
|
95
|
+
}
|
|
96
|
+
return chain;
|
|
97
|
+
}
|
|
98
|
+
// A missing view object is caller input like a missing chain: the closed
|
|
99
|
+
// error, never a dereference throw.
|
|
100
|
+
function viewChainOrInvalid(view) {
|
|
101
|
+
if (!view || typeof view !== "object")
|
|
102
|
+
return { error: "invalid_input", code: "signing_input_invalid" };
|
|
103
|
+
return chainViewOrInvalid(view.chain);
|
|
127
104
|
}
|
|
128
|
-
// ---------------------------------------------------------------------------
|
|
129
|
-
// Honest-signer refusal guards (R1–R3 from the Elixir reference): they run
|
|
130
|
-
// against the caller's own verified view BEFORE any key is touched.
|
|
131
|
-
// ---------------------------------------------------------------------------
|
|
132
105
|
// ---------------------------------------------------------------------------
|
|
133
106
|
// Public signing surface
|
|
134
107
|
// ---------------------------------------------------------------------------
|
|
135
108
|
export async function signDescriptor(claims, keyHandle, opts = {}) {
|
|
136
|
-
const signed = await
|
|
109
|
+
const signed = await signProduced(keyHandle, (kid, algorithm) => descriptorSigningInput(kid, claims, algorithm), opts);
|
|
137
110
|
if ("error" in signed)
|
|
138
111
|
return { ok: false, ...signed };
|
|
139
|
-
const
|
|
140
|
-
|
|
112
|
+
const assembled = compactOrInvalid(signed);
|
|
113
|
+
if ("error" in assembled)
|
|
114
|
+
return { ok: false, ...assembled };
|
|
115
|
+
const verified = verifyDescriptor(assembled.compact);
|
|
141
116
|
if (!verified.ok)
|
|
142
|
-
return { ok: false, error: "
|
|
143
|
-
return { ok: true, result: { descriptor: compact } };
|
|
117
|
+
return { ok: false, error: "verification_failed" };
|
|
118
|
+
return { ok: true, result: { descriptor: assembled.compact } };
|
|
144
119
|
}
|
|
145
120
|
export async function signReceipt(claims, keyHandle, chain, opts = {}) {
|
|
146
|
-
|
|
121
|
+
// The issuing view is REQUIRED (the reference takes ChainFacts or a
|
|
122
|
+
// CharterRevision, never none) and its discipline runs after the build,
|
|
123
|
+
// before the key - the reference's context is a verified ChainFacts by
|
|
124
|
+
// construction, so a TS view that fails the chain discipline is caller
|
|
125
|
+
// input, never a burned key operation.
|
|
126
|
+
const signed = await signProduced(keyHandle, (kid, algorithm) => {
|
|
127
|
+
const built = receiptSigningInput(kid, claims, algorithm);
|
|
128
|
+
if (!built.ok)
|
|
129
|
+
return built;
|
|
130
|
+
const resolved = chainViewOrInvalid(chain);
|
|
131
|
+
if ("error" in resolved)
|
|
132
|
+
return { ok: false, code: resolved.code };
|
|
133
|
+
if (!verifyChain(resolved).ok)
|
|
134
|
+
return { ok: false, code: "chain_invalid" };
|
|
135
|
+
return built;
|
|
136
|
+
}, opts);
|
|
147
137
|
if ("error" in signed)
|
|
148
138
|
return { ok: false, ...signed };
|
|
149
|
-
const
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return { ok: true, result: { receipt: compact } };
|
|
139
|
+
const assembled = compactOrInvalid(signed);
|
|
140
|
+
if ("error" in assembled)
|
|
141
|
+
return { ok: false, ...assembled };
|
|
142
|
+
const verified = verifyReceipt(assembled.compact, chain);
|
|
143
|
+
if (!verified.ok)
|
|
144
|
+
return { ok: false, error: "verification_failed" };
|
|
145
|
+
return { ok: true, result: { receipt: assembled.compact } };
|
|
156
146
|
}
|
|
157
147
|
export async function signAcceptance(claims, keyHandle, view, opts = {}) {
|
|
158
|
-
const
|
|
148
|
+
const viewChain = viewChainOrInvalid(view);
|
|
149
|
+
const signed = await signProduced(keyHandle, (kid, algorithm) => {
|
|
150
|
+
if ("error" in viewChain)
|
|
151
|
+
return { ok: false, code: viewChain.code };
|
|
152
|
+
return acceptanceSigningInput(kid, claims, viewChain, algorithm);
|
|
153
|
+
}, opts);
|
|
159
154
|
if ("error" in signed)
|
|
160
155
|
return { ok: false, ...signed };
|
|
161
|
-
const
|
|
162
|
-
|
|
156
|
+
const assembled = compactOrInvalid(signed);
|
|
157
|
+
if ("error" in assembled)
|
|
158
|
+
return { ok: false, ...assembled };
|
|
159
|
+
const verified = verifyAcceptance(assembled.compact, view.revisionText, view.descriptorCompacts);
|
|
163
160
|
if (!verified.ok)
|
|
164
|
-
return { ok: false, error: "
|
|
165
|
-
return { ok: true, result: { acceptance: compact } };
|
|
161
|
+
return { ok: false, error: "verification_failed" };
|
|
162
|
+
return { ok: true, result: { acceptance: assembled.compact } };
|
|
166
163
|
}
|
|
167
164
|
export async function signTermination(claims, keyHandle, view, opts = {}) {
|
|
168
|
-
const
|
|
165
|
+
const viewChain = viewChainOrInvalid(view);
|
|
166
|
+
const signed = await signProduced(keyHandle, (kid, algorithm) => {
|
|
167
|
+
if ("error" in viewChain)
|
|
168
|
+
return { ok: false, code: viewChain.code };
|
|
169
|
+
return terminationSigningInput(kid, claims, viewChain, algorithm);
|
|
170
|
+
}, opts);
|
|
169
171
|
if ("error" in signed)
|
|
170
172
|
return { ok: false, ...signed };
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
+
const assembled = compactOrInvalid(signed);
|
|
174
|
+
if ("error" in assembled)
|
|
175
|
+
return { ok: false, ...assembled };
|
|
176
|
+
const verified = verifyTermination(assembled.compact, view.revisionText, view.descriptorCompacts);
|
|
173
177
|
if (!verified.ok)
|
|
174
|
-
return { ok: false, error: "
|
|
175
|
-
return { ok: true, result: { termination: compact } };
|
|
178
|
+
return { ok: false, error: "verification_failed" };
|
|
179
|
+
return { ok: true, result: { termination: assembled.compact } };
|
|
176
180
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@charter-agreement-protocol/signer",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Holder-side companion signer for the Charter Agreement Protocol (CAP) in TypeScript
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Holder-side companion signer for the Charter Agreement Protocol (CAP) in TypeScript - key custody only: an atomic key snapshot, the sign callback, the wrong-key guard, and post-sign verification, with framing, the claims gate, the R1-R3 honest-signer refusals, and assembly all delegated to the @charter-agreement-protocol/verifier producer surface. CAP verifies; it never authorizes; this signer signs, it never verifies third-party artifacts or takes custody.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"./package.json": "./package.json"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
|
+
"CHANGELOG.md",
|
|
17
18
|
"dist",
|
|
18
19
|
"LICENSE",
|
|
19
20
|
"NOTICE",
|
|
@@ -26,13 +27,19 @@
|
|
|
26
27
|
"build": "tsc -p tsconfig.build.json",
|
|
27
28
|
"typecheck": "tsc --noEmit",
|
|
28
29
|
"test": "node --test --experimental-strip-types --no-warnings signer.test.ts",
|
|
29
|
-
"prepack": "npm run build"
|
|
30
|
+
"prepack": "npm run build",
|
|
31
|
+
"typecheck:web": "tsc -p tsconfig.web.json",
|
|
32
|
+
"build:site": "node web/build.mjs && esbuild web/main.ts --bundle --minify --format=iife --target=es2022 --outfile=site-dist/app.js --alias:node:crypto=./web/shim/node-crypto.ts --alias:node:fs=./web/shim/node-fs.ts --alias:node:path=./web/shim/node-path.ts --inject:./web/shim/buffer-global.ts"
|
|
30
33
|
},
|
|
31
34
|
"dependencies": {
|
|
32
|
-
"@charter-agreement-protocol/verifier": "^0.
|
|
35
|
+
"@charter-agreement-protocol/verifier": "^0.5.0"
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
38
|
+
"@noble/ed25519": "^3.2.0",
|
|
39
|
+
"@noble/hashes": "^2.4.0",
|
|
35
40
|
"@types/node": "^26.5.1",
|
|
41
|
+
"buffer": "^6.0.3",
|
|
42
|
+
"esbuild": "^0.28.2",
|
|
36
43
|
"typescript": "~5.9.0"
|
|
37
44
|
},
|
|
38
45
|
"repository": {
|