@metalabel/dfos-protocol 0.4.0 → 0.5.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/PROTOCOL.md +89 -34
- package/README.md +13 -0
- package/dist/chain/index.d.ts +107 -32
- package/dist/chain/index.js +17 -5
- package/dist/{chunk-GEVJ3SEV.js → chunk-QKHP7UVL.js} +251 -54
- package/dist/index.d.ts +1 -1
- package/dist/index.js +16 -4
- package/package.json +2 -2
package/PROTOCOL.md
CHANGED
|
@@ -20,15 +20,16 @@ The protocol is not coupled to the DFOS platform. Any system implementing the sa
|
|
|
20
20
|
|
|
21
21
|
## Protocol Overview
|
|
22
22
|
|
|
23
|
-
The DFOS protocol has
|
|
23
|
+
The DFOS protocol has six components:
|
|
24
24
|
|
|
25
|
-
| Component | Concern
|
|
26
|
-
| --------------------- |
|
|
27
|
-
| **Crypto core** | Identity chains + content chains — Ed25519 signatures, JWS tokens, CID links
|
|
28
|
-
| **Credentials** | Auth tokens (DID-signed JWT) and VC-JWT credentials for authorization
|
|
29
|
-
| **Beacons** | Signed merkle root announcements — periodic commitment over content sets
|
|
30
|
-
| **
|
|
31
|
-
| **
|
|
25
|
+
| Component | Concern |
|
|
26
|
+
| --------------------- | ------------------------------------------------------------------------------- |
|
|
27
|
+
| **Crypto core** | Identity chains + content chains — Ed25519 signatures, JWS tokens, CID links |
|
|
28
|
+
| **Credentials** | Auth tokens (DID-signed JWT) and VC-JWT credentials for authorization |
|
|
29
|
+
| **Beacons** | Signed merkle root announcements — periodic commitment over content sets |
|
|
30
|
+
| **Artifacts** | Standalone signed inline documents — immutable, CID-addressable structured data |
|
|
31
|
+
| **Countersignatures** | Standalone witness attestation — signed references to any CID-addressable op |
|
|
32
|
+
| **Merkle trees** | SHA-256 binary trees over content IDs — inclusion proofs for beacon roots |
|
|
32
33
|
|
|
33
34
|
The crypto core is the trust boundary — everything below it is cryptographically verified. Documents are flat content objects, content-addressed directly: `documentCID = CID(dagCborCanonicalEncode(contentObject))`. What goes inside the content object is application-defined — see the [DFOS Content Model](https://protocol.dfos.com/content-model) for the standard schema library.
|
|
34
35
|
|
|
@@ -126,6 +127,8 @@ The JWS `typ` header uses protocol-specific values (not IANA media types):
|
|
|
126
127
|
| `did:dfos:identity-op` | Identity chain operations |
|
|
127
128
|
| `did:dfos:content-op` | Content chain operations |
|
|
128
129
|
| `did:dfos:beacon` | Beacon announcements |
|
|
130
|
+
| `did:dfos:artifact` | Standalone signed inline documents |
|
|
131
|
+
| `did:dfos:countersign` | Standalone witness attestations |
|
|
129
132
|
| `JWT` | Auth tokens (DID-signed relay authentication) |
|
|
130
133
|
| `vc+jwt` | VC-JWT credentials (W3C VC Data Model v2) |
|
|
131
134
|
|
|
@@ -584,15 +587,9 @@ typ: did:dfos:beacon
|
|
|
584
587
|
cid: bafyreihholuui7s7ns74iem6ahfxsb472hwogbqd32yrrp5fztc3kxa5qu
|
|
585
588
|
```
|
|
586
589
|
|
|
587
|
-
**Witness countersignature** (
|
|
590
|
+
**Witness countersignature** (a separate identity countersigns the beacon by CID):
|
|
588
591
|
|
|
589
|
-
|
|
590
|
-
kid: did:dfos:e3vvtck42d4eacdnzvtrn6#key_ez9a874tckr3dv933d3ckd
|
|
591
|
-
typ: did:dfos:beacon
|
|
592
|
-
cid: bafyreihholuui7s7ns74iem6ahfxsb472hwogbqd32yrrp5fztc3kxa5qu
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
Both JWS tokens commit to identical bytes (same CID). The controller/witness distinction is determined at verification time by comparing the `kid` DID to the payload `did`.
|
|
592
|
+
A countersignature is a standalone operation with its own CID and `typ: did:dfos:countersign`. See the [Countersignatures](#countersignatures) section below.
|
|
596
593
|
|
|
597
594
|
Full JWS tokens are in [`examples/beacon.json`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/examples/beacon.json).
|
|
598
595
|
|
|
@@ -700,22 +697,78 @@ Proof path (from [`examples/merkle-tree.json`](https://github.com/metalabel/dfos
|
|
|
700
697
|
|
|
701
698
|
---
|
|
702
699
|
|
|
700
|
+
## Artifacts
|
|
701
|
+
|
|
702
|
+
Artifacts are standalone signed inline documents — immutable, CID-addressable proof plane primitives. Unlike chain operations which extend a sequence, an artifact is a single signed statement with no predecessor or successor.
|
|
703
|
+
|
|
704
|
+
### Payload
|
|
705
|
+
|
|
706
|
+
```json
|
|
707
|
+
{
|
|
708
|
+
"version": 1,
|
|
709
|
+
"type": "artifact",
|
|
710
|
+
"did": "did:dfos:...",
|
|
711
|
+
"content": {
|
|
712
|
+
"$schema": "https://schemas.dfos.com/profile/v1",
|
|
713
|
+
"name": "Example"
|
|
714
|
+
},
|
|
715
|
+
"createdAt": "2026-03-25T00:00:00.000Z"
|
|
716
|
+
}
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
The `content` object MUST include a `$schema` string that identifies the artifact's schema. The schema acts as a discriminator — consumers use it to determine how to interpret the artifact's content. Schema names are free-form strings (no protocol-level registry).
|
|
720
|
+
|
|
721
|
+
### Constraints
|
|
722
|
+
|
|
723
|
+
- **JWS `typ` header**: `did:dfos:artifact`
|
|
724
|
+
- **Max payload size**: 16384 bytes CBOR-encoded. Protocol constant — not configurable
|
|
725
|
+
- **Immutability**: Once published, an artifact is never updated or replaced
|
|
726
|
+
- **CID-addressable**: Each artifact is addressed by the CID of its CBOR-encoded payload
|
|
727
|
+
|
|
728
|
+
### Verification
|
|
729
|
+
|
|
730
|
+
1. JWS signature verification against the signing DID's current key state
|
|
731
|
+
2. CID integrity — `header.cid` matches the CID computed from dag-cbor canonical encoding the raw payload
|
|
732
|
+
3. Payload schema validation — `version`, `type: "artifact"`, `did`, `content` with `$schema`, `createdAt`
|
|
733
|
+
4. Size limit — CBOR-encoded payload does not exceed 16384 bytes
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
703
737
|
## Countersignatures
|
|
704
738
|
|
|
705
|
-
A countersignature is a witness attestation — a
|
|
739
|
+
A countersignature is a standalone witness attestation — a signed statement that references a target operation by CID. Each countersignature has its own `typ` header (`did:dfos:countersign`), its own payload, and its own CID distinct from the target.
|
|
740
|
+
|
|
741
|
+
### Payload
|
|
742
|
+
|
|
743
|
+
```json
|
|
744
|
+
{
|
|
745
|
+
"version": 1,
|
|
746
|
+
"type": "countersign",
|
|
747
|
+
"did": "did:dfos:witness...",
|
|
748
|
+
"targetCID": "bafy...",
|
|
749
|
+
"createdAt": "2026-03-25T00:00:00.000Z"
|
|
750
|
+
}
|
|
751
|
+
```
|
|
706
752
|
|
|
707
|
-
|
|
753
|
+
The `did` field is the witness identity — the DID signing the attestation. The `targetCID` references the operation being attested to.
|
|
708
754
|
|
|
709
|
-
|
|
755
|
+
### Properties
|
|
710
756
|
|
|
711
|
-
-
|
|
712
|
-
-
|
|
757
|
+
- **JWS `typ` header**: `did:dfos:countersign`
|
|
758
|
+
- **Own CID**: Each countersignature has its own CID derived from its own payload, distinct from the target. This avoids the ambiguity of multiple JWS tokens sharing the same CID
|
|
759
|
+
- **Stateless verification**: Signature + CID integrity + payload schema. No chain state required to verify the cryptographic validity of a countersignature
|
|
760
|
+
- **Composable**: The `targetCID` can reference any CID-addressable operation — content ops, beacons, artifacts, identity ops, even other countersignatures
|
|
761
|
+
- **Immutable**: Once published, a countersignature is permanent
|
|
713
762
|
|
|
714
|
-
###
|
|
763
|
+
### Verification
|
|
715
764
|
|
|
716
|
-
|
|
765
|
+
1. Decode JWS, verify `typ` is `did:dfos:countersign`
|
|
766
|
+
2. Parse and validate countersign payload (`version`, `type: "countersign"`, `did`, `targetCID`, `createdAt`)
|
|
767
|
+
3. Verify the `kid` DID matches the payload `did` (the witness must sign with their own key)
|
|
768
|
+
4. CID integrity — `header.cid` matches the CID computed from dag-cbor canonical encoding the raw payload
|
|
769
|
+
5. Verify EdDSA JWS signature against the witness's public key
|
|
717
770
|
|
|
718
|
-
|
|
771
|
+
Relay-level semantic checks (target exists, witness ≠ author, deduplication) are enforcement concerns, not protocol verification.
|
|
719
772
|
|
|
720
773
|
---
|
|
721
774
|
|
|
@@ -741,7 +794,7 @@ Countersignatures are not part of the chain — they do not have `previousOperat
|
|
|
741
794
|
2. First op must be `type: "create"` — the signer is the chain creator
|
|
742
795
|
3. For each subsequent op: verify `previousOperationCID` matches, verify `createdAt` increasing
|
|
743
796
|
4. Derive the operation CID via dag-cbor canonical encoding. Verify `header.cid` matches the derived CID.
|
|
744
|
-
5. Verify the `kid` DID matches the payload `did` field
|
|
797
|
+
5. Verify the `kid` DID matches the payload `did` field
|
|
745
798
|
6. Resolve `kid` via external key resolver (caller provides)
|
|
746
799
|
7. Verify EdDSA JWS signature
|
|
747
800
|
8. If `enforceAuthorization` is enabled and the signer DID differs from the chain creator: verify the `authorization` field contains a valid `DFOSContentWrite` VC-JWT issued by the creator DID, with `sub` matching the signer, not expired at `op.createdAt`, and `contentId` (if present) matching this chain
|
|
@@ -1077,7 +1130,7 @@ Given the artifacts above, verify:
|
|
|
1077
1130
|
|
|
1078
1131
|
## Source and Verification
|
|
1079
1132
|
|
|
1080
|
-
All source lives in [`packages/dfos-protocol/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol) — self-contained, zero monorepo dependencies.
|
|
1133
|
+
All source lives in [`packages/dfos-protocol/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol) — self-contained, zero monorepo dependencies. 266 checks across 5 languages.
|
|
1081
1134
|
|
|
1082
1135
|
- [`crypto/ed25519`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/ed25519.ts) — `createNewEd25519Keypair`, `importEd25519Keypair`, `signPayloadEd25519`, `isValidEd25519Signature`
|
|
1083
1136
|
- [`crypto/jws`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/jws.ts) — `createJws`, `verifyJws`, `decodeJwsUnsafe`
|
|
@@ -1086,11 +1139,12 @@ All source lives in [`packages/dfos-protocol/`](https://github.com/metalabel/dfo
|
|
|
1086
1139
|
- [`crypto/multiformats`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/multiformats.ts) — `dagCborCanonicalEncode`, `dagCborCanonicalEqual`
|
|
1087
1140
|
- [`crypto/id`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/crypto/id.ts) — `generateId`, `generateIdNoPrefix`, `isValidId`
|
|
1088
1141
|
- [`chain/multikey`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/multikey.ts) — `encodeEd25519Multikey`, `decodeMultikey`
|
|
1089
|
-
- [`chain/schemas`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/schemas.ts) — `IdentityOperation`, `ContentOperation`, `MultikeyPublicKey`, `VerifiedIdentity`
|
|
1090
|
-
- [`chain/identity-chain`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/identity-chain.ts) — `signIdentityOperation`, `verifyIdentityChain`
|
|
1091
|
-
- [`chain/content-chain`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/content-chain.ts) — `signContentOperation`, `verifyContentChain`
|
|
1142
|
+
- [`chain/schemas`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/schemas.ts) — `IdentityOperation`, `ContentOperation`, `ArtifactPayload`, `CountersignPayload`, `MultikeyPublicKey`, `VerifiedIdentity`
|
|
1143
|
+
- [`chain/identity-chain`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/identity-chain.ts) — `signIdentityOperation`, `verifyIdentityChain`, `verifyIdentityExtensionFromTrustedState`
|
|
1144
|
+
- [`chain/content-chain`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/content-chain.ts) — `signContentOperation`, `verifyContentChain`, `verifyContentExtensionFromTrustedState`
|
|
1092
1145
|
- [`chain/derivation`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/derivation.ts) — `deriveChainIdentifier`, `deriveContentId`
|
|
1093
1146
|
- [`chain/beacon`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/beacon.ts) — `signBeacon`, `verifyBeacon`
|
|
1147
|
+
- [`chain/artifact`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/artifact.ts) — `signArtifact`, `verifyArtifact`
|
|
1094
1148
|
- [`chain/countersign`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/chain/countersign.ts) — `signCountersignature`, `verifyCountersignature`
|
|
1095
1149
|
- [`credentials/auth-token`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/credentials/auth-token.ts) — `createAuthToken`, `verifyAuthToken`
|
|
1096
1150
|
- [`credentials/credential`](https://github.com/metalabel/dfos/blob/main/packages/dfos-protocol/src/credentials/credential.ts) — `createCredential`, `verifyCredential`, `decodeCredentialUnsafe`
|
|
@@ -1102,16 +1156,17 @@ All source lives in [`packages/dfos-protocol/`](https://github.com/metalabel/dfo
|
|
|
1102
1156
|
|
|
1103
1157
|
- [DID Method: `did:dfos`](https://protocol.dfos.com/did-method) — W3C DID method specification for identity chains
|
|
1104
1158
|
- [Content Model](https://protocol.dfos.com/content-model) — Standard content schemas (post, profile) for document content objects
|
|
1159
|
+
- [Web Relay](https://protocol.dfos.com/web-relay) — HTTP relay specification for ingestion, state, and content plane
|
|
1105
1160
|
|
|
1106
1161
|
### Cross-Language Verification
|
|
1107
1162
|
|
|
1108
1163
|
| Language | Tests | Source |
|
|
1109
1164
|
| ---------- | ----- | ---------------------------------------------------------------------------------------------------- |
|
|
1110
|
-
| TypeScript |
|
|
1111
|
-
|
|
|
1112
|
-
|
|
|
1113
|
-
|
|
|
1114
|
-
| Swift |
|
|
1165
|
+
| TypeScript | 224 | [`tests/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/tests) |
|
|
1166
|
+
| Go | 18 | [`verify/go/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/go) |
|
|
1167
|
+
| Rust | 18 | [`verify/rust/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/rust) |
|
|
1168
|
+
| Python | 3 | [`verify/python/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/python) |
|
|
1169
|
+
| Swift | 3 | [`verify/swift/`](https://github.com/metalabel/dfos/tree/main/packages/dfos-protocol/verify/swift) |
|
|
1115
1170
|
|
|
1116
1171
|
---
|
|
1117
1172
|
|
package/README.md
CHANGED
|
@@ -53,6 +53,19 @@ The `examples/` directory contains deterministic reference fixtures that can be
|
|
|
53
53
|
- `merkle-tree.json` — 5 content IDs → sorted tree → root, with inclusion proof
|
|
54
54
|
- `beacon.json` — signed merkle root announcement with witness countersignature
|
|
55
55
|
|
|
56
|
+
## Cross-Language Verification
|
|
57
|
+
|
|
58
|
+
The `verify/` directory contains independent verification suites that re-derive CIDs and verify signatures from the reference fixtures — proving protocol correctness across implementations:
|
|
59
|
+
|
|
60
|
+
| Language | Path | Status |
|
|
61
|
+
| -------- | ---------------- | ------- |
|
|
62
|
+
| Go | `verify/go/` | Passing |
|
|
63
|
+
| Python | `verify/python/` | Passing |
|
|
64
|
+
| Rust | `verify/rust/` | Passing |
|
|
65
|
+
| Swift | `verify/swift/` | Passing |
|
|
66
|
+
|
|
67
|
+
Each suite uses only its language's native Ed25519, dag-cbor, and multihash implementations — no shared code with the TypeScript reference.
|
|
68
|
+
|
|
56
69
|
## License
|
|
57
70
|
|
|
58
71
|
MIT
|
package/dist/chain/index.d.ts
CHANGED
|
@@ -113,6 +113,28 @@ declare const BeaconPayload: z.ZodObject<{
|
|
|
113
113
|
createdAt: z.ZodISODateTime;
|
|
114
114
|
}, z.core.$strict>;
|
|
115
115
|
type BeaconPayload = z.infer<typeof BeaconPayload>;
|
|
116
|
+
/** Max CBOR-encoded payload size for artifacts (bytes) — protocol constant */
|
|
117
|
+
declare const MAX_ARTIFACT_PAYLOAD_SIZE = 16384;
|
|
118
|
+
/** Artifact: standalone signed inline document, immutable, CID-addressable */
|
|
119
|
+
declare const ArtifactPayload: z.ZodObject<{
|
|
120
|
+
version: z.ZodLiteral<1>;
|
|
121
|
+
type: z.ZodLiteral<"artifact">;
|
|
122
|
+
did: z.ZodString;
|
|
123
|
+
content: z.ZodObject<{
|
|
124
|
+
$schema: z.ZodString;
|
|
125
|
+
}, z.core.$catchall<z.ZodUnknown>>;
|
|
126
|
+
createdAt: z.ZodISODateTime;
|
|
127
|
+
}, z.core.$strict>;
|
|
128
|
+
type ArtifactPayload = z.infer<typeof ArtifactPayload>;
|
|
129
|
+
/** Countersign: standalone witness attestation referencing a target operation by CID */
|
|
130
|
+
declare const CountersignPayload: z.ZodObject<{
|
|
131
|
+
version: z.ZodLiteral<1>;
|
|
132
|
+
type: z.ZodLiteral<"countersign">;
|
|
133
|
+
did: z.ZodString;
|
|
134
|
+
targetCID: z.ZodString;
|
|
135
|
+
createdAt: z.ZodISODateTime;
|
|
136
|
+
}, z.core.$strict>;
|
|
137
|
+
type CountersignPayload = z.infer<typeof CountersignPayload>;
|
|
116
138
|
|
|
117
139
|
/** Ed25519 public key multicodec value */
|
|
118
140
|
declare const ED25519_PUB_MULTICODEC = 237;
|
|
@@ -172,6 +194,33 @@ declare const verifyIdentityChain: (input: {
|
|
|
172
194
|
didPrefix: string;
|
|
173
195
|
log: string[];
|
|
174
196
|
}) => Promise<VerifiedIdentity>;
|
|
197
|
+
/**
|
|
198
|
+
* Verify a single new operation against already-verified identity state
|
|
199
|
+
*
|
|
200
|
+
* The caller guarantees that `currentState` was produced by a correct prior
|
|
201
|
+
* verification (full chain replay or a chain of trusted extensions from a
|
|
202
|
+
* verified genesis). This function performs one signature verification and one
|
|
203
|
+
* state transition — constant time regardless of chain length.
|
|
204
|
+
*
|
|
205
|
+
* Note: key-ID consistency across the full chain history is NOT checked here.
|
|
206
|
+
* That invariant is established during genesis verification and maintained by
|
|
207
|
+
* the protocol's key consistency rules. Periodic full re-verification can
|
|
208
|
+
* audit this property.
|
|
209
|
+
*/
|
|
210
|
+
declare const verifyIdentityExtensionFromTrustedState: (input: {
|
|
211
|
+
/** Previously verified identity state */
|
|
212
|
+
currentState: VerifiedIdentity;
|
|
213
|
+
/** CID of the most recent operation in the chain */
|
|
214
|
+
headCID: string;
|
|
215
|
+
/** createdAt timestamp of the most recent operation */
|
|
216
|
+
lastCreatedAt: string;
|
|
217
|
+
/** The new JWS operation to verify */
|
|
218
|
+
newOp: string;
|
|
219
|
+
}) => Promise<{
|
|
220
|
+
state: VerifiedIdentity;
|
|
221
|
+
operationCID: string;
|
|
222
|
+
createdAt: string;
|
|
223
|
+
}>;
|
|
175
224
|
|
|
176
225
|
interface VerifiedContentChain {
|
|
177
226
|
/** Content identifier — bare 22-char hash derived from genesis CID */
|
|
@@ -227,6 +276,29 @@ declare const verifyContentChain: (input: {
|
|
|
227
276
|
*/
|
|
228
277
|
enforceAuthorization?: boolean;
|
|
229
278
|
}) => Promise<VerifiedContentChain>;
|
|
279
|
+
/**
|
|
280
|
+
* Verify a single new content operation against already-verified chain state
|
|
281
|
+
*
|
|
282
|
+
* Same trust model as verifyIdentityExtensionFromTrustedState — the caller
|
|
283
|
+
* guarantees `currentState` was correctly verified. One signature verification,
|
|
284
|
+
* one key resolution, one state transition.
|
|
285
|
+
*/
|
|
286
|
+
declare const verifyContentExtensionFromTrustedState: (input: {
|
|
287
|
+
/** Previously verified content chain state */
|
|
288
|
+
currentState: VerifiedContentChain;
|
|
289
|
+
/** createdAt timestamp of the most recent operation */
|
|
290
|
+
lastCreatedAt: string;
|
|
291
|
+
/** The new JWS operation to verify */
|
|
292
|
+
newOp: string;
|
|
293
|
+
/** Resolve a kid (DID URL) to the raw Ed25519 public key bytes */
|
|
294
|
+
resolveKey: (kid: string) => Promise<Uint8Array>;
|
|
295
|
+
/** Enforce creator-sovereignty authorization (see verifyContentChain) */
|
|
296
|
+
enforceAuthorization?: boolean;
|
|
297
|
+
}) => Promise<{
|
|
298
|
+
state: VerifiedContentChain;
|
|
299
|
+
operationCID: string;
|
|
300
|
+
createdAt: string;
|
|
301
|
+
}>;
|
|
230
302
|
|
|
231
303
|
/**
|
|
232
304
|
* Sign a beacon announcement as a JWS
|
|
@@ -253,57 +325,60 @@ declare const verifyBeacon: (input: {
|
|
|
253
325
|
now?: number;
|
|
254
326
|
}) => Promise<VerifiedBeacon>;
|
|
255
327
|
|
|
328
|
+
interface VerifiedCountersignature {
|
|
329
|
+
/** CID of this countersign operation (distinct from the target) */
|
|
330
|
+
countersignCID: string;
|
|
331
|
+
/** The witness DID (payload.did — the DID that signed this attestation) */
|
|
332
|
+
witnessDID: string;
|
|
333
|
+
/** The CID being attested to */
|
|
334
|
+
targetCID: string;
|
|
335
|
+
}
|
|
256
336
|
/**
|
|
257
|
-
* Sign
|
|
258
|
-
*
|
|
259
|
-
* The witness signs the same payload as the author, producing a different
|
|
260
|
-
* JWS token with their own kid. The CID is identical because the payload
|
|
261
|
-
* is identical.
|
|
337
|
+
* Sign a countersignature attesting to a target operation by CID
|
|
262
338
|
*/
|
|
263
339
|
declare const signCountersignature: (input: {
|
|
264
|
-
|
|
265
|
-
operationPayload: ContentOperation;
|
|
266
|
-
/** Witness signer */
|
|
340
|
+
payload: CountersignPayload;
|
|
267
341
|
signer: Signer;
|
|
268
|
-
/** Witness kid — DID URL of the witness (must differ from payload.did) */
|
|
269
342
|
kid: string;
|
|
270
343
|
}) => Promise<{
|
|
271
344
|
jwsToken: string;
|
|
272
|
-
|
|
345
|
+
countersignCID: string;
|
|
273
346
|
}>;
|
|
274
|
-
interface VerifiedCountersignature {
|
|
275
|
-
operationCID: string;
|
|
276
|
-
/** The DID that authored the operation (payload.did) */
|
|
277
|
-
authorDID: string;
|
|
278
|
-
/** The DID that witnessed the operation (kid DID) */
|
|
279
|
-
witnessDID: string;
|
|
280
|
-
}
|
|
281
347
|
/**
|
|
282
|
-
* Verify a countersignature JWS
|
|
348
|
+
* Verify a countersignature JWS — stateless verification
|
|
283
349
|
*
|
|
284
|
-
* Checks: valid signature, CID
|
|
350
|
+
* Checks: valid signature, CID integrity, payload schema. Does NOT check
|
|
351
|
+
* whether the target exists or whether the witness differs from the target
|
|
352
|
+
* author — those are relay-level semantic checks.
|
|
285
353
|
*/
|
|
286
354
|
declare const verifyCountersignature: (input: {
|
|
287
355
|
jwsToken: string;
|
|
288
|
-
expectedCID: string;
|
|
289
356
|
resolveKey: (kid: string) => Promise<Uint8Array>;
|
|
290
357
|
}) => Promise<VerifiedCountersignature>;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
/** The DID that witnessed the beacon (kid DID) */
|
|
296
|
-
witnessDID: string;
|
|
358
|
+
|
|
359
|
+
interface VerifiedArtifact {
|
|
360
|
+
payload: ArtifactPayload;
|
|
361
|
+
artifactCID: string;
|
|
297
362
|
}
|
|
298
363
|
/**
|
|
299
|
-
*
|
|
364
|
+
* Sign an artifact as a JWS
|
|
300
365
|
*
|
|
301
|
-
*
|
|
366
|
+
* Enforces the protocol size limit on the CBOR-encoded payload.
|
|
367
|
+
*/
|
|
368
|
+
declare const signArtifact: (input: {
|
|
369
|
+
payload: ArtifactPayload;
|
|
370
|
+
signer: Signer;
|
|
371
|
+
kid: string;
|
|
372
|
+
}) => Promise<{
|
|
373
|
+
jwsToken: string;
|
|
374
|
+
artifactCID: string;
|
|
375
|
+
}>;
|
|
376
|
+
/**
|
|
377
|
+
* Verify an artifact JWS — signature, CID, payload schema, size limit
|
|
302
378
|
*/
|
|
303
|
-
declare const
|
|
379
|
+
declare const verifyArtifact: (input: {
|
|
304
380
|
jwsToken: string;
|
|
305
|
-
expectedCID: string;
|
|
306
381
|
resolveKey: (kid: string) => Promise<Uint8Array>;
|
|
307
|
-
}) => Promise<
|
|
382
|
+
}) => Promise<VerifiedArtifact>;
|
|
308
383
|
|
|
309
|
-
export { BeaconPayload, ContentOperation, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MultikeyPublicKey, type Signer, type
|
|
384
|
+
export { ArtifactPayload, BeaconPayload, ContentOperation, CountersignPayload, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MAX_ARTIFACT_PAYLOAD_SIZE, MultikeyPublicKey, type Signer, type VerifiedArtifact, type VerifiedBeacon, type VerifiedContentChain, type VerifiedCountersignature, VerifiedIdentity, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, signArtifact, signBeacon, signContentOperation, signCountersignature, signIdentityOperation, verifyArtifact, verifyBeacon, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyIdentityChain, verifyIdentityExtensionFromTrustedState };
|
package/dist/chain/index.js
CHANGED
|
@@ -1,46 +1,58 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ArtifactPayload,
|
|
2
3
|
BeaconPayload,
|
|
3
4
|
ContentOperation,
|
|
5
|
+
CountersignPayload,
|
|
4
6
|
ED25519_PRIV_MULTICODEC,
|
|
5
7
|
ED25519_PUB_MULTICODEC,
|
|
6
8
|
IdentityOperation,
|
|
9
|
+
MAX_ARTIFACT_PAYLOAD_SIZE,
|
|
7
10
|
MultikeyPublicKey,
|
|
8
11
|
VerifiedIdentity,
|
|
9
12
|
decodeMultikey,
|
|
10
13
|
deriveChainIdentifier,
|
|
11
14
|
deriveContentId,
|
|
12
15
|
encodeEd25519Multikey,
|
|
16
|
+
signArtifact,
|
|
13
17
|
signBeacon,
|
|
14
18
|
signContentOperation,
|
|
15
19
|
signCountersignature,
|
|
16
20
|
signIdentityOperation,
|
|
21
|
+
verifyArtifact,
|
|
17
22
|
verifyBeacon,
|
|
18
|
-
verifyBeaconCountersignature,
|
|
19
23
|
verifyContentChain,
|
|
24
|
+
verifyContentExtensionFromTrustedState,
|
|
20
25
|
verifyCountersignature,
|
|
21
|
-
verifyIdentityChain
|
|
22
|
-
|
|
26
|
+
verifyIdentityChain,
|
|
27
|
+
verifyIdentityExtensionFromTrustedState
|
|
28
|
+
} from "../chunk-QKHP7UVL.js";
|
|
23
29
|
import "../chunk-CZSEEZLL.js";
|
|
24
30
|
import "../chunk-ZXXP5W5N.js";
|
|
25
31
|
export {
|
|
32
|
+
ArtifactPayload,
|
|
26
33
|
BeaconPayload,
|
|
27
34
|
ContentOperation,
|
|
35
|
+
CountersignPayload,
|
|
28
36
|
ED25519_PRIV_MULTICODEC,
|
|
29
37
|
ED25519_PUB_MULTICODEC,
|
|
30
38
|
IdentityOperation,
|
|
39
|
+
MAX_ARTIFACT_PAYLOAD_SIZE,
|
|
31
40
|
MultikeyPublicKey,
|
|
32
41
|
VerifiedIdentity,
|
|
33
42
|
decodeMultikey,
|
|
34
43
|
deriveChainIdentifier,
|
|
35
44
|
deriveContentId,
|
|
36
45
|
encodeEd25519Multikey,
|
|
46
|
+
signArtifact,
|
|
37
47
|
signBeacon,
|
|
38
48
|
signContentOperation,
|
|
39
49
|
signCountersignature,
|
|
40
50
|
signIdentityOperation,
|
|
51
|
+
verifyArtifact,
|
|
41
52
|
verifyBeacon,
|
|
42
|
-
verifyBeaconCountersignature,
|
|
43
53
|
verifyContentChain,
|
|
54
|
+
verifyContentExtensionFromTrustedState,
|
|
44
55
|
verifyCountersignature,
|
|
45
|
-
verifyIdentityChain
|
|
56
|
+
verifyIdentityChain,
|
|
57
|
+
verifyIdentityExtensionFromTrustedState
|
|
46
58
|
};
|
|
@@ -104,6 +104,23 @@ var BeaconPayload = z.strictObject({
|
|
|
104
104
|
merkleRoot: z.string().regex(/^[0-9a-f]{64}$/),
|
|
105
105
|
createdAt: Iso8601
|
|
106
106
|
});
|
|
107
|
+
var MAX_SCHEMA = 256;
|
|
108
|
+
var MAX_ARTIFACT_PAYLOAD_SIZE = 16384;
|
|
109
|
+
var ArtifactContent = z.object({ $schema: z.string().max(MAX_SCHEMA) }).catchall(z.unknown());
|
|
110
|
+
var ArtifactPayload = z.strictObject({
|
|
111
|
+
version: z.literal(1),
|
|
112
|
+
type: z.literal("artifact"),
|
|
113
|
+
did: z.string().max(MAX_DID),
|
|
114
|
+
content: ArtifactContent,
|
|
115
|
+
createdAt: Iso8601
|
|
116
|
+
});
|
|
117
|
+
var CountersignPayload = z.strictObject({
|
|
118
|
+
version: z.literal(1),
|
|
119
|
+
type: z.literal("countersign"),
|
|
120
|
+
did: z.string().max(MAX_DID),
|
|
121
|
+
targetCID: CIDString,
|
|
122
|
+
createdAt: Iso8601
|
|
123
|
+
});
|
|
107
124
|
|
|
108
125
|
// src/chain/multikey.ts
|
|
109
126
|
import { base58btc } from "multiformats/bases/base58";
|
|
@@ -299,6 +316,78 @@ var verifyIdentityChain = async (input) => {
|
|
|
299
316
|
controllerKeys: state.controllerKeys
|
|
300
317
|
};
|
|
301
318
|
};
|
|
319
|
+
var verifyIdentityExtensionFromTrustedState = async (input) => {
|
|
320
|
+
const { currentState, headCID, lastCreatedAt, newOp } = input;
|
|
321
|
+
if (currentState.isDeleted) {
|
|
322
|
+
throw new Error("cannot extend a deleted identity");
|
|
323
|
+
}
|
|
324
|
+
const decoded = decodeJwsUnsafe(newOp);
|
|
325
|
+
if (!decoded) throw new Error("failed to decode JWS");
|
|
326
|
+
const result = IdentityOperation.safeParse(decoded.payload);
|
|
327
|
+
if (!result.success) {
|
|
328
|
+
const messages = result.error.issues.map((e) => e.message).join(", ");
|
|
329
|
+
throw new Error(messages);
|
|
330
|
+
}
|
|
331
|
+
const op = result.data;
|
|
332
|
+
if (decoded.header.typ !== "did:dfos:identity-op") {
|
|
333
|
+
throw new Error(`invalid typ: ${decoded.header.typ}`);
|
|
334
|
+
}
|
|
335
|
+
if (op.type === "create") {
|
|
336
|
+
throw new Error("extension cannot be a create operation");
|
|
337
|
+
}
|
|
338
|
+
if (op.previousOperationCID !== headCID) {
|
|
339
|
+
throw new Error("previousCID is incorrect");
|
|
340
|
+
}
|
|
341
|
+
if (op.createdAt <= lastCreatedAt) {
|
|
342
|
+
throw new Error("createdAt must be after last op");
|
|
343
|
+
}
|
|
344
|
+
const encoded = await dagCborCanonicalEncode(op);
|
|
345
|
+
const operationCID = encoded.cid.toString();
|
|
346
|
+
if (!decoded.header.cid) throw new Error("missing cid in protected header");
|
|
347
|
+
if (decoded.header.cid !== operationCID) throw new Error("cid mismatch in protected header");
|
|
348
|
+
const kid = decoded.header.kid;
|
|
349
|
+
if (!kid.includes("#")) {
|
|
350
|
+
throw new Error("non-genesis op kid must be DID URL, got bare key ID");
|
|
351
|
+
}
|
|
352
|
+
const hashIdx = kid.indexOf("#");
|
|
353
|
+
const signingKeyId = kid.substring(hashIdx + 1);
|
|
354
|
+
const kidDid = kid.substring(0, hashIdx);
|
|
355
|
+
if (kidDid !== currentState.did) {
|
|
356
|
+
throw new Error("kid DID does not match identity DID");
|
|
357
|
+
}
|
|
358
|
+
const signingKey = currentState.controllerKeys.find((k) => k.id === signingKeyId);
|
|
359
|
+
if (!signingKey) {
|
|
360
|
+
throw new Error(`kid references unknown key: ${signingKeyId}`);
|
|
361
|
+
}
|
|
362
|
+
const { keyBytes } = decodeMultikey(signingKey.publicKeyMultibase);
|
|
363
|
+
try {
|
|
364
|
+
verifyJws({ token: newOp, publicKey: keyBytes });
|
|
365
|
+
} catch {
|
|
366
|
+
throw new Error("invalid signature");
|
|
367
|
+
}
|
|
368
|
+
if (op.type === "update") {
|
|
369
|
+
[op.authKeys, op.assertKeys, op.controllerKeys].forEach((keys) => {
|
|
370
|
+
const set = new Set(keys.map((k) => k.id));
|
|
371
|
+
if (set.size !== keys.length) {
|
|
372
|
+
throw new Error("cannot repeat key ids in same usage");
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
const newState = op.type === "update" ? {
|
|
377
|
+
did: currentState.did,
|
|
378
|
+
isDeleted: false,
|
|
379
|
+
authKeys: op.authKeys,
|
|
380
|
+
assertKeys: op.assertKeys,
|
|
381
|
+
controllerKeys: op.controllerKeys
|
|
382
|
+
} : {
|
|
383
|
+
did: currentState.did,
|
|
384
|
+
isDeleted: true,
|
|
385
|
+
authKeys: currentState.authKeys,
|
|
386
|
+
assertKeys: currentState.assertKeys,
|
|
387
|
+
controllerKeys: currentState.controllerKeys
|
|
388
|
+
};
|
|
389
|
+
return { state: newState, operationCID, createdAt: op.createdAt };
|
|
390
|
+
};
|
|
302
391
|
|
|
303
392
|
// src/chain/content-chain.ts
|
|
304
393
|
var signContentOperation = async (input) => {
|
|
@@ -447,6 +536,98 @@ var verifyContentChain = async (input) => {
|
|
|
447
536
|
creatorDID: state.creatorDID
|
|
448
537
|
};
|
|
449
538
|
};
|
|
539
|
+
var verifyContentExtensionFromTrustedState = async (input) => {
|
|
540
|
+
const { currentState, lastCreatedAt, newOp, resolveKey } = input;
|
|
541
|
+
if (currentState.isDeleted) {
|
|
542
|
+
throw new Error("cannot extend a deleted chain");
|
|
543
|
+
}
|
|
544
|
+
const decoded = decodeJwsUnsafe(newOp);
|
|
545
|
+
if (!decoded) throw new Error("failed to decode JWS");
|
|
546
|
+
const result = ContentOperation.safeParse(decoded.payload);
|
|
547
|
+
if (!result.success) {
|
|
548
|
+
const messages = result.error.issues.map((e) => e.message).join(", ");
|
|
549
|
+
throw new Error(messages);
|
|
550
|
+
}
|
|
551
|
+
const op = result.data;
|
|
552
|
+
if (decoded.header.typ !== "did:dfos:content-op") {
|
|
553
|
+
throw new Error(`invalid typ: ${decoded.header.typ}`);
|
|
554
|
+
}
|
|
555
|
+
if (op.type === "create") {
|
|
556
|
+
throw new Error("extension cannot be a create operation");
|
|
557
|
+
}
|
|
558
|
+
if (op.previousOperationCID !== currentState.headCID) {
|
|
559
|
+
throw new Error("previousOperationCID is incorrect");
|
|
560
|
+
}
|
|
561
|
+
if (op.createdAt <= lastCreatedAt) {
|
|
562
|
+
throw new Error("createdAt must be after last op");
|
|
563
|
+
}
|
|
564
|
+
const kid = decoded.header.kid;
|
|
565
|
+
const hashIdx = kid.indexOf("#");
|
|
566
|
+
if (hashIdx < 0) throw new Error("kid must be a DID URL");
|
|
567
|
+
const kidDid = kid.substring(0, hashIdx);
|
|
568
|
+
if (kidDid !== op.did) {
|
|
569
|
+
throw new Error("kid DID does not match operation did");
|
|
570
|
+
}
|
|
571
|
+
const publicKey = await resolveKey(kid);
|
|
572
|
+
try {
|
|
573
|
+
verifyJws({ token: newOp, publicKey });
|
|
574
|
+
} catch {
|
|
575
|
+
throw new Error("invalid signature");
|
|
576
|
+
}
|
|
577
|
+
if (op.did !== currentState.creatorDID && input.enforceAuthorization) {
|
|
578
|
+
const authorization = op.authorization;
|
|
579
|
+
if (!authorization) {
|
|
580
|
+
throw new Error(`signer ${op.did} is not the chain creator \u2014 authorization VC required`);
|
|
581
|
+
}
|
|
582
|
+
const vcDecoded = decodeCredentialUnsafe(authorization);
|
|
583
|
+
if (!vcDecoded) throw new Error("failed to decode authorization VC");
|
|
584
|
+
const vcKid = vcDecoded.header.kid;
|
|
585
|
+
if (!vcKid || !vcKid.includes("#")) {
|
|
586
|
+
throw new Error("authorization VC kid must be a DID URL");
|
|
587
|
+
}
|
|
588
|
+
let creatorPublicKey;
|
|
589
|
+
try {
|
|
590
|
+
creatorPublicKey = await resolveKey(vcKid);
|
|
591
|
+
} catch {
|
|
592
|
+
throw new Error("cannot resolve creator key for authorization verification");
|
|
593
|
+
}
|
|
594
|
+
const opCreatedAtUnix = Math.floor(new Date(op.createdAt).getTime() / 1e3);
|
|
595
|
+
try {
|
|
596
|
+
const credential = verifyCredential({
|
|
597
|
+
token: authorization,
|
|
598
|
+
publicKey: creatorPublicKey,
|
|
599
|
+
subject: op.did,
|
|
600
|
+
expectedType: VC_TYPE_CONTENT_WRITE,
|
|
601
|
+
currentTime: opCreatedAtUnix
|
|
602
|
+
});
|
|
603
|
+
if (credential.iss !== currentState.creatorDID) {
|
|
604
|
+
throw new Error("VC issuer is not the chain creator");
|
|
605
|
+
}
|
|
606
|
+
if (credential.contentId && credential.contentId !== currentState.contentId) {
|
|
607
|
+
throw new Error(
|
|
608
|
+
`VC contentId ${credential.contentId} does not match chain ${currentState.contentId}`
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
} catch (err) {
|
|
612
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
613
|
+
throw new Error(`authorization verification failed: ${message}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
const encoded = await dagCborCanonicalEncode(op);
|
|
617
|
+
const operationCID = encoded.cid.toString();
|
|
618
|
+
if (!decoded.header.cid) throw new Error("missing cid in protected header");
|
|
619
|
+
if (decoded.header.cid !== operationCID) throw new Error("cid mismatch in protected header");
|
|
620
|
+
const newState = {
|
|
621
|
+
contentId: currentState.contentId,
|
|
622
|
+
genesisCID: currentState.genesisCID,
|
|
623
|
+
headCID: operationCID,
|
|
624
|
+
isDeleted: op.type === "delete",
|
|
625
|
+
currentDocumentCID: op.type === "update" ? op.documentCID : null,
|
|
626
|
+
length: currentState.length + 1,
|
|
627
|
+
creatorDID: currentState.creatorDID
|
|
628
|
+
};
|
|
629
|
+
return { state: newState, operationCID, createdAt: op.createdAt };
|
|
630
|
+
};
|
|
450
631
|
|
|
451
632
|
// src/chain/beacon.ts
|
|
452
633
|
var signBeacon = async (input) => {
|
|
@@ -499,92 +680,102 @@ var verifyBeacon = async (input) => {
|
|
|
499
680
|
|
|
500
681
|
// src/chain/countersign.ts
|
|
501
682
|
var signCountersignature = async (input) => {
|
|
502
|
-
const encoded = await dagCborCanonicalEncode(input.
|
|
503
|
-
const
|
|
683
|
+
const encoded = await dagCborCanonicalEncode(input.payload);
|
|
684
|
+
const countersignCID = encoded.cid.toString();
|
|
504
685
|
const jwsToken = await createJws({
|
|
505
|
-
header: { alg: "EdDSA", typ: "did:dfos:
|
|
506
|
-
payload: input.
|
|
686
|
+
header: { alg: "EdDSA", typ: "did:dfos:countersign", kid: input.kid, cid: countersignCID },
|
|
687
|
+
payload: input.payload,
|
|
507
688
|
sign: input.signer
|
|
508
689
|
});
|
|
509
|
-
return { jwsToken,
|
|
690
|
+
return { jwsToken, countersignCID };
|
|
510
691
|
};
|
|
511
692
|
var verifyCountersignature = async (input) => {
|
|
512
693
|
const decoded = decodeJwsUnsafe(input.jwsToken);
|
|
513
694
|
if (!decoded) throw new Error("failed to decode countersignature JWS");
|
|
514
|
-
if (decoded.header.typ !== "did:dfos:
|
|
695
|
+
if (decoded.header.typ !== "did:dfos:countersign") {
|
|
515
696
|
throw new Error(`invalid countersignature typ: ${decoded.header.typ}`);
|
|
516
697
|
}
|
|
517
|
-
const result =
|
|
698
|
+
const result = CountersignPayload.safeParse(decoded.payload);
|
|
518
699
|
if (!result.success) {
|
|
519
700
|
const messages = result.error.issues.map((e) => e.message).join(", ");
|
|
520
|
-
throw new Error(`invalid
|
|
521
|
-
}
|
|
522
|
-
const op = result.data;
|
|
523
|
-
const encoded = await dagCborCanonicalEncode(op);
|
|
524
|
-
const operationCID = encoded.cid.toString();
|
|
525
|
-
if (operationCID !== input.expectedCID) {
|
|
526
|
-
throw new Error("countersignature CID does not match expected CID");
|
|
527
|
-
}
|
|
528
|
-
if (decoded.header.cid !== operationCID) {
|
|
529
|
-
throw new Error("countersignature header cid mismatch");
|
|
701
|
+
throw new Error(`invalid countersignature payload: ${messages}`);
|
|
530
702
|
}
|
|
703
|
+
const payload = result.data;
|
|
531
704
|
const kid = decoded.header.kid;
|
|
705
|
+
const hashIdx = kid.indexOf("#");
|
|
706
|
+
if (hashIdx < 0) throw new Error("countersignature kid must be a DID URL");
|
|
707
|
+
const kidDid = kid.substring(0, hashIdx);
|
|
708
|
+
if (kidDid !== payload.did) {
|
|
709
|
+
throw new Error("countersignature kid DID does not match payload did");
|
|
710
|
+
}
|
|
532
711
|
const publicKey = await input.resolveKey(kid);
|
|
533
712
|
try {
|
|
534
713
|
verifyJws({ token: input.jwsToken, publicKey });
|
|
535
714
|
} catch {
|
|
536
|
-
throw new Error("invalid countersignature");
|
|
537
|
-
}
|
|
538
|
-
const hashIdx = kid.indexOf("#");
|
|
539
|
-
if (hashIdx < 0) throw new Error("countersignature kid must be a DID URL");
|
|
540
|
-
const witnessDID = kid.substring(0, hashIdx);
|
|
541
|
-
if (witnessDID === op.did) {
|
|
542
|
-
throw new Error("countersignature kid DID must differ from operation did (not a witness)");
|
|
715
|
+
throw new Error("invalid countersignature signature");
|
|
543
716
|
}
|
|
717
|
+
const encoded = await dagCborCanonicalEncode(decoded.payload);
|
|
718
|
+
const countersignCID = encoded.cid.toString();
|
|
719
|
+
if (!decoded.header.cid) throw new Error("missing cid in countersignature header");
|
|
720
|
+
if (decoded.header.cid !== countersignCID) throw new Error("countersignature cid mismatch");
|
|
544
721
|
return {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
722
|
+
countersignCID,
|
|
723
|
+
witnessDID: payload.did,
|
|
724
|
+
targetCID: payload.targetCID
|
|
548
725
|
};
|
|
549
726
|
};
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
727
|
+
|
|
728
|
+
// src/chain/artifact.ts
|
|
729
|
+
var signArtifact = async (input) => {
|
|
730
|
+
const encoded = await dagCborCanonicalEncode(input.payload);
|
|
731
|
+
const artifactCID = encoded.cid.toString();
|
|
732
|
+
if (encoded.bytes.length > MAX_ARTIFACT_PAYLOAD_SIZE) {
|
|
733
|
+
throw new Error(
|
|
734
|
+
`artifact payload exceeds max size: ${encoded.bytes.length} > ${MAX_ARTIFACT_PAYLOAD_SIZE}`
|
|
735
|
+
);
|
|
555
736
|
}
|
|
556
|
-
const
|
|
737
|
+
const jwsToken = await createJws({
|
|
738
|
+
header: { alg: "EdDSA", typ: "did:dfos:artifact", kid: input.kid, cid: artifactCID },
|
|
739
|
+
payload: input.payload,
|
|
740
|
+
sign: input.signer
|
|
741
|
+
});
|
|
742
|
+
return { jwsToken, artifactCID };
|
|
743
|
+
};
|
|
744
|
+
var verifyArtifact = async (input) => {
|
|
745
|
+
const decoded = decodeJwsUnsafe(input.jwsToken);
|
|
746
|
+
if (!decoded) throw new Error("failed to decode artifact JWS");
|
|
747
|
+
const result = ArtifactPayload.safeParse(decoded.payload);
|
|
557
748
|
if (!result.success) {
|
|
558
749
|
const messages = result.error.issues.map((e) => e.message).join(", ");
|
|
559
|
-
throw new Error(`invalid
|
|
750
|
+
throw new Error(`invalid artifact payload: ${messages}`);
|
|
560
751
|
}
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if (beaconCID !== input.expectedCID) {
|
|
565
|
-
throw new Error("beacon countersignature CID does not match expected CID");
|
|
566
|
-
}
|
|
567
|
-
if (decoded.header.cid !== beaconCID) {
|
|
568
|
-
throw new Error("beacon countersignature header cid mismatch");
|
|
752
|
+
const payload = result.data;
|
|
753
|
+
if (decoded.header.typ !== "did:dfos:artifact") {
|
|
754
|
+
throw new Error(`invalid artifact typ: ${decoded.header.typ}`);
|
|
569
755
|
}
|
|
570
756
|
const kid = decoded.header.kid;
|
|
757
|
+
const hashIdx = kid.indexOf("#");
|
|
758
|
+
if (hashIdx < 0) throw new Error("artifact kid must be a DID URL");
|
|
759
|
+
const kidDid = kid.substring(0, hashIdx);
|
|
760
|
+
if (kidDid !== payload.did) {
|
|
761
|
+
throw new Error("artifact kid DID does not match payload did");
|
|
762
|
+
}
|
|
571
763
|
const publicKey = await input.resolveKey(kid);
|
|
572
764
|
try {
|
|
573
765
|
verifyJws({ token: input.jwsToken, publicKey });
|
|
574
766
|
} catch {
|
|
575
|
-
throw new Error("invalid
|
|
767
|
+
throw new Error("invalid artifact signature");
|
|
576
768
|
}
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
if (
|
|
581
|
-
|
|
769
|
+
const encoded = await dagCborCanonicalEncode(decoded.payload);
|
|
770
|
+
const artifactCID = encoded.cid.toString();
|
|
771
|
+
if (!decoded.header.cid) throw new Error("missing cid in artifact header");
|
|
772
|
+
if (decoded.header.cid !== artifactCID) throw new Error("artifact cid mismatch");
|
|
773
|
+
if (encoded.bytes.length > MAX_ARTIFACT_PAYLOAD_SIZE) {
|
|
774
|
+
throw new Error(
|
|
775
|
+
`artifact payload exceeds max size: ${encoded.bytes.length} > ${MAX_ARTIFACT_PAYLOAD_SIZE}`
|
|
776
|
+
);
|
|
582
777
|
}
|
|
583
|
-
return {
|
|
584
|
-
beaconCID,
|
|
585
|
-
controllerDID: beacon.did,
|
|
586
|
-
witnessDID
|
|
587
|
-
};
|
|
778
|
+
return { payload, artifactCID };
|
|
588
779
|
};
|
|
589
780
|
|
|
590
781
|
export {
|
|
@@ -593,6 +784,9 @@ export {
|
|
|
593
784
|
VerifiedIdentity,
|
|
594
785
|
ContentOperation,
|
|
595
786
|
BeaconPayload,
|
|
787
|
+
MAX_ARTIFACT_PAYLOAD_SIZE,
|
|
788
|
+
ArtifactPayload,
|
|
789
|
+
CountersignPayload,
|
|
596
790
|
ED25519_PUB_MULTICODEC,
|
|
597
791
|
ED25519_PRIV_MULTICODEC,
|
|
598
792
|
encodeEd25519Multikey,
|
|
@@ -601,11 +795,14 @@ export {
|
|
|
601
795
|
deriveContentId,
|
|
602
796
|
signIdentityOperation,
|
|
603
797
|
verifyIdentityChain,
|
|
798
|
+
verifyIdentityExtensionFromTrustedState,
|
|
604
799
|
signContentOperation,
|
|
605
800
|
verifyContentChain,
|
|
801
|
+
verifyContentExtensionFromTrustedState,
|
|
606
802
|
signBeacon,
|
|
607
803
|
verifyBeacon,
|
|
608
804
|
signCountersignature,
|
|
609
805
|
verifyCountersignature,
|
|
610
|
-
|
|
806
|
+
signArtifact,
|
|
807
|
+
verifyArtifact
|
|
611
808
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { JwsHeader, JwsVerificationError, JwtClaims, JwtCreateOptions, JwtHeader, JwtVerificationError, JwtVerifyOptions, PrefixedID, base64urlDecode, base64urlEncode, createJws, createJwt, createNewEd25519Keypair, dagCborCanonicalEncode, decodeJwsUnsafe, decodeJwtUnsafe, generateId, generateIdNoPrefix, importEd25519Keypair, isCanonicallyEqual, isValidEd25519Signature, isValidId, normalizedId, parseDagCborCID, signPayloadEd25519, verifyJws, verifyJwt } from './crypto/index.js';
|
|
2
|
-
export { BeaconPayload, ContentOperation, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MultikeyPublicKey, Signer,
|
|
2
|
+
export { ArtifactPayload, BeaconPayload, ContentOperation, CountersignPayload, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, MAX_ARTIFACT_PAYLOAD_SIZE, MultikeyPublicKey, Signer, VerifiedArtifact, VerifiedBeacon, VerifiedContentChain, VerifiedCountersignature, VerifiedIdentity, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, signArtifact, signBeacon, signContentOperation, signCountersignature, signIdentityOperation, verifyArtifact, verifyBeacon, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyIdentityChain, verifyIdentityExtensionFromTrustedState } from './chain/index.js';
|
|
3
3
|
export { MerkleProof, buildMerkleTree, generateMerkleProof, hashLeaf, hexToBytes, verifyMerkleProof } from './merkle/index.js';
|
|
4
4
|
export { AuthTokenClaims, AuthTokenCreateOptions, AuthTokenVerificationError, AuthTokenVerifyOptions, ContentReadSubject, ContentWriteSubject, CredentialClaims, CredentialCreateOptions, CredentialVerificationError, CredentialVerifyOptions, DFOSCredentialType, VCClaim, VC_TYPE_CONTENT_READ, VC_TYPE_CONTENT_WRITE, VerifiedAuthToken, VerifiedCredential, createAuthToken, createCredential, decodeCredentialUnsafe, verifyAuthToken, verifyCredential } from './credentials/index.js';
|
|
5
5
|
import 'multiformats';
|
package/dist/index.js
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ArtifactPayload,
|
|
2
3
|
BeaconPayload,
|
|
3
4
|
ContentOperation,
|
|
5
|
+
CountersignPayload,
|
|
4
6
|
ED25519_PRIV_MULTICODEC,
|
|
5
7
|
ED25519_PUB_MULTICODEC,
|
|
6
8
|
IdentityOperation,
|
|
9
|
+
MAX_ARTIFACT_PAYLOAD_SIZE,
|
|
7
10
|
MultikeyPublicKey,
|
|
8
11
|
VerifiedIdentity,
|
|
9
12
|
decodeMultikey,
|
|
10
13
|
deriveChainIdentifier,
|
|
11
14
|
deriveContentId,
|
|
12
15
|
encodeEd25519Multikey,
|
|
16
|
+
signArtifact,
|
|
13
17
|
signBeacon,
|
|
14
18
|
signContentOperation,
|
|
15
19
|
signCountersignature,
|
|
16
20
|
signIdentityOperation,
|
|
21
|
+
verifyArtifact,
|
|
17
22
|
verifyBeacon,
|
|
18
|
-
verifyBeaconCountersignature,
|
|
19
23
|
verifyContentChain,
|
|
24
|
+
verifyContentExtensionFromTrustedState,
|
|
20
25
|
verifyCountersignature,
|
|
21
|
-
verifyIdentityChain
|
|
22
|
-
|
|
26
|
+
verifyIdentityChain,
|
|
27
|
+
verifyIdentityExtensionFromTrustedState
|
|
28
|
+
} from "./chunk-QKHP7UVL.js";
|
|
23
29
|
import {
|
|
24
30
|
buildMerkleTree,
|
|
25
31
|
generateMerkleProof,
|
|
@@ -68,12 +74,14 @@ import {
|
|
|
68
74
|
verifyJwt
|
|
69
75
|
} from "./chunk-ZXXP5W5N.js";
|
|
70
76
|
export {
|
|
77
|
+
ArtifactPayload,
|
|
71
78
|
AuthTokenClaims,
|
|
72
79
|
AuthTokenVerificationError,
|
|
73
80
|
BeaconPayload,
|
|
74
81
|
ContentOperation,
|
|
75
82
|
ContentReadSubject,
|
|
76
83
|
ContentWriteSubject,
|
|
84
|
+
CountersignPayload,
|
|
77
85
|
CredentialClaims,
|
|
78
86
|
CredentialVerificationError,
|
|
79
87
|
DFOSCredentialType,
|
|
@@ -82,6 +90,7 @@ export {
|
|
|
82
90
|
IdentityOperation,
|
|
83
91
|
JwsVerificationError,
|
|
84
92
|
JwtVerificationError,
|
|
93
|
+
MAX_ARTIFACT_PAYLOAD_SIZE,
|
|
85
94
|
MultikeyPublicKey,
|
|
86
95
|
VCClaim,
|
|
87
96
|
VC_TYPE_CONTENT_READ,
|
|
@@ -114,18 +123,21 @@ export {
|
|
|
114
123
|
isValidId,
|
|
115
124
|
normalizedId,
|
|
116
125
|
parseDagCborCID,
|
|
126
|
+
signArtifact,
|
|
117
127
|
signBeacon,
|
|
118
128
|
signContentOperation,
|
|
119
129
|
signCountersignature,
|
|
120
130
|
signIdentityOperation,
|
|
121
131
|
signPayloadEd25519,
|
|
132
|
+
verifyArtifact,
|
|
122
133
|
verifyAuthToken,
|
|
123
134
|
verifyBeacon,
|
|
124
|
-
verifyBeaconCountersignature,
|
|
125
135
|
verifyContentChain,
|
|
136
|
+
verifyContentExtensionFromTrustedState,
|
|
126
137
|
verifyCountersignature,
|
|
127
138
|
verifyCredential,
|
|
128
139
|
verifyIdentityChain,
|
|
140
|
+
verifyIdentityExtensionFromTrustedState,
|
|
129
141
|
verifyJws,
|
|
130
142
|
verifyJwt,
|
|
131
143
|
verifyMerkleProof
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metalabel/dfos-protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DFOS Protocol — Ed25519 signed chain primitives, beacons, merkle trees, and verification",
|
|
6
6
|
"license": "MIT",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"ajv-formats": "^3.0.1",
|
|
70
70
|
"tsup": "^8.5.1",
|
|
71
71
|
"tsx": "^4.21.0",
|
|
72
|
-
"vitest": "^4.0
|
|
72
|
+
"vitest": "^4.1.0"
|
|
73
73
|
},
|
|
74
74
|
"scripts": {
|
|
75
75
|
"build": "tsup",
|