@charter-agreement-protocol/signer 0.2.2 → 0.3.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/CHANGELOG.md +133 -3
- package/CODE_OF_CONDUCT.md +87 -0
- package/CONTRIBUTING.md +71 -0
- package/README.md +170 -73
- package/SECURITY.md +55 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -4
- package/dist/signer.d.ts +17 -22
- package/dist/signer.js +258 -149
- package/package.json +11 -6
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,137 @@
|
|
|
3
3
|
All notable public changes to `@charter-agreement-protocol/signer` are
|
|
4
4
|
documented here.
|
|
5
5
|
|
|
6
|
+
## [0.3.0] — October 14, 2026
|
|
7
|
+
|
|
8
|
+
### Breaking
|
|
9
|
+
|
|
10
|
+
- `signAcceptance` and `signTermination` now take `(claims, keyHandle, chain,
|
|
11
|
+
opts)`. The verifier derives context from that chain; `revisionText` and
|
|
12
|
+
`descriptorCompacts` are removed from the signer input. Migration:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
// Before
|
|
16
|
+
await signAcceptance(claims, keyHandle, { revisionText, descriptorCompacts, chain }, opts);
|
|
17
|
+
// Now
|
|
18
|
+
await signAcceptance(claims, keyHandle, chain, opts);
|
|
19
|
+
await signTermination(terminationClaims, keyHandle, chain, opts);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- The holder's own descriptor key is checked before signing. A wrong kid,
|
|
23
|
+
public key, or key algorithm returns
|
|
24
|
+
`{ ok: false, error: "refused", code: "signing_key_mismatch" }`.
|
|
25
|
+
`signing_key_invalid` maps to `invalid_key_handle`. For otherwise valid
|
|
26
|
+
descriptor claims, an absent or null successor predecessor, a valid predecessor
|
|
27
|
+
supplied to a genesis, broken signed lineage, or digest aliases that disagree
|
|
28
|
+
with the reverified lineage return `invalid_input`/`descriptor_chain_invalid`.
|
|
29
|
+
A non-object predecessor, both digest aliases absent, or missing, null, empty,
|
|
30
|
+
or non-string lineage return `invalid_input`/`invalid_type`. Throwing reads also
|
|
31
|
+
return `invalid_type`. History excluded by a profile or requiring unavailable
|
|
32
|
+
crypto preserves the profile or substrate code.
|
|
33
|
+
- Context, claims/context mismatches, profile exclusions, and compact size
|
|
34
|
+
limits reject before the signing callback. Missing or malformed context
|
|
35
|
+
types return `invalid_input`/`invalid_type`; malformed sets preserve the
|
|
36
|
+
verifier's code, including `compact_invalid`. `verification_failed` now
|
|
37
|
+
means only that `finishSigning` rejected what preparation approved.
|
|
38
|
+
- Unavailable crypto returns the top-level
|
|
39
|
+
`{ ok: false, error: "algorithm_unsupported_on_substrate" }` before signing,
|
|
40
|
+
including an ML-DSA-65 request on the browser bundle.
|
|
41
|
+
- Rename `invalid_handle` to `invalid_key_handle`. Update error comparisons
|
|
42
|
+
and exhaustive switches; the result shape is unchanged. Add the new substrate
|
|
43
|
+
error and refusal code to exhaustive handling.
|
|
44
|
+
- Options must be absent or a plain object containing only `algorithm`,
|
|
45
|
+
`limits`, `profile`, and (for `signDescriptor`) `predecessor`. Unknown keys,
|
|
46
|
+
non-plain objects, and unreadable getters return `invalid_input`/`invalid_type`.
|
|
47
|
+
Malformed limits and profiles preserve `invalid_limits` and `invalid_profile`.
|
|
48
|
+
Previously, a misspelled option key silently fell back to Ed25519.
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- `signDescriptor` accepts the verifier's `DescriptorPredecessor` as
|
|
53
|
+
`opts.predecessor`, with retained signed `lineage`, for successor descriptors.
|
|
54
|
+
- All four functions accept verifier `limits` and `profile` options. Limits
|
|
55
|
+
include the final compact and context artifacts and also apply to the custody
|
|
56
|
+
guard and completion. Profiles check the new artifact and retained context.
|
|
57
|
+
- `signReceipt` accepts bare revision text as well as a chain view. With a
|
|
58
|
+
bare revision, the custody guard checks the snapshot key and verifier facts
|
|
59
|
+
list `signature` in `not_verified`.
|
|
60
|
+
- Export `SigningOptions` and `DescriptorOptions`. Real-key tests cover
|
|
61
|
+
Ed25519 and ML-DSA-65 successors and pre-sign refusals, with zero signing calls
|
|
62
|
+
asserted on rejection. The browser bundle test runs the complete workshop flow.
|
|
63
|
+
- Include `SECURITY.md`, `CODE_OF_CONDUCT.md`, and `CONTRIBUTING.md` in the
|
|
64
|
+
npm package with this package's reporting and contribution practices.
|
|
65
|
+
- Run the shared signer table from `charter_agreement_signer` 0.5.0, copied byte for byte with
|
|
66
|
+
a SHA-256 check. All 121 rows use real seed keys and compare result shapes,
|
|
67
|
+
codes, snapshot counts, and custody counts. Twelve Ed25519 successes match
|
|
68
|
+
exact compacts; five ML-DSA-65 successes pass public verification. The explicit
|
|
69
|
+
list of unexpressible rows is empty, and the run count is asserted. Eight
|
|
70
|
+
shared requests or contexts needing ML-DSA-65 refuse before browser custody.
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- Delegate preparation and completion to verifier 0.7.0's `prepareSigning`
|
|
75
|
+
and `finishSigning`; remove the signer's context derivation,
|
|
76
|
+
producer calls, assembly calls, and choice of post-sign verification function.
|
|
77
|
+
- Require `@charter-agreement-protocol/verifier` ^0.7.0. Its signing preparation
|
|
78
|
+
corpus uses the public input shapes, and signing plans can only be made through
|
|
79
|
+
`prepareSigning`. The signer already uses that path.
|
|
80
|
+
- Accept signature bytes from any JavaScript realm, including Buffer and plain
|
|
81
|
+
`Uint8Array`. Ed25519 snapshot keys with noncanonical y coordinates, no curve
|
|
82
|
+
point, x = 0 with the sign bit set, or small order return `invalid_key_handle`.
|
|
83
|
+
Invalid descriptor claim keys can fail earlier with `signing_input_invalid`.
|
|
84
|
+
The verifier checks option values before either key callback. Sparse profile
|
|
85
|
+
algorithm lists return `invalid_input`/`invalid_profile`; nonfinite or
|
|
86
|
+
explicitly undefined limit values return `invalid_input`/`invalid_limits`.
|
|
87
|
+
- Copy claims into plain JSON before the first `await`, without array
|
|
88
|
+
constructor hooks. Read defined context and predecessor members once and
|
|
89
|
+
retain artifact lists for the verifier. Lineage arrays have no JSON bounds.
|
|
90
|
+
Claims entry guards use the verifier's maximum JSON bounds. Caller limits and final compact size are
|
|
91
|
+
checked by `prepareSigning`; oversize claims return `signing_input_invalid`.
|
|
92
|
+
Predecessor lineage has no signer item limit.
|
|
93
|
+
- Read defined option members once with ordinary property access, including
|
|
94
|
+
inherited members and accepted class or Map properties. Keep invalid member
|
|
95
|
+
values opaque for the verifier's checks. Return every verifier option error,
|
|
96
|
+
including copy errors. Long algorithm lists retain an invalid length without
|
|
97
|
+
walking their entries.
|
|
98
|
+
- Run all 121 shared rows in Node and the browser bundle with no skips.
|
|
99
|
+
`ml-successor-ok` and `ml-successor-wrong` now use their table
|
|
100
|
+
`expected_limited`: `algorithm_unsupported_on_substrate`, one snapshot, and
|
|
101
|
+
zero custody calls because the browser cannot verify ML-DSA-65 predecessor
|
|
102
|
+
history. Node retains successor success and
|
|
103
|
+
`invalid_input`/`descriptor_chain_invalid`, respectively. Removing the duplicate
|
|
104
|
+
`termination-excluded-context` row
|
|
105
|
+
reduces the exact Ed25519 success count from 13 to 12 in each lane.
|
|
106
|
+
Existing row outcomes and the signer API remain unchanged with this build.
|
|
107
|
+
- Resolve limits, then profile, then algorithm, before taking the key snapshot.
|
|
108
|
+
Absent, `undefined`, and `null` algorithms select Ed25519. Explicit values are
|
|
109
|
+
`Ed25519` and `ML-DSA-65`; others return `invalid_input`/`algorithm_unsupported`
|
|
110
|
+
before either callback. The snapshot checks only a non-empty string kid and
|
|
111
|
+
a string public key; the verifier checks key encoding and shape.
|
|
112
|
+
- Keep version 0.3.0 and the workshop badge. Builds remove stale library output;
|
|
113
|
+
CI typechecks and builds the workshop on Linux with Node 24.8.0 and Node 24.
|
|
114
|
+
- SECURITY.md uses GitHub private vulnerability reporting. Evidence descriptions
|
|
115
|
+
name the checks that run; this signer has no telemetry.
|
|
116
|
+
- See the [profile scope](README.md#profile-scope) for the shared ruling.
|
|
117
|
+
|
|
118
|
+
### Migration from CAP 0.4 to 0.5
|
|
119
|
+
|
|
120
|
+
- Revisions must name two distinct parties from different descriptor histories.
|
|
121
|
+
Replace fixtures that use one descriptor for both roles with two real parties.
|
|
122
|
+
Chain views supply explicit `revisions`, `acceptances`, `descriptors`, and
|
|
123
|
+
`terminations` arrays, including empty lists.
|
|
124
|
+
|
|
125
|
+
### Migration from verifier 0.5 to 0.6
|
|
126
|
+
|
|
127
|
+
- Producer build failures surface as `invalid_input`/`signing_input_invalid`.
|
|
128
|
+
Termination claims omit `extensions` and other extra members.
|
|
129
|
+
- Digests are canonical base64url SHA-256 values and revision JSON is canonical.
|
|
130
|
+
Descriptor keys use canonical encodings with the selected algorithm's byte
|
|
131
|
+
length. Mixed party histories and unknown claims reject.
|
|
132
|
+
- Direct verifier callers pass retained signed `lineage` in predecessor facts
|
|
133
|
+
to `verifyDescriptor`; `{ digest, claims }` alone is insufficient. Genesis
|
|
134
|
+
calls omit the predecessor. This signer exposes that predecessor in options.
|
|
135
|
+
- Profiles and limits can reject context as well as the new artifact.
|
|
136
|
+
|
|
6
137
|
## [0.2.2] — October 6, 2026
|
|
7
138
|
|
|
8
139
|
### Changed
|
|
@@ -27,9 +158,8 @@ No API change: verifier 0.5.0 added consumer-side surface only (the
|
|
|
27
158
|
capability probe, the signature registry identity, the descriptor
|
|
28
159
|
timestamp floor, the 104-case corpus); the producer surface this package
|
|
29
160
|
delegates to is unchanged. Typecheck strict, 18/18 tests against the
|
|
30
|
-
resolved 0.5.0.
|
|
31
|
-
|
|
32
|
-
profile-bound signing) is the next feature release on this line.
|
|
161
|
+
resolved 0.5.0. Corrected in 0.3.0: 0.2.2 did not add signer capability metadata,
|
|
162
|
+
profile options, or telemetry.
|
|
33
163
|
|
|
34
164
|
## [0.2.0] — 2026-09-17
|
|
35
165
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<!-- Adapted from the Contributor Covenant version 2.1 (https://www.contributor-covenant.org),
|
|
2
|
+
the canonical text with the reporting contact substituted for this repository. -->
|
|
3
|
+
|
|
4
|
+
# Contributor Covenant Code of Conduct
|
|
5
|
+
|
|
6
|
+
## Our Pledge
|
|
7
|
+
|
|
8
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
|
9
|
+
|
|
10
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
|
11
|
+
|
|
12
|
+
## Our Standards
|
|
13
|
+
|
|
14
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
|
15
|
+
|
|
16
|
+
* Demonstrating empathy and kindness toward other people
|
|
17
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
18
|
+
* Giving and gracefully accepting constructive feedback
|
|
19
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
|
20
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
|
21
|
+
|
|
22
|
+
Examples of unacceptable behavior include:
|
|
23
|
+
|
|
24
|
+
* The use of sexualized language or imagery, and sexual attention or advances of any kind
|
|
25
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
26
|
+
* Public or private harassment
|
|
27
|
+
* Publishing others' private information, such as a physical or email address, without their explicit permission
|
|
28
|
+
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
29
|
+
|
|
30
|
+
## Enforcement Responsibilities
|
|
31
|
+
|
|
32
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
|
33
|
+
|
|
34
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
|
35
|
+
|
|
36
|
+
## Scope
|
|
37
|
+
|
|
38
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
|
39
|
+
|
|
40
|
+
## Enforcement
|
|
41
|
+
|
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at the repository maintainer PRIVATELY at palermo777@gmail.com — conduct reports are kept
|
|
43
|
+
confidential; please do not open public issues for conduct reports. All complaints will be reviewed and investigated promptly and fairly.
|
|
44
|
+
|
|
45
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
|
46
|
+
|
|
47
|
+
## Enforcement Guidelines
|
|
48
|
+
|
|
49
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
|
50
|
+
|
|
51
|
+
### 1. Correction
|
|
52
|
+
|
|
53
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
|
54
|
+
|
|
55
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
|
56
|
+
|
|
57
|
+
### 2. Warning
|
|
58
|
+
|
|
59
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
|
60
|
+
|
|
61
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
|
62
|
+
|
|
63
|
+
### 3. Temporary Ban
|
|
64
|
+
|
|
65
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
|
66
|
+
|
|
67
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
|
68
|
+
|
|
69
|
+
### 4. Permanent Ban
|
|
70
|
+
|
|
71
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
|
72
|
+
|
|
73
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
|
74
|
+
|
|
75
|
+
## Attribution
|
|
76
|
+
|
|
77
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
78
|
+
|
|
79
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
80
|
+
|
|
81
|
+
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
|
|
82
|
+
|
|
83
|
+
[homepage]: https://www.contributor-covenant.org
|
|
84
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
85
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
86
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
|
87
|
+
[translations]: https://www.contributor-covenant.org/translations
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Contributions must preserve the signer's boundaries: the library depends
|
|
4
|
+
only on the public verifier package, key identity comes from one atomic
|
|
5
|
+
snapshot, errors contain no caller values, and test handles do not ship.
|
|
6
|
+
Private keys stay behind the caller's callbacks.
|
|
7
|
+
|
|
8
|
+
## Toolchain
|
|
9
|
+
|
|
10
|
+
Use Node 24, at least 24.8.0, and npm. `package-lock.json` pins the build
|
|
11
|
+
tools and verifier version. CI runs on Linux with Node 24.8.0 and the current
|
|
12
|
+
Node 24 line.
|
|
13
|
+
|
|
14
|
+
## Before a pull request
|
|
15
|
+
|
|
16
|
+
Install with the frozen lockfile, then run the local checks:
|
|
17
|
+
|
|
18
|
+
```console
|
|
19
|
+
npm ci
|
|
20
|
+
npm test
|
|
21
|
+
npm run typecheck
|
|
22
|
+
npm run typecheck:web
|
|
23
|
+
npm run build
|
|
24
|
+
npm run build:site
|
|
25
|
+
npm pack --dry-run
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The checks after installation run locally without a network connection.
|
|
29
|
+
The suite builds a browser bundle with the workshop's shims and exercises
|
|
30
|
+
real Ed25519 and ML-DSA-65 keys. The library build writes `dist/`; the site
|
|
31
|
+
build writes `site-dist/`.
|
|
32
|
+
|
|
33
|
+
## Windows
|
|
34
|
+
|
|
35
|
+
Developer platforms are macOS and Linux. Windows developers use WSL2,
|
|
36
|
+
clone inside its filesystem and follow the Linux commands. CI runs on
|
|
37
|
+
Linux only. Native ML-DSA tests require the supported Node crypto runtime.
|
|
38
|
+
|
|
39
|
+
## Commits
|
|
40
|
+
|
|
41
|
+
Use explicit paths when staging or committing. Keep tool directories and
|
|
42
|
+
local process state out of commits. Work on `main` in small commits with
|
|
43
|
+
plain messages unless the maintainer asks otherwise. Do not use `git stash`.
|
|
44
|
+
|
|
45
|
+
## Tests and checks
|
|
46
|
+
|
|
47
|
+
- For behavior changes, write the test, observe it fail for the intended
|
|
48
|
+
reason, then make it pass. Record the observed failure in the commit.
|
|
49
|
+
- New checks must fail under a named temporary mutation. Restore the
|
|
50
|
+
original code and run the suite again before committing.
|
|
51
|
+
- Use real keys and real signing callbacks. Do not use mocks, fake crypto
|
|
52
|
+
or handles that return invented signatures.
|
|
53
|
+
- Search for tests of the old contract before changing behavior.
|
|
54
|
+
|
|
55
|
+
The local suite runs the [shared signer table](test/fixtures/README.md)
|
|
56
|
+
against the TypeScript verifier this checkout installs. It compares recorded
|
|
57
|
+
Elixir outcomes and exact Ed25519 compacts with real seed keys, then checks
|
|
58
|
+
every success through verification. It does not execute Elixir locally.
|
|
59
|
+
Keep the source table byte for byte and report disagreements with both outcomes.
|
|
60
|
+
|
|
61
|
+
## Protocol dependency changes
|
|
62
|
+
|
|
63
|
+
Move the verifier requirement and lockfile together. Read its changelog,
|
|
64
|
+
test against the published package and document changes to input rules
|
|
65
|
+
or error codes. Keep signing fixtures canonical.
|
|
66
|
+
|
|
67
|
+
## Docs
|
|
68
|
+
|
|
69
|
+
Update documentation with the behavior it describes. Keep error names,
|
|
70
|
+
supported options, package metadata and the workshop's version badge in
|
|
71
|
+
sync. Describe only checks that run and features that ship.
|
package/README.md
CHANGED
|
@@ -7,15 +7,12 @@ Holder-side companion signer for the
|
|
|
7
7
|
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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.
|
|
10
|
+
with two callbacks. The signer resolves limits, profile, and algorithm, copies
|
|
11
|
+
claims and context at entry, and takes one atomic `keyIdentity` snapshot.
|
|
12
|
+
The verifier's `prepareSigning` checks framing, claims, context, profile,
|
|
13
|
+
limits, and the holder's own descriptor key before the callback signs.
|
|
14
|
+
The signer checks the signature against the snapshot public key, then calls
|
|
15
|
+
`finishSigning` to assemble and verify under the retained context and settings.
|
|
19
16
|
|
|
20
17
|
## Install
|
|
21
18
|
|
|
@@ -23,8 +20,8 @@ verification of the assembled artifact through the
|
|
|
23
20
|
npm install @charter-agreement-protocol/signer
|
|
24
21
|
```
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
Version **0.3.0** requires Node >= 24.8.0 and
|
|
24
|
+
`@charter-agreement-protocol/verifier` ^0.7.0, for its pre-sign API.
|
|
28
25
|
|
|
29
26
|
## Quickstart
|
|
30
27
|
|
|
@@ -72,76 +69,175 @@ For ML-DSA-65 artifacts pass `{ algorithm: "ML-DSA-65" }` as the options
|
|
|
72
69
|
argument with `protocol_revision: 3` claims (pure ML-DSA per RFC 9964, the
|
|
73
70
|
empty context — your handle signs the exact message bytes as with Ed25519).
|
|
74
71
|
|
|
72
|
+
An absent, `undefined` or `null` algorithm selects Ed25519. Explicit
|
|
73
|
+
algorithms are `Ed25519` and `ML-DSA-65`; any other value returns
|
|
74
|
+
`invalid_input` with `code: "algorithm_unsupported"` before either key
|
|
75
|
+
callback runs.
|
|
76
|
+
|
|
75
77
|
## API
|
|
76
78
|
|
|
77
79
|
| Export | What it does |
|
|
78
80
|
|---|---|
|
|
79
|
-
| `signDescriptor(claims, handle, opts?)` | Signs
|
|
80
|
-
| `signReceipt(claims, handle,
|
|
81
|
-
| `signAcceptance(claims, handle,
|
|
82
|
-
| `signTermination(claims, handle,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
81
|
+
| `signDescriptor(claims, handle, opts?)` | Signs a genesis or successor Party Descriptor; successors require `opts.predecessor` |
|
|
82
|
+
| `signReceipt(claims, handle, context, opts?)` | Signs a Receipt against a chain view or bare revision text |
|
|
83
|
+
| `signAcceptance(claims, handle, chain, opts?)` | Signs an Acceptance using the context the verifier derives from the chain |
|
|
84
|
+
| `signTermination(claims, handle, chain, opts?)` | Signs a Termination using the context the verifier derives from the chain |
|
|
85
|
+
| `SigningOptions`, `DescriptorOptions`, `ChainView`, `KeyHandle`, `KeySnapshot`, `SignError`, `SignResult` | Public TypeScript contracts |
|
|
86
|
+
|
|
87
|
+
A `ChainView` contains four string arrays: `{ revisions, acceptances,
|
|
88
|
+
descriptors, terminations }`. Pass all four, including explicit empty arrays.
|
|
89
|
+
Acceptance and termination use this chain directly. The verifier derives the
|
|
90
|
+
claimed revision and the holder's own descriptor history from its verified
|
|
91
|
+
contents. The signer copies claims at entry, before awaiting a key callback.
|
|
92
|
+
Claims copies contain fresh plain JSON objects and arrays, strings, finite
|
|
93
|
+
numbers, booleans, and null. Repeated objects are copied separately. Non-JSON
|
|
94
|
+
claims, cycles, and throwing reads return `invalid_input`/`invalid_type`.
|
|
95
|
+
Chain context and predecessor use their defined members with ordinary property
|
|
96
|
+
reads, and retained artifact lists get fresh arrays. Valid members are immutable
|
|
97
|
+
text; invalid member values stay opaque for the verifier.
|
|
98
|
+
|
|
99
|
+
Claims copies use an iterative traversal guarded by the verifier's maximum
|
|
100
|
+
supported JSON bounds, rather than the caller's limits. Values beyond those
|
|
101
|
+
maximums return `invalid_input`/`signing_input_invalid`. Smaller claims reach
|
|
102
|
+
`prepareSigning`, which owns all checks under caller limits, including final
|
|
103
|
+
compact size. Context list copies retain enough entries to exceed every
|
|
104
|
+
supported artifact item limit, letting the verifier return its own result.
|
|
105
|
+
Predecessor lineage has no signer item limit. The verifier checks immutable
|
|
106
|
+
artifact text under artifact byte limits, separately from parsed claims' string
|
|
107
|
+
limits.
|
|
108
|
+
|
|
109
|
+
Migration from the previous issuing view:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// Before
|
|
113
|
+
await signAcceptance(claims, handle, { revisionText, descriptorCompacts, chain }, opts);
|
|
114
|
+
// Now
|
|
115
|
+
await signAcceptance(claims, handle, chain, opts);
|
|
116
|
+
await signTermination(terminationClaims, handle, chain, opts);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
For a successor descriptor, pass the verifier's `DescriptorPredecessor` facts
|
|
120
|
+
with retained signed `lineage`. The signing key must match an active key in
|
|
121
|
+
the predecessor:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { verifyDescriptor } from "@charter-agreement-protocol/verifier";
|
|
125
|
+
|
|
126
|
+
const previous = verifyDescriptor(genesisCompact);
|
|
127
|
+
if (!previous.ok) throw new Error(previous.code);
|
|
128
|
+
const next = await signDescriptor(successorClaims, handle, { predecessor: previous.facts });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
For a receipt against bare revision text, the verifier checks the receipt's
|
|
132
|
+
claims against that revision. The revision names no signing key, so the signer
|
|
133
|
+
uses its snapshot for the custody guard; receipt verification reports
|
|
134
|
+
`signature` in `not_verified`. A chain-backed receipt checks the key against
|
|
135
|
+
the issuing party's descriptor keys in the holder's own view.
|
|
136
|
+
|
|
137
|
+
`opts` is absent or an object with known own keys only: `algorithm`, `limits`,
|
|
138
|
+
and `profile`, plus `predecessor` for `signDescriptor`. Another root shape, an
|
|
139
|
+
unknown own key, or a throwing getter returns `invalid_input` with
|
|
140
|
+
`code: "invalid_type"`. Defined members use ordinary property reads, once at
|
|
141
|
+
entry, including inherited and nonenumerable members. Class instances and
|
|
142
|
+
`Map` objects work where the verifier accepts their properties; Map entries
|
|
143
|
+
are not option properties.
|
|
144
|
+
|
|
145
|
+
`limits` is the verifier's `Partial<Limits>` and `profile` is its `Profile`.
|
|
146
|
+
The signer reads only their defined shape. Invalid values stay opaque for the
|
|
147
|
+
verifier's synchronous checks; they are not recursively copied by the signer.
|
|
148
|
+
Limits are checked before profile, algorithm, and the key snapshot. Malformed
|
|
149
|
+
values, including bigint, symbol, and boxed numbers, retain the verifier's
|
|
150
|
+
`invalid_limits` or `invalid_profile` result. Sparse algorithm lists return
|
|
151
|
+
`invalid_profile`; nonfinite or explicitly undefined limit values return
|
|
152
|
+
`invalid_limits`. A list longer than the algorithm registry cannot be a valid
|
|
153
|
+
unique subset, so its invalid length reaches the verifier without reading its
|
|
154
|
+
entries. The verifier's copy failures are also returned unchanged.
|
|
155
|
+
Limits bound the final compact, including signature bytes, and
|
|
156
|
+
every context artifact read; raised limits also apply to assembly and the
|
|
157
|
+
custody guard. Profiles can restrict `algorithms` and/or
|
|
158
|
+
`revisions: { min, max }`; an omitted axis keeps the full supported range.
|
|
159
|
+
An EdDSA-only profile, an ML-DSA-44/87-only profile, or revisions 1..1 excludes
|
|
160
|
+
every artifact this signer emits. Exclusions return `algorithm_outside_profile`
|
|
161
|
+
or `revision_outside_profile`.
|
|
162
|
+
|
|
163
|
+
<a id="profile-scope"></a>
|
|
164
|
+
|
|
165
|
+
For acceptance and termination, the caller's profile covers the claimed
|
|
166
|
+
revision, the signing party's own descriptor history, and the new artifact.
|
|
167
|
+
The rest of the set is checked under the full default profile. For receipts,
|
|
168
|
+
the profile covers the new artifact and the whole retained set. For descriptors,
|
|
169
|
+
it covers the new artifact and predecessor history.
|
|
170
|
+
|
|
171
|
+
Errors are closed:
|
|
172
|
+
|
|
173
|
+
| Result error | Meaning |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `invalid_input`, with `code` | The verifier rejected claims, context, limits, or profile, or options were invalid |
|
|
176
|
+
| `invalid_key_handle` | The snapshot callback failed, its shape was malformed, or the verifier returned `signing_key_invalid` |
|
|
177
|
+
| `refused`, with `code: "signing_refused"` | The claims contradict the holder's own verified view (R1–R3) |
|
|
178
|
+
| `refused`, with `code: "signing_key_mismatch"` | The kid, public key bytes, or key algorithm does not match the holder's own descriptor key |
|
|
179
|
+
| `algorithm_unsupported_on_substrate` | The runtime cannot verify the selected algorithm or a context artifact |
|
|
180
|
+
| `signing_failed` | The callback failed, returned a malformed signature, or failed the wrong-key guard |
|
|
181
|
+
| `verification_failed` | `finishSigning` rejected what preparation approved |
|
|
182
|
+
|
|
183
|
+
Every failure has `ok: false`. Only `invalid_input` and `refused` carry `code`.
|
|
184
|
+
The signing callback can return a Buffer or any `Uint8Array`, including one
|
|
185
|
+
from another JavaScript realm. The verifier rejects Ed25519 public keys with
|
|
186
|
+
noncanonical y coordinates, no curve point, x = 0 with the sign bit set, or
|
|
187
|
+
small order as `invalid_key_handle` when they are snapshot keys. Descriptor
|
|
188
|
+
claims containing such keys can fail earlier with `signing_input_invalid`.
|
|
189
|
+
All pre-sign rejections leave the `sign` callback untouched; the atomic key
|
|
190
|
+
snapshot precedes `prepareSigning`. A malformed context type returns
|
|
191
|
+
`invalid_input`/`invalid_type`. For otherwise valid descriptor claims, predecessor
|
|
192
|
+
errors return `invalid_input` with these codes before signing:
|
|
193
|
+
|
|
194
|
+
- `invalid_type`: a non-object predecessor, a missing digest alias (both `digest`
|
|
195
|
+
and `descriptor_digest` absent), or missing, null, empty, or non-string `lineage`.
|
|
196
|
+
Throwing reads also return this code.
|
|
197
|
+
- `descriptor_chain_invalid`: an absent or null predecessor for a successor,
|
|
198
|
+
a valid predecessor supplied to a genesis, a broken signed lineage, or digest
|
|
199
|
+
aliases that disagree with the reverified lineage.
|
|
200
|
+
- `algorithm_outside_profile`, `revision_outside_profile`, or
|
|
201
|
+
`algorithm_unsupported_on_substrate`: predecessor history excluded by the
|
|
202
|
+
profile or requiring unavailable crypto.
|
|
102
203
|
|
|
103
204
|
## The sibling verifier
|
|
104
205
|
|
|
105
|
-
|
|
106
|
-
Charter Agreement Protocol
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
the wrong-key guard, post-sign verification, and the closed error
|
|
113
|
-
vocabulary — all protocol logic delegated to the verifier.
|
|
114
|
-
- [`@charter-agreement-protocol/verifier`](https://www.npmjs.com/package/@charter-agreement-protocol/verifier)
|
|
115
|
-
— verification and the pure producer surface (signing inputs, claims
|
|
116
|
-
gate, refusal guards, assembly) that this package signs through.
|
|
117
|
-
|
|
118
|
-
This signer depends on the verifier (`^0.5.0`, locked to 0.5.0). When changing
|
|
119
|
-
that dependency, publish the verifier version first, then lock and release
|
|
120
|
-
the signer against the published version. The packages have independent
|
|
121
|
-
SemVer versions; signer documentation and build-tool updates can release
|
|
122
|
-
without a verifier version change.
|
|
206
|
+
The signer and verifier are separate TypeScript packages implementing the
|
|
207
|
+
Charter Agreement Protocol, with no Elixir dependency at runtime. The
|
|
208
|
+
verifier ships a corpus certified against the Elixir protocol reference.
|
|
209
|
+
This signer tests its artifacts through the TypeScript verifier.
|
|
210
|
+
All protocol checks run in `prepareSigning` and `finishSigning`; the signer
|
|
211
|
+
keeps key custody callbacks, the signature guard, entry copies, option
|
|
212
|
+
resolution, and the closed error vocabulary.
|
|
123
213
|
|
|
124
214
|
## Evidence
|
|
125
215
|
|
|
126
|
-
-
|
|
127
|
-
|
|
128
|
-
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
216
|
+
- Linux CI runs the suite on Node 24.8.0 and the current Node 24 line.
|
|
217
|
+
Real Node crypto mints Ed25519 and ML-DSA-65 genesis and successor descriptors.
|
|
218
|
+
Pre-sign rejection tests count real signing calls and require zero calls.
|
|
219
|
+
Tests cover key mismatches, predecessor checks, malformed contexts, profiles,
|
|
220
|
+
final compact limits, receipt claims, entry copies, and unknown algorithms.
|
|
221
|
+
- The workshop's browser bundle mints Ed25519 and runs the complete workshop
|
|
222
|
+
flow. It cannot verify ML-DSA-65 and refuses that request before signing with
|
|
223
|
+
the top-level `algorithm_unsupported_on_substrate` error.
|
|
224
|
+
- The [shared table](test/fixtures/README.md) runs all 121 rows from the Elixir
|
|
225
|
+
signer, `charter_agreement_signer` 0.5.0, with its exact source bytes pinned by SHA-256. Real seed
|
|
226
|
+
keys check result shapes and codes, snapshot and custody counts, 12 exact
|
|
227
|
+
Ed25519 success compacts, and five ML-DSA-65 successes through verification.
|
|
228
|
+
All 121 rows run in both Node and the browser bundle with no skips. Eight
|
|
229
|
+
shared requests or contexts that need ML-DSA-65 also return a substrate error
|
|
230
|
+
before custody in the browser bundle.
|
|
231
|
+
This compares the recorded Elixir outcomes; it does not run Elixir locally.
|
|
232
|
+
|
|
233
|
+
## Verifier compatibility and telemetry
|
|
234
|
+
|
|
235
|
+
The verifier requires canonical digests and revision JSON, distinct party
|
|
236
|
+
histories, and all four chain arrays. Build failures surface as `invalid_input`
|
|
237
|
+
with `code: "signing_input_invalid"`; termination claims must omit `extensions`.
|
|
238
|
+
See [CHANGELOG.md](CHANGELOG.md) for migration notes.
|
|
239
|
+
|
|
240
|
+
This package has no telemetry events or hooks and no signer capability report.
|
|
145
241
|
|
|
146
242
|
## SemVer
|
|
147
243
|
|
|
@@ -156,13 +252,14 @@ or changes behavior.
|
|
|
156
252
|
## Development
|
|
157
253
|
|
|
158
254
|
Use Node 24 (at least 24.8.0) and npm. The lockfile selects TypeScript 7.0.2
|
|
159
|
-
and `@types/node` 26.6.4. CI runs on Linux with Node 24 and
|
|
160
|
-
the same npm commands are used for local development on macOS and Linux.
|
|
255
|
+
and `@types/node` 26.6.4. CI runs on Linux with Node 24.8.0 and Node 24
|
|
256
|
+
using a frozen install; the same npm commands are used for local development on macOS and Linux.
|
|
161
257
|
|
|
162
258
|
```console
|
|
163
259
|
npm ci
|
|
164
260
|
npm run typecheck
|
|
165
261
|
npm test
|
|
262
|
+
npm run test:cases
|
|
166
263
|
npm run build
|
|
167
264
|
npm run typecheck:web
|
|
168
265
|
npm run build:site
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
The latest published `0.x` line is supported. Wire compatibility follows
|
|
6
|
+
the public `@charter-agreement-protocol/verifier` package's contract.
|
|
7
|
+
|
|
8
|
+
## Security invariants
|
|
9
|
+
|
|
10
|
+
- The library never solicits, processes or retains private key material.
|
|
11
|
+
Custody lives behind the caller's key-handle callbacks.
|
|
12
|
+
- Every signature is verified against the snapshot public key before
|
|
13
|
+
success. A signature from another key returns `signing_failed`.
|
|
14
|
+
- Key identity (`kid` and public key) comes from one atomic `keyIdentity`
|
|
15
|
+
snapshot per signing call. The signer supplies that snapshot's kid to
|
|
16
|
+
the producer.
|
|
17
|
+
- Errors contain closed names and codes, with no key material, message
|
|
18
|
+
bytes, claims content or callback exception text.
|
|
19
|
+
- The library's only runtime dependency is the public verifier package.
|
|
20
|
+
It contains no transport or HTTP client/server.
|
|
21
|
+
- Test handles stay in test files and do not ship in the npm package.
|
|
22
|
+
|
|
23
|
+
## Reporting a vulnerability
|
|
24
|
+
|
|
25
|
+
Report it privately through GitHub's private vulnerability reporting for
|
|
26
|
+
`baselabs/charter_agreement_signer_typescript` (the repository's Security
|
|
27
|
+
tab, then "Report a vulnerability"). Do not open a public issue containing
|
|
28
|
+
an exploit, credential, private key or unreleased vulnerability detail.
|
|
29
|
+
|
|
30
|
+
A report should include:
|
|
31
|
+
|
|
32
|
+
- the affected version or commit;
|
|
33
|
+
- the violated property;
|
|
34
|
+
- a minimal reproduction with newly generated test keys and no production
|
|
35
|
+
claims or credentials;
|
|
36
|
+
- the expected security outcome.
|
|
37
|
+
|
|
38
|
+
## Verifying a release
|
|
39
|
+
|
|
40
|
+
The [Release workflow](https://github.com/baselabs/charter_agreement_signer_typescript/blob/main/.github/workflows/release.yml)
|
|
41
|
+
checks the package before staging it on npm with provenance through
|
|
42
|
+
GitHub Actions OIDC. The owner approves the staged release on npm with 2FA.
|
|
43
|
+
Inspect the published package's source repository, archive integrity and
|
|
44
|
+
provenance metadata with:
|
|
45
|
+
|
|
46
|
+
```console
|
|
47
|
+
npm view @charter-agreement-protocol/signer repository.url dist.integrity dist.attestations
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Acknowledgment
|
|
51
|
+
|
|
52
|
+
We will acknowledge reports within 7 days and send status updates at least
|
|
53
|
+
weekly until resolution, coordinating disclosure through a private advisory.
|
|
54
|
+
This is a best-effort cadence from a small maintainer team, not a contractual
|
|
55
|
+
SLA. Mark urgent reports clearly.
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { signDescriptor, signReceipt, signAcceptance, signTermination, type KeyHandle, type KeySnapshot, type ChainView, type SignError, type SignResult, } from "./signer.ts";
|
|
1
|
+
export { signDescriptor, signReceipt, signAcceptance, signTermination, type KeyHandle, type KeySnapshot, type ChainView, type SigningOptions, type DescriptorOptions, type SignError, type SignResult, } from "./signer.ts";
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
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
|
-
// artifact — goes through @charter-agreement-protocol/verifier: exactly
|
|
8
|
-
// one implementation. This package holds the custody half only.
|
|
5
|
+
// vocabulary. Protocol checks run through prepareSigning and finishSigning
|
|
6
|
+
// in @charter-agreement-protocol/verifier. This package keeps custody.
|
|
9
7
|
export { signDescriptor, signReceipt, signAcceptance, signTermination, } from "./signer.js";
|
package/dist/signer.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DescriptorPredecessor, type Limits, type Profile } from "@charter-agreement-protocol/verifier";
|
|
1
2
|
export type KeySnapshot = {
|
|
2
3
|
kid: string;
|
|
3
4
|
publicKey: string;
|
|
@@ -7,7 +8,7 @@ export type KeyHandle = {
|
|
|
7
8
|
sign(message: Uint8Array, handle: unknown): Uint8Array | Promise<Uint8Array>;
|
|
8
9
|
};
|
|
9
10
|
export type SignError = {
|
|
10
|
-
error: "
|
|
11
|
+
error: "invalid_key_handle";
|
|
11
12
|
} | {
|
|
12
13
|
error: "signing_failed";
|
|
13
14
|
} | {
|
|
@@ -15,7 +16,9 @@ export type SignError = {
|
|
|
15
16
|
code: string;
|
|
16
17
|
} | {
|
|
17
18
|
error: "refused";
|
|
18
|
-
code:
|
|
19
|
+
code: "signing_refused" | "signing_key_mismatch";
|
|
20
|
+
} | {
|
|
21
|
+
error: "algorithm_unsupported_on_substrate";
|
|
19
22
|
} | {
|
|
20
23
|
error: "verification_failed";
|
|
21
24
|
};
|
|
@@ -32,31 +35,23 @@ export type ChainView = {
|
|
|
32
35
|
descriptors: string[];
|
|
33
36
|
terminations: string[];
|
|
34
37
|
};
|
|
35
|
-
export
|
|
36
|
-
algorithm?: string;
|
|
37
|
-
|
|
38
|
+
export type SigningOptions = {
|
|
39
|
+
algorithm?: string | null;
|
|
40
|
+
limits?: Partial<Limits>;
|
|
41
|
+
profile?: Profile;
|
|
42
|
+
};
|
|
43
|
+
export type DescriptorOptions = SigningOptions & {
|
|
44
|
+
predecessor?: DescriptorPredecessor | null;
|
|
45
|
+
};
|
|
46
|
+
export declare function signDescriptor(claims: Record<string, unknown>, keyHandle: unknown, opts?: DescriptorOptions): Promise<SignResult<{
|
|
38
47
|
descriptor: string;
|
|
39
48
|
}>>;
|
|
40
|
-
export declare function signReceipt(claims: Record<string, unknown>, keyHandle: unknown,
|
|
41
|
-
algorithm?: string;
|
|
42
|
-
}): Promise<SignResult<{
|
|
49
|
+
export declare function signReceipt(claims: Record<string, unknown>, keyHandle: unknown, context: ChainView | string, opts?: SigningOptions): Promise<SignResult<{
|
|
43
50
|
receipt: string;
|
|
44
51
|
}>>;
|
|
45
|
-
export declare function signAcceptance(claims: Record<string, unknown>, keyHandle: unknown,
|
|
46
|
-
revisionText: string;
|
|
47
|
-
descriptorCompacts: string[];
|
|
48
|
-
chain: ChainView;
|
|
49
|
-
}, opts?: {
|
|
50
|
-
algorithm?: string;
|
|
51
|
-
}): Promise<SignResult<{
|
|
52
|
+
export declare function signAcceptance(claims: Record<string, unknown>, keyHandle: unknown, chain: ChainView, opts?: SigningOptions): Promise<SignResult<{
|
|
52
53
|
acceptance: string;
|
|
53
54
|
}>>;
|
|
54
|
-
export declare function signTermination(claims: Record<string, unknown>, keyHandle: unknown,
|
|
55
|
-
revisionText: string;
|
|
56
|
-
descriptorCompacts: string[];
|
|
57
|
-
chain: ChainView;
|
|
58
|
-
}, opts?: {
|
|
59
|
-
algorithm?: string;
|
|
60
|
-
}): Promise<SignResult<{
|
|
55
|
+
export declare function signTermination(claims: Record<string, unknown>, keyHandle: unknown, chain: ChainView, opts?: SigningOptions): Promise<SignResult<{
|
|
61
56
|
termination: string;
|
|
62
57
|
}>>;
|
package/dist/signer.js
CHANGED
|
@@ -1,180 +1,289 @@
|
|
|
1
1
|
// CAP never authorizes.
|
|
2
|
-
//
|
|
3
|
-
// The
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
2
|
+
// The holder supplies one key snapshot and signs the verifier's prepared
|
|
3
|
+
// message. The verifier owns framing, claims, context, limits, profile,
|
|
4
|
+
// key matching, assembly, and completion. This package keeps custody and
|
|
5
|
+
// the wrong-key guard.
|
|
6
|
+
import { DEFAULT_LIMITS, algorithmRegistry, MAXIMUM_LIMITS, defaultEmissionName, decodeCharterRevision, emissions, finishSigning, prepareSigning, verifySignature, } from "@charter-agreement-protocol/verifier";
|
|
7
|
+
const COPY_LIMIT = Symbol("copy limit");
|
|
8
|
+
// Read one child at a time without recursion or array constructor hooks.
|
|
9
|
+
// Only ancestors are retained, so repeated objects get separate copies.
|
|
10
|
+
function copyClaims(value) {
|
|
11
|
+
const limits = MAXIMUM_LIMITS;
|
|
12
|
+
const ancestors = new WeakSet();
|
|
13
|
+
const frames = [];
|
|
14
|
+
let bytes = 0;
|
|
15
|
+
const addBytes = (size) => {
|
|
16
|
+
bytes += size;
|
|
17
|
+
if (bytes > limits.max_bytes)
|
|
18
|
+
throw COPY_LIMIT;
|
|
19
|
+
};
|
|
20
|
+
const stringBytes = (text) => {
|
|
21
|
+
if (Buffer.byteLength(text) > limits.max_string_bytes)
|
|
22
|
+
throw COPY_LIMIT;
|
|
23
|
+
return Buffer.byteLength(JSON.stringify(text));
|
|
24
|
+
};
|
|
25
|
+
const copy = (item, depth) => {
|
|
26
|
+
if (item === null || typeof item === "boolean") {
|
|
27
|
+
addBytes(item === null || item === true ? 4 : 5);
|
|
28
|
+
return item;
|
|
29
|
+
}
|
|
30
|
+
if (typeof item === "string") {
|
|
31
|
+
addBytes(stringBytes(item));
|
|
32
|
+
return item;
|
|
33
|
+
}
|
|
34
|
+
if (typeof item === "number" && Number.isFinite(item)) {
|
|
35
|
+
addBytes(JSON.stringify(item).length);
|
|
36
|
+
return item;
|
|
37
|
+
}
|
|
38
|
+
if (typeof item !== "object" || ancestors.has(item))
|
|
39
|
+
throw new TypeError("invalid_type");
|
|
40
|
+
if (depth + 1 > limits.max_depth)
|
|
41
|
+
throw COPY_LIMIT;
|
|
42
|
+
const array = Array.isArray(item);
|
|
43
|
+
let keys = null;
|
|
44
|
+
if (!array) {
|
|
45
|
+
const prototype = Object.getPrototypeOf(item);
|
|
46
|
+
if ((prototype !== Object.prototype && prototype !== null) || Object.getOwnPropertySymbols(item).length > 0) {
|
|
47
|
+
throw new TypeError("invalid_type");
|
|
48
|
+
}
|
|
49
|
+
keys = Object.keys(item);
|
|
50
|
+
}
|
|
51
|
+
const size = array ? item.length : keys.length;
|
|
52
|
+
if (!Number.isSafeInteger(size) || size < 0 || array && size > 2 ** 32 - 1)
|
|
53
|
+
throw new TypeError("invalid_type");
|
|
54
|
+
if (size > (array ? limits.max_array_items : limits.max_object_members)) {
|
|
55
|
+
throw COPY_LIMIT;
|
|
56
|
+
}
|
|
57
|
+
addBytes(2 + Math.max(0, size - 1));
|
|
58
|
+
const target = array ? [] : Object.create(null);
|
|
59
|
+
ancestors.add(item);
|
|
60
|
+
frames.push({ source: item, target, keys, size, index: 0, depth: depth + 1 });
|
|
61
|
+
return target;
|
|
62
|
+
};
|
|
63
|
+
const result = copy(value, 0);
|
|
64
|
+
while (frames.length > 0) {
|
|
65
|
+
const frame = frames[frames.length - 1];
|
|
66
|
+
if (frame.index === frame.size) {
|
|
67
|
+
ancestors.delete(frame.source);
|
|
68
|
+
frames.pop();
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const index = frame.index++;
|
|
72
|
+
const key = frame.keys ? frame.keys[index] : String(index);
|
|
73
|
+
if (frame.keys)
|
|
74
|
+
addBytes(stringBytes(key) + 1);
|
|
75
|
+
const child = frame.source[key];
|
|
76
|
+
const copied = copy(child, frame.depth);
|
|
77
|
+
if (Array.isArray(frame.target))
|
|
78
|
+
frame.target.push(copied);
|
|
79
|
+
else
|
|
80
|
+
frame.target[key] = copied;
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
function copyChainView(chain) {
|
|
85
|
+
const names = ["revisions", "acceptances", "descriptors", "terminations"];
|
|
86
|
+
const lists = Object.fromEntries(names.map((name) => [name, (value) => copyTextList(value, MAXIMUM_LIMITS.max_artifact_set_items + 1)]));
|
|
87
|
+
return optionMembers(chain, names, lists);
|
|
21
88
|
}
|
|
22
89
|
async function resolveKeyIdentity(handle) {
|
|
23
|
-
if (handle === null || typeof handle !== "object" || !("keyIdentity" in handle)) {
|
|
24
|
-
return { error: "invalid_handle" };
|
|
25
|
-
}
|
|
26
90
|
try {
|
|
91
|
+
if (handle === null || typeof handle !== "object")
|
|
92
|
+
return { error: "invalid_key_handle" };
|
|
27
93
|
const snapshot = await handle.keyIdentity(handle);
|
|
28
|
-
if (snapshot
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return snapshot;
|
|
94
|
+
if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot))
|
|
95
|
+
return { error: "invalid_key_handle" };
|
|
96
|
+
const { kid, publicKey } = snapshot;
|
|
97
|
+
if (typeof kid !== "string" || kid.length === 0 || typeof publicKey !== "string") {
|
|
98
|
+
return { error: "invalid_key_handle" };
|
|
34
99
|
}
|
|
35
|
-
return {
|
|
100
|
+
return { kid, publicKey };
|
|
36
101
|
}
|
|
37
102
|
catch (_fault) {
|
|
38
|
-
return { error: "
|
|
103
|
+
return { error: "invalid_key_handle" };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const OPTION_KEYS = new Set(["algorithm", "limits", "profile"]);
|
|
107
|
+
const byteArrayTag = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Uint8Array.prototype), Symbol.toStringTag).get;
|
|
108
|
+
// Read the defined shape once, including inherited and nonenumerable members.
|
|
109
|
+
// Invalid member values stay opaque for the verifier to check synchronously.
|
|
110
|
+
function optionMembers(value, names, nested = {}) {
|
|
111
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Number)
|
|
112
|
+
return value;
|
|
113
|
+
const source = value;
|
|
114
|
+
const result = Object.create(null);
|
|
115
|
+
const unknown = Object.keys(source).find((name) => !names.includes(name));
|
|
116
|
+
if (unknown !== undefined)
|
|
117
|
+
result[unknown] = null;
|
|
118
|
+
for (const name of names) {
|
|
119
|
+
const member = source[name];
|
|
120
|
+
if (member !== undefined || name in source)
|
|
121
|
+
result[name] = nested[name] ? nested[name](member) : member;
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
function optionAlgorithms(value) {
|
|
126
|
+
if (!Array.isArray(value))
|
|
127
|
+
return value;
|
|
128
|
+
const length = value.length;
|
|
129
|
+
if (!Number.isSafeInteger(length) || length < 0)
|
|
130
|
+
throw new TypeError("invalid_type");
|
|
131
|
+
// A unique registry subset cannot be longer than the registry. Retain an
|
|
132
|
+
// invalid length for the verifier without reading an unbounded caller list.
|
|
133
|
+
if (length > MAXIMUM_LIMITS.max_artifact_set_items)
|
|
134
|
+
return Array(MAXIMUM_LIMITS.max_artifact_set_items + 1);
|
|
135
|
+
if (length > algorithmRegistry().length)
|
|
136
|
+
return value;
|
|
137
|
+
const result = [];
|
|
138
|
+
for (let i = 0; i < length; i += 1)
|
|
139
|
+
result.push(value[i]);
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
function copyTextList(value, maximum = 2 ** 32 - 1) {
|
|
143
|
+
if (!Array.isArray(value))
|
|
144
|
+
return value;
|
|
145
|
+
const length = value.length;
|
|
146
|
+
// A plain array cannot represent a length above 2^32 - 1.
|
|
147
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > 2 ** 32 - 1)
|
|
148
|
+
throw new TypeError("invalid_type");
|
|
149
|
+
const result = [];
|
|
150
|
+
for (let i = 0; i < Math.min(length, maximum); i += 1)
|
|
151
|
+
result.push(value[i]);
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
function checkOptions(settings) {
|
|
155
|
+
// Empty text always gives invalid_syntax after successful option checks.
|
|
156
|
+
// Every earlier code comes from the verifier, including copy failures.
|
|
157
|
+
const checked = decodeCharterRevision("", settings);
|
|
158
|
+
return !checked.ok && checked.code !== "invalid_syntax" ? { error: "invalid_input", code: checked.code } : null;
|
|
159
|
+
}
|
|
160
|
+
function signingOptions(opts, descriptor) {
|
|
161
|
+
try {
|
|
162
|
+
if (opts === null || typeof opts !== "object" || Array.isArray(opts) || opts instanceof Number) {
|
|
163
|
+
return { error: "invalid_input", code: "invalid_type" };
|
|
164
|
+
}
|
|
165
|
+
if (!Reflect.ownKeys(opts).every((key) => typeof key === "string" &&
|
|
166
|
+
(OPTION_KEYS.has(key) || descriptor && key === "predecessor"))) {
|
|
167
|
+
return { error: "invalid_input", code: "invalid_type" };
|
|
168
|
+
}
|
|
169
|
+
const supplied = opts;
|
|
170
|
+
const settings = {
|
|
171
|
+
limits: optionMembers(supplied.limits, Object.keys(DEFAULT_LIMITS)),
|
|
172
|
+
};
|
|
173
|
+
const limitsError = checkOptions(settings);
|
|
174
|
+
if (limitsError)
|
|
175
|
+
return limitsError;
|
|
176
|
+
settings.profile = optionMembers(supplied.profile, ["algorithms", "revisions"], {
|
|
177
|
+
algorithms: optionAlgorithms,
|
|
178
|
+
revisions: (range) => optionMembers(range, ["min", "max"]),
|
|
179
|
+
});
|
|
180
|
+
const profileError = checkOptions(settings);
|
|
181
|
+
if (profileError)
|
|
182
|
+
return profileError;
|
|
183
|
+
const algorithm = supplied.algorithm ?? defaultEmissionName();
|
|
184
|
+
if (typeof algorithm !== "string" || !Object.hasOwn(emissions(), algorithm)) {
|
|
185
|
+
return { error: "invalid_input", code: "algorithm_unsupported" };
|
|
186
|
+
}
|
|
187
|
+
if (!descriptor)
|
|
188
|
+
return { ...settings, algorithm };
|
|
189
|
+
const previous = supplied.predecessor;
|
|
190
|
+
const predecessor = optionMembers(previous, ["digest", "descriptor_digest", "lineage"], {
|
|
191
|
+
lineage: (value) => copyTextList(value),
|
|
192
|
+
});
|
|
193
|
+
return { ...settings, algorithm, predecessor: predecessor };
|
|
194
|
+
}
|
|
195
|
+
catch (fault) {
|
|
196
|
+
return { error: "invalid_input", code: fault === COPY_LIMIT ? "limit_exceeded" : "invalid_type" };
|
|
39
197
|
}
|
|
40
198
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
199
|
+
function prepareError(code) {
|
|
200
|
+
switch (code) {
|
|
201
|
+
case "signing_refused":
|
|
202
|
+
case "signing_key_mismatch":
|
|
203
|
+
return { error: "refused", code };
|
|
204
|
+
case "signing_key_invalid":
|
|
205
|
+
return { error: "invalid_key_handle" };
|
|
206
|
+
case "algorithm_unsupported_on_substrate":
|
|
207
|
+
return { error: "algorithm_unsupported_on_substrate" };
|
|
208
|
+
default:
|
|
209
|
+
return { error: "invalid_input", code };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async function signPrepared(kind, claims, keyHandle, context, opts) {
|
|
213
|
+
const options = signingOptions(opts, kind === "descriptor");
|
|
214
|
+
if ("error" in options)
|
|
215
|
+
return options;
|
|
216
|
+
let retained;
|
|
217
|
+
try {
|
|
218
|
+
retained = kind === "descriptor" ? options.predecessor ?? null :
|
|
219
|
+
typeof context === "string" ? context : copyChainView(context);
|
|
220
|
+
}
|
|
221
|
+
catch (fault) {
|
|
222
|
+
return { error: "invalid_input", code: fault === COPY_LIMIT ? "limit_exceeded" : "invalid_type" };
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
// Caller limits belong to preparation. The entry guard only stops values
|
|
226
|
+
// beyond every JSON limit the verifier can accept.
|
|
227
|
+
claims = copyClaims(claims);
|
|
228
|
+
}
|
|
229
|
+
catch (fault) {
|
|
230
|
+
return { error: "invalid_input", code: fault === COPY_LIMIT ? "signing_input_invalid" : "invalid_type" };
|
|
231
|
+
}
|
|
46
232
|
const snapshot = await resolveKeyIdentity(keyHandle);
|
|
47
233
|
if ("error" in snapshot)
|
|
48
234
|
return snapshot;
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// The holder signs the exact message; a fault or a wrong-length result is
|
|
58
|
-
// a signing failure, never a silent pass.
|
|
235
|
+
const prepared = prepareSigning(kind, snapshot.kid, claims, snapshot.publicKey, retained, {
|
|
236
|
+
algorithm: options.algorithm,
|
|
237
|
+
limits: options.limits,
|
|
238
|
+
profile: options.profile,
|
|
239
|
+
});
|
|
240
|
+
if (!prepared.ok)
|
|
241
|
+
return prepareError(prepared.code);
|
|
242
|
+
const input = prepared.plan.input;
|
|
59
243
|
let signature;
|
|
60
244
|
try {
|
|
61
|
-
signature = await keyHandle.sign(
|
|
245
|
+
signature = await keyHandle.sign(Buffer.from(input.message), keyHandle);
|
|
62
246
|
}
|
|
63
247
|
catch (_fault) {
|
|
64
248
|
return { error: "signing_failed" };
|
|
65
249
|
}
|
|
66
|
-
if (!(signature
|
|
250
|
+
if (!ArrayBuffer.isView(signature) || byteArrayTag.call(signature) !== "Uint8Array") {
|
|
67
251
|
return { error: "signing_failed" };
|
|
68
252
|
}
|
|
69
|
-
|
|
70
|
-
|
|
253
|
+
let checkedSignature;
|
|
254
|
+
try {
|
|
255
|
+
checkedSignature = Buffer.from(signature);
|
|
256
|
+
}
|
|
257
|
+
catch (_signature) {
|
|
71
258
|
return { error: "signing_failed" };
|
|
72
259
|
}
|
|
73
|
-
// The
|
|
74
|
-
|
|
75
|
-
|
|
260
|
+
// The verifier's signature guard owns signature length checks.
|
|
261
|
+
if (!verifySignature(prepared.plan.input.message, checkedSignature, snapshot.publicKey, input.alg, {
|
|
262
|
+
limits: options.limits,
|
|
263
|
+
profile: options.profile,
|
|
264
|
+
})) {
|
|
76
265
|
return { error: "signing_failed" };
|
|
77
266
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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);
|
|
267
|
+
const finished = finishSigning(prepared.plan, checkedSignature);
|
|
268
|
+
// No real-crypto path reaches this mapping after preparation and the
|
|
269
|
+
// custody guard succeed; keep it for a verifier completion error.
|
|
270
|
+
if (!finished.ok)
|
|
271
|
+
return { error: "verification_failed" };
|
|
272
|
+
return { compact: finished.compact };
|
|
104
273
|
}
|
|
105
|
-
// ---------------------------------------------------------------------------
|
|
106
|
-
// Public signing surface
|
|
107
|
-
// ---------------------------------------------------------------------------
|
|
108
274
|
export async function signDescriptor(claims, keyHandle, opts = {}) {
|
|
109
|
-
const signed = await
|
|
110
|
-
|
|
111
|
-
return { ok: false, ...signed };
|
|
112
|
-
const assembled = compactOrInvalid(signed);
|
|
113
|
-
if ("error" in assembled)
|
|
114
|
-
return { ok: false, ...assembled };
|
|
115
|
-
const verified = verifyDescriptor(assembled.compact);
|
|
116
|
-
if (!verified.ok)
|
|
117
|
-
return { ok: false, error: "verification_failed" };
|
|
118
|
-
return { ok: true, result: { descriptor: assembled.compact } };
|
|
275
|
+
const signed = await signPrepared("descriptor", claims, keyHandle, null, opts);
|
|
276
|
+
return "error" in signed ? { ok: false, ...signed } : { ok: true, result: { descriptor: signed.compact } };
|
|
119
277
|
}
|
|
120
|
-
export async function signReceipt(claims, keyHandle,
|
|
121
|
-
|
|
122
|
-
|
|
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);
|
|
137
|
-
if ("error" in signed)
|
|
138
|
-
return { ok: false, ...signed };
|
|
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 } };
|
|
278
|
+
export async function signReceipt(claims, keyHandle, context, opts = {}) {
|
|
279
|
+
const signed = await signPrepared("receipt", claims, keyHandle, context, opts);
|
|
280
|
+
return "error" in signed ? { ok: false, ...signed } : { ok: true, result: { receipt: signed.compact } };
|
|
146
281
|
}
|
|
147
|
-
export async function signAcceptance(claims, keyHandle,
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
if ("error" in viewChain)
|
|
151
|
-
return { ok: false, code: viewChain.code };
|
|
152
|
-
return acceptanceSigningInput(kid, claims, viewChain, algorithm);
|
|
153
|
-
}, opts);
|
|
154
|
-
if ("error" in signed)
|
|
155
|
-
return { ok: false, ...signed };
|
|
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);
|
|
160
|
-
if (!verified.ok)
|
|
161
|
-
return { ok: false, error: "verification_failed" };
|
|
162
|
-
return { ok: true, result: { acceptance: assembled.compact } };
|
|
282
|
+
export async function signAcceptance(claims, keyHandle, chain, opts = {}) {
|
|
283
|
+
const signed = await signPrepared("acceptance", claims, keyHandle, chain, opts);
|
|
284
|
+
return "error" in signed ? { ok: false, ...signed } : { ok: true, result: { acceptance: signed.compact } };
|
|
163
285
|
}
|
|
164
|
-
export async function signTermination(claims, keyHandle,
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
if ("error" in viewChain)
|
|
168
|
-
return { ok: false, code: viewChain.code };
|
|
169
|
-
return terminationSigningInput(kid, claims, viewChain, algorithm);
|
|
170
|
-
}, opts);
|
|
171
|
-
if ("error" in signed)
|
|
172
|
-
return { ok: false, ...signed };
|
|
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);
|
|
177
|
-
if (!verified.ok)
|
|
178
|
-
return { ok: false, error: "verification_failed" };
|
|
179
|
-
return { ok: true, result: { termination: assembled.compact } };
|
|
286
|
+
export async function signTermination(claims, keyHandle, chain, opts = {}) {
|
|
287
|
+
const signed = await signPrepared("termination", claims, keyHandle, chain, opts);
|
|
288
|
+
return "error" in signed ? { ok: false, ...signed } : { ok: true, result: { termination: signed.compact } };
|
|
180
289
|
}
|
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 - key custody only: an atomic key snapshot, the sign callback, the wrong-key guard, and post-sign verification, with framing,
|
|
3
|
+
"version": "0.3.0",
|
|
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, claims, context, key matching, limits, profile, and completion delegated to the @charter-agreement-protocol/verifier pre-sign API. 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",
|
|
@@ -15,24 +15,29 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"CHANGELOG.md",
|
|
18
|
+
"CODE_OF_CONDUCT.md",
|
|
19
|
+
"CONTRIBUTING.md",
|
|
18
20
|
"dist",
|
|
19
21
|
"LICENSE",
|
|
20
22
|
"NOTICE",
|
|
21
|
-
"README.md"
|
|
23
|
+
"README.md",
|
|
24
|
+
"SECURITY.md"
|
|
22
25
|
],
|
|
23
26
|
"engines": {
|
|
24
27
|
"node": ">=24.8.0"
|
|
25
28
|
},
|
|
26
29
|
"scripts": {
|
|
27
|
-
"build": "tsc -p tsconfig.build.json",
|
|
30
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json",
|
|
28
31
|
"typecheck": "tsc --noEmit",
|
|
29
|
-
"test": "node --test --experimental-strip-types --no-warnings signer.test.ts",
|
|
32
|
+
"test": "node --test --experimental-strip-types --no-warnings signer.test.ts signer.browser.test.ts signer.cases.test.ts",
|
|
33
|
+
"test:cost": "node --test --experimental-strip-types --no-warnings signer.cost.test.ts",
|
|
34
|
+
"test:cases": "node --test --experimental-strip-types --no-warnings signer.cases.test.ts",
|
|
30
35
|
"prepack": "npm run build",
|
|
31
36
|
"typecheck:web": "tsc -p tsconfig.web.json",
|
|
32
37
|
"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"
|
|
33
38
|
},
|
|
34
39
|
"dependencies": {
|
|
35
|
-
"@charter-agreement-protocol/verifier": "^0.
|
|
40
|
+
"@charter-agreement-protocol/verifier": "^0.7.0"
|
|
36
41
|
},
|
|
37
42
|
"devDependencies": {
|
|
38
43
|
"@noble/ed25519": "^3.2.0",
|