nostr-attestations 0.0.0-development

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TheCryptoDonkey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # nostr-attestations
2
+
3
+ One Nostr event kind for all attestations — credentials, endorsements, vouches, provenance, licensing, and trust.
4
+
5
+ ## Install
6
+
7
+ ```
8
+ npm install nostr-attestations
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { createAttestation, TYPES } from 'nostr-attestations'
15
+
16
+ // Create an unsigned attestation event template
17
+ const event = createAttestation({
18
+ type: TYPES.CREDENTIAL,
19
+ identifier: '<subject-pubkey>',
20
+ subject: '<subject-pubkey>',
21
+ summary: 'Professional credential verified',
22
+ expiration: 1735689600,
23
+ tags: [['profession', 'attorney'], ['jurisdiction', 'US-NY']],
24
+ content: JSON.stringify({ proof: '...' }),
25
+ })
26
+ // => { kind: 31000, tags: [...], content: '...' }
27
+ // Sign with your preferred library and publish to relays
28
+ ```
29
+
30
+ No relay client, no signing library, no crypto. Bring your own. Works with nostr-tools, any Nostr SDK, or bare WebSocket code.
31
+
32
+ ## Why This?
33
+
34
+ Nostr has several ways to label or badge identities, but none designed for verifiable attestations. [NIP-58](https://github.com/nostr-protocol/nips/blob/master/58.md) badges are display-only — no expiry, no revocation, no structured claims. [NIP-85](https://github.com/nostr-protocol/nips/blob/master/85.md) covers social graph metrics, not arbitrary claims. [NIP-32](https://github.com/nostr-protocol/nips/blob/master/32.md) labels are lightweight but not individually replaceable per subject.
35
+
36
+ nostr-attestations uses **one kind (31000) with a `type` tag** instead of inventing a new event kind for every attestation use case. Credentials, endorsements, vouches, licensing, provenance, and revocations all share the same event structure. New attestation types need zero protocol changes — just define a new `type` value.
37
+
38
+ ## Revocation
39
+
40
+ ```typescript
41
+ import { createRevocation, isRevoked, parseAttestation } from 'nostr-attestations'
42
+
43
+ // Revoke a previously issued attestation
44
+ const revocation = createRevocation({
45
+ type: 'credential',
46
+ identifier: '<subject-pubkey>',
47
+ subject: '<subject-pubkey>',
48
+ reason: 'licence-expired',
49
+ effective: 1704067200,
50
+ })
51
+ // Publish — addressable event semantics replace the original
52
+
53
+ // Check if a fetched event is revoked
54
+ const revoked = isRevoked(event) // true if ["status", "revoked"] tag present
55
+ ```
56
+
57
+ ## Parsing
58
+
59
+ ```typescript
60
+ import { parseAttestation } from 'nostr-attestations'
61
+
62
+ const attestation = parseAttestation(event)
63
+ // {
64
+ // kind: 31000,
65
+ // type: 'credential',
66
+ // pubkey: '<attestor-pubkey>',
67
+ // createdAt: 1700000000,
68
+ // identifier: '<subject-pubkey>',
69
+ // subject: '<subject-pubkey>',
70
+ // summary: 'Professional credential verified',
71
+ // expiration: 1735689600,
72
+ // revoked: false,
73
+ // reason: null,
74
+ // tags: [...],
75
+ // content: '...',
76
+ // }
77
+ ```
78
+
79
+ ## API Reference
80
+
81
+ ### Builders
82
+
83
+ | Function | Signature | Returns |
84
+ |----------|-----------|---------|
85
+ | `createAttestation` | `(params: AttestationParams) => EventTemplate` | Unsigned attestation event |
86
+ | `createRevocation` | `(params: RevocationParams) => EventTemplate` | Unsigned revocation event |
87
+
88
+ ### Parsers
89
+
90
+ | Function | Signature | Returns |
91
+ |----------|-----------|---------|
92
+ | `parseAttestation` | `(event: NostrEvent) => Attestation` | Typed attestation data |
93
+ | `isRevoked` | `(event: NostrEvent) => boolean` | True if event has `["status", "revoked"]` |
94
+
95
+ ### Validators
96
+
97
+ | Function | Signature | Returns |
98
+ |----------|-----------|---------|
99
+ | `validateAttestation` | `(event: NostrEvent) => ValidationResult` | `{ valid: boolean, errors: string[] }` |
100
+
101
+ ### Filters
102
+
103
+ | Function | Signature | Returns |
104
+ |----------|-----------|---------|
105
+ | `attestationFilter` | `(params: FilterParams) => NostrFilter` | Relay query filter |
106
+ | `revocationFilter` | `(type: string, identifier: string) => NostrFilter` | Revocation check filter |
107
+ | `buildDTag` | `(type: string, identifier: string) => string` | `"type:identifier"` string |
108
+ | `parseDTag` | `(dTag: string) => { type: string; identifier: string } \| null` | Parsed d-tag |
109
+
110
+ ### Constants
111
+
112
+ | Export | Value | Description |
113
+ |--------|-------|-------------|
114
+ | `ATTESTATION_KIND` | `31000` | NIP-VA event kind |
115
+ | `TYPES` | `{ CREDENTIAL, ENDORSEMENT, VOUCH, VERIFIER, PROVENANCE }` | Well-known type constants |
116
+
117
+ ### Types
118
+
119
+ `AttestationParams`, `RevocationParams`, `Attestation`, `ValidationResult`, `FilterParams`, `NostrFilter`, `NostrEvent`, `EventTemplate` — all exported from the package root.
120
+
121
+ ## Test Vectors
122
+
123
+ `vectors/attestations.json` contains 10 frozen conformance test vectors covering the full range of attestation types (credential, endorsement, vouch, verifier, provenance) and states (active, revoked, self-attestation). Any conformant implementation must produce identical parse results from these inputs. The vectors are pinned — if tests against them fail, the implementation is broken, not the vector.
124
+
125
+ ## NIP-VA
126
+
127
+ Full protocol specification: [NIP-VA.md](./NIP-VA.md)
128
+
129
+ ## Licence
130
+
131
+ MIT
@@ -0,0 +1,12 @@
1
+ import type { AttestationParams, RevocationParams, EventTemplate } from './types.js';
2
+ /**
3
+ * Create an unsigned attestation event template.
4
+ * The caller is responsible for signing (adding pubkey, id, sig, created_at).
5
+ */
6
+ export declare function createAttestation(params: AttestationParams): EventTemplate;
7
+ /**
8
+ * Create an unsigned revocation event template.
9
+ * When published, this replaces the original attestation via addressable event semantics.
10
+ */
11
+ export declare function createRevocation(params: RevocationParams): EventTemplate;
12
+ //# sourceMappingURL=builders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../src/builders.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAEpF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,aAAa,CAwC1E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,aAAa,CAyBxE"}
@@ -0,0 +1,71 @@
1
+ import { ATTESTATION_KIND } from './constants.js';
2
+ import { buildDTag } from './filters.js';
3
+ /**
4
+ * Create an unsigned attestation event template.
5
+ * The caller is responsible for signing (adding pubkey, id, sig, created_at).
6
+ */
7
+ export function createAttestation(params) {
8
+ if (!params.type)
9
+ throw new Error('type must not be empty');
10
+ if (params.type.includes(':'))
11
+ throw new Error('type must not contain colons');
12
+ const tags = [];
13
+ // d-tag: type:identifier, or type:subject if no identifier, or just type
14
+ const identifier = params.identifier ?? params.subject;
15
+ if (identifier) {
16
+ tags.push(['d', buildDTag(params.type, identifier)]);
17
+ }
18
+ else {
19
+ tags.push(['d', params.type]);
20
+ }
21
+ tags.push(['type', params.type]);
22
+ if (params.subject) {
23
+ tags.push(['p', params.subject]);
24
+ }
25
+ if (params.summary) {
26
+ tags.push(['summary', params.summary]);
27
+ }
28
+ if (params.expiration != null) {
29
+ if (!Number.isFinite(params.expiration))
30
+ throw new Error('expiration must be a finite number');
31
+ tags.push(['expiration', String(params.expiration)]);
32
+ }
33
+ if (params.tags) {
34
+ for (const tag of params.tags) {
35
+ tags.push(tag);
36
+ }
37
+ }
38
+ return {
39
+ kind: ATTESTATION_KIND,
40
+ tags,
41
+ content: params.content ?? '',
42
+ };
43
+ }
44
+ /**
45
+ * Create an unsigned revocation event template.
46
+ * When published, this replaces the original attestation via addressable event semantics.
47
+ */
48
+ export function createRevocation(params) {
49
+ const tags = [
50
+ ['d', buildDTag(params.type, params.identifier)],
51
+ ['type', params.type],
52
+ ['status', 'revoked'],
53
+ ];
54
+ if (params.subject) {
55
+ tags.push(['p', params.subject]);
56
+ }
57
+ if (params.reason) {
58
+ tags.push(['reason', params.reason]);
59
+ }
60
+ if (params.effective != null) {
61
+ if (!Number.isFinite(params.effective))
62
+ throw new Error('effective must be a finite number');
63
+ tags.push(['effective', String(params.effective)]);
64
+ }
65
+ return {
66
+ kind: ATTESTATION_KIND,
67
+ tags,
68
+ content: '',
69
+ };
70
+ }
71
+ //# sourceMappingURL=builders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builders.js","sourceRoot":"","sources":["../src/builders.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /** NIP-VA Verifiable Attestation event kind (addressable). */
2
+ export declare const ATTESTATION_KIND: 31000;
3
+ /**
4
+ * Well-known attestation type constants.
5
+ * Applications may define their own types beyond these.
6
+ */
7
+ export declare const TYPES: {
8
+ readonly CREDENTIAL: "credential";
9
+ readonly ENDORSEMENT: "endorsement";
10
+ readonly VOUCH: "vouch";
11
+ readonly VERIFIER: "verifier";
12
+ readonly PROVENANCE: "provenance";
13
+ };
14
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,EAAG,KAAc,CAAA;AAE9C;;;GAGG;AACH,eAAO,MAAM,KAAK;;;;;;CAMR,CAAA"}
@@ -0,0 +1,14 @@
1
+ /** NIP-VA Verifiable Attestation event kind (addressable). */
2
+ export const ATTESTATION_KIND = 31000;
3
+ /**
4
+ * Well-known attestation type constants.
5
+ * Applications may define their own types beyond these.
6
+ */
7
+ export const TYPES = {
8
+ CREDENTIAL: 'credential',
9
+ ENDORSEMENT: 'endorsement',
10
+ VOUCH: 'vouch',
11
+ VERIFIER: 'verifier',
12
+ PROVENANCE: 'provenance',
13
+ };
14
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAc,CAAA;AAE9C;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,UAAU,EAAE,YAAY;IACxB,WAAW,EAAE,aAAa;IAC1B,KAAK,EAAE,OAAO;IACd,QAAQ,EAAE,UAAU;IACpB,UAAU,EAAE,YAAY;CAChB,CAAA"}
@@ -0,0 +1,19 @@
1
+ import type { FilterParams, NostrFilter } from './types.js';
2
+ /**
3
+ * Build a d-tag from type and identifier.
4
+ * Type values must not contain colons — the first colon is the delimiter.
5
+ */
6
+ export declare function buildDTag(type: string, identifier: string): string;
7
+ /**
8
+ * Parse a d-tag into type and identifier.
9
+ * Returns null if the d-tag does not contain a colon.
10
+ */
11
+ export declare function parseDTag(dTag: string): {
12
+ type: string;
13
+ identifier: string;
14
+ } | null;
15
+ /** Build a Nostr relay filter for querying attestation events. */
16
+ export declare function attestationFilter(params: FilterParams): NostrFilter;
17
+ /** Build a Nostr relay filter for fetching the latest version of a specific attestation (for revocation checking). */
18
+ export declare function revocationFilter(type: string, identifier: string): NostrFilter;
19
+ //# sourceMappingURL=filters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../src/filters.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE3D;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAIlE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAOnF;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAMnE;AAED,sHAAsH;AACtH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAK9E"}
@@ -0,0 +1,44 @@
1
+ import { ATTESTATION_KIND } from './constants.js';
2
+ /**
3
+ * Build a d-tag from type and identifier.
4
+ * Type values must not contain colons — the first colon is the delimiter.
5
+ */
6
+ export function buildDTag(type, identifier) {
7
+ if (!type)
8
+ throw new Error('type must not be empty');
9
+ if (type.includes(':'))
10
+ throw new Error('type must not contain colons');
11
+ return `${type}:${identifier}`;
12
+ }
13
+ /**
14
+ * Parse a d-tag into type and identifier.
15
+ * Returns null if the d-tag does not contain a colon.
16
+ */
17
+ export function parseDTag(dTag) {
18
+ const idx = dTag.indexOf(':');
19
+ if (idx <= 0)
20
+ return null;
21
+ return {
22
+ type: dTag.slice(0, idx),
23
+ identifier: dTag.slice(idx + 1),
24
+ };
25
+ }
26
+ /** Build a Nostr relay filter for querying attestation events. */
27
+ export function attestationFilter(params) {
28
+ const filter = { kinds: [ATTESTATION_KIND] };
29
+ if (params.authors?.length)
30
+ filter.authors = params.authors;
31
+ if (params.subject)
32
+ filter['#p'] = [params.subject];
33
+ if (params.type)
34
+ filter['#type'] = [params.type];
35
+ return filter;
36
+ }
37
+ /** Build a Nostr relay filter for fetching the latest version of a specific attestation (for revocation checking). */
38
+ export function revocationFilter(type, identifier) {
39
+ return {
40
+ kinds: [ATTESTATION_KIND],
41
+ '#d': [buildDTag(type, identifier)],
42
+ };
43
+ }
44
+ //# sourceMappingURL=filters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filters.js","sourceRoot":"","sources":["../src/filters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAGjD;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,UAAkB;IACxD,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAA;IACpD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAA;IACvE,OAAO,GAAG,IAAI,IAAI,UAAU,EAAE,CAAA;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC7B,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACzB,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;QACxB,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;KAChC,CAAA;AACH,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,iBAAiB,CAAC,MAAoB;IACpD,MAAM,MAAM,GAAgB,EAAE,KAAK,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAA;IACzD,IAAI,MAAM,CAAC,OAAO,EAAE,MAAM;QAAE,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAA;IAC3D,IAAI,MAAM,CAAC,OAAO;QAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACnD,IAAI,MAAM,CAAC,IAAI;QAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAChD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,sHAAsH;AACtH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,UAAkB;IAC/D,OAAO;QACL,KAAK,EAAE,CAAC,gBAAgB,CAAC;QACzB,IAAI,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;KACpC,CAAA;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Find the first tag with the given name. Internal helper — not part of the public API. */
2
+ export declare function findTag(tags: string[][], name: string): string | null;
3
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGrE"}
@@ -0,0 +1,6 @@
1
+ /** Find the first tag with the given name. Internal helper — not part of the public API. */
2
+ export function findTag(tags, name) {
3
+ const tag = tags.find(t => t[0] === name);
4
+ return tag?.[1] ?? null;
5
+ }
6
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,MAAM,UAAU,OAAO,CAAC,IAAgB,EAAE,IAAY;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAA;IACzC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;AACzB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { ATTESTATION_KIND, TYPES } from './constants.js';
2
+ export { createAttestation, createRevocation } from './builders.js';
3
+ export { parseAttestation, isRevoked } from './parsers.js';
4
+ export { validateAttestation } from './validators.js';
5
+ export { buildDTag, parseDTag, attestationFilter, revocationFilter } from './filters.js';
6
+ export type { AttestationParams, RevocationParams, Attestation, ValidationResult, FilterParams, NostrFilter, NostrEvent, EventTemplate, } from './types.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAExD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEnE,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAErD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAExF,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { ATTESTATION_KIND, TYPES } from './constants.js';
2
+ export { createAttestation, createRevocation } from './builders.js';
3
+ export { parseAttestation, isRevoked } from './parsers.js';
4
+ export { validateAttestation } from './validators.js';
5
+ export { buildDTag, parseDTag, attestationFilter, revocationFilter } from './filters.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAExD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEnE,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAErD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA"}
@@ -0,0 +1,12 @@
1
+ import type { NostrEvent, Attestation } from './types.js';
2
+ /**
3
+ * Parse a NostrEvent into a typed Attestation.
4
+ * Returns null if the event is not a valid kind 31000 attestation.
5
+ */
6
+ export declare function parseAttestation(event: NostrEvent): Attestation | null;
7
+ /**
8
+ * Check whether a NostrEvent is a revoked attestation.
9
+ * Returns false for non-attestation events.
10
+ */
11
+ export declare function isRevoked(event: NostrEvent): boolean;
12
+ //# sourceMappingURL=parsers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parsers.d.ts","sourceRoot":"","sources":["../src/parsers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAEzD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI,CAyBtE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAGpD"}
@@ -0,0 +1,41 @@
1
+ import { ATTESTATION_KIND } from './constants.js';
2
+ import { findTag } from './helpers.js';
3
+ import { parseDTag } from './filters.js';
4
+ /**
5
+ * Parse a NostrEvent into a typed Attestation.
6
+ * Returns null if the event is not a valid kind 31000 attestation.
7
+ */
8
+ export function parseAttestation(event) {
9
+ if (event.kind !== ATTESTATION_KIND)
10
+ return null;
11
+ const type = findTag(event.tags, 'type');
12
+ if (!type)
13
+ return null;
14
+ const dTag = findTag(event.tags, 'd');
15
+ const parsed = dTag ? parseDTag(dTag) : null;
16
+ const expirationStr = findTag(event.tags, 'expiration');
17
+ return {
18
+ kind: ATTESTATION_KIND,
19
+ type,
20
+ pubkey: event.pubkey,
21
+ createdAt: event.created_at,
22
+ identifier: parsed?.identifier ?? null,
23
+ subject: findTag(event.tags, 'p'),
24
+ summary: findTag(event.tags, 'summary'),
25
+ expiration: expirationStr ? Number(expirationStr) : null,
26
+ revoked: findTag(event.tags, 'status') === 'revoked',
27
+ reason: findTag(event.tags, 'reason'),
28
+ tags: event.tags,
29
+ content: event.content,
30
+ };
31
+ }
32
+ /**
33
+ * Check whether a NostrEvent is a revoked attestation.
34
+ * Returns false for non-attestation events.
35
+ */
36
+ export function isRevoked(event) {
37
+ if (event.kind !== ATTESTATION_KIND)
38
+ return false;
39
+ return findTag(event.tags, 'status') === 'revoked';
40
+ }
41
+ //# sourceMappingURL=parsers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parsers.js","sourceRoot":"","sources":["../src/parsers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAGxC;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAiB;IAChD,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB;QAAE,OAAO,IAAI,CAAA;IAEhD,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAEtB,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACrC,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAE5C,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IAEvD,OAAO;QACL,IAAI,EAAE,gBAAyB;QAC/B,IAAI;QACJ,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,SAAS,EAAE,KAAK,CAAC,UAAU;QAC3B,UAAU,EAAE,MAAM,EAAE,UAAU,IAAI,IAAI;QACtC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC;QACjC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC;QACvC,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI;QACxD,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,KAAK,SAAS;QACpD,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC;QACrC,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EAAE,KAAK,CAAC,OAAO;KACvB,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,KAAiB;IACzC,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB;QAAE,OAAO,KAAK,CAAA;IACjD,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,KAAK,SAAS,CAAA;AACpD,CAAC"}
@@ -0,0 +1,90 @@
1
+ /** Unsigned event template returned by builders. */
2
+ export interface EventTemplate {
3
+ kind: number;
4
+ tags: string[][];
5
+ content: string;
6
+ created_at?: number;
7
+ }
8
+ /** Signed Nostr event consumed by parsers and validators. */
9
+ export interface NostrEvent {
10
+ kind: number;
11
+ pubkey: string;
12
+ created_at: number;
13
+ tags: string[][];
14
+ content: string;
15
+ id: string;
16
+ sig: string;
17
+ }
18
+ /** Parameters for creating an attestation event. */
19
+ export interface AttestationParams {
20
+ /** Application-defined attestation type (must not contain colons). */
21
+ type: string;
22
+ /** D-tag second segment: hex pubkey or context string. */
23
+ identifier?: string;
24
+ /** P-tag: hex pubkey of the subject (for third-party attestations). */
25
+ subject?: string;
26
+ /** Human-readable fallback for clients that do not understand the type. */
27
+ summary?: string;
28
+ /** Unix timestamp for attestation expiry (NIP-40). */
29
+ expiration?: number;
30
+ /** Additional application-specific tags. */
31
+ tags?: string[][];
32
+ /** Event content: empty string, human-readable text, or JSON. */
33
+ content?: string;
34
+ }
35
+ /** Parameters for creating a revocation event. */
36
+ export interface RevocationParams {
37
+ /** Type of the attestation being revoked. */
38
+ type: string;
39
+ /** D-tag second segment — must match the original attestation. */
40
+ identifier: string;
41
+ /** P-tag — include if the original attestation had one. */
42
+ subject?: string;
43
+ /** Human-readable revocation reason. */
44
+ reason?: string;
45
+ /** Unix timestamp for when the revocation takes effect. */
46
+ effective?: number;
47
+ }
48
+ /** Parsed attestation data extracted from a NostrEvent. */
49
+ export interface Attestation {
50
+ kind: 31000;
51
+ type: string;
52
+ /** Attestor's pubkey (from outer event). */
53
+ pubkey: string;
54
+ /** Event timestamp (from outer event). */
55
+ createdAt: number;
56
+ /** D-tag second segment (pubkey or context string). */
57
+ identifier: string | null;
58
+ /** P-tag value (hex pubkey of subject, if present). */
59
+ subject: string | null;
60
+ summary: string | null;
61
+ expiration: number | null;
62
+ revoked: boolean;
63
+ reason: string | null;
64
+ /** All tags from the original event (for application-specific access). */
65
+ tags: string[][];
66
+ content: string;
67
+ }
68
+ /** Result of structural validation. */
69
+ export interface ValidationResult {
70
+ valid: boolean;
71
+ errors: string[];
72
+ }
73
+ /** Parameters for building a Nostr relay filter. */
74
+ export interface FilterParams {
75
+ /** Filter by attestation type (uses type tag). */
76
+ type?: string;
77
+ /** Filter by subject pubkey (p-tag). */
78
+ subject?: string;
79
+ /** Filter by attestor pubkeys. */
80
+ authors?: string[];
81
+ }
82
+ /** Nostr relay filter object. */
83
+ export interface NostrFilter {
84
+ kinds: number[];
85
+ authors?: string[];
86
+ '#p'?: string[];
87
+ '#d'?: string[];
88
+ '#type'?: string[];
89
+ }
90
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,EAAE,EAAE,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,EAAE,EAAE,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,oDAAoD;AACpD,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,CAAA;IACjB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC/B,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAA;IAClB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,KAAK,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAA;IACd,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB,uDAAuD;IACvD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,uDAAuD;IACvD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,EAAE,EAAE,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,uCAAuC;AACvC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED,oDAAoD;AACpD,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,iCAAiC;AACjC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import type { NostrEvent, ValidationResult } from './types.js';
2
+ /**
3
+ * Validate structural correctness of a Nostr event as a kind 31000 attestation.
4
+ * Does not verify signatures or check relay state.
5
+ */
6
+ export declare function validateAttestation(event: NostrEvent): ValidationResult;
7
+ //# sourceMappingURL=validators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAE9D;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,gBAAgB,CAgCvE"}
@@ -0,0 +1,37 @@
1
+ import { ATTESTATION_KIND } from './constants.js';
2
+ import { findTag } from './helpers.js';
3
+ /**
4
+ * Validate structural correctness of a Nostr event as a kind 31000 attestation.
5
+ * Does not verify signatures or check relay state.
6
+ */
7
+ export function validateAttestation(event) {
8
+ const errors = [];
9
+ if (event.kind !== ATTESTATION_KIND) {
10
+ errors.push('kind must be 31000');
11
+ }
12
+ const dTag = findTag(event.tags, 'd');
13
+ if (!dTag) {
14
+ errors.push('missing required d tag');
15
+ }
16
+ const type = findTag(event.tags, 'type');
17
+ if (type == null) {
18
+ errors.push('missing required type tag');
19
+ }
20
+ else if (!type.trim()) {
21
+ errors.push('type tag must not be empty');
22
+ }
23
+ else if (type.includes(':')) {
24
+ errors.push('type must not contain colons');
25
+ }
26
+ // d-tag must start with the type value (or equal it for self-attestations)
27
+ if (dTag && type && !type.includes(':')) {
28
+ if (dTag !== type && !dTag.startsWith(`${type}:`)) {
29
+ errors.push(`d tag must start with type value "${type}"`);
30
+ }
31
+ }
32
+ return {
33
+ valid: errors.length === 0,
34
+ errors,
35
+ };
36
+ }
37
+ //# sourceMappingURL=validators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validators.js","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAGtC;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAiB;IACnD,MAAM,MAAM,GAAa,EAAE,CAAA;IAE3B,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;IACnC,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACrC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAA;IACvC,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACxC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACjB,MAAM,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAA;IAC1C,CAAC;SAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAA;IAC3C,CAAC;SAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAA;IAC7C,CAAC;IAED,2EAA2E;IAC3E,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACxC,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC;YAClD,MAAM,CAAC,IAAI,CAAC,qCAAqC,IAAI,GAAG,CAAC,CAAA;QAC3D,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC;QAC1B,MAAM;KACP,CAAA;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "nostr-attestations",
3
+ "version": "0.0.0-development",
4
+ "description": "Builders, parsers, and validators for NIP-VA (kind 31000) Verifiable Attestation events on Nostr.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "sideEffects": false,
9
+ "engines": {
10
+ "node": ">=22"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "dist/",
21
+ "LICENSE",
22
+ "vectors/"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest",
28
+ "typecheck": "tsc --noEmit",
29
+ "lint": "eslint src/",
30
+ "lint:fix": "eslint src/ --fix",
31
+ "prepublishOnly": "npm run build && npm test"
32
+ },
33
+ "homepage": "https://github.com/forgesworn/nostr-attestations",
34
+ "keywords": [
35
+ "nostr",
36
+ "attestation",
37
+ "credential",
38
+ "endorsement",
39
+ "verification",
40
+ "identity",
41
+ "nip",
42
+ "verifiable",
43
+ "trust",
44
+ "reputation",
45
+ "provenance",
46
+ "revocation",
47
+ "addressable-event",
48
+ "kind-31000",
49
+ "zero-dependency",
50
+ "typescript"
51
+ ],
52
+ "author": "forgesworn",
53
+ "license": "MIT",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "https://github.com/forgesworn/nostr-attestations.git"
57
+ },
58
+ "devDependencies": {
59
+ "@eslint/js": "^10.0.1",
60
+ "@semantic-release/changelog": "^6.0.3",
61
+ "@semantic-release/commit-analyzer": "^13.0.1",
62
+ "@semantic-release/git": "^10.0.1",
63
+ "@semantic-release/github": "^12.0.6",
64
+ "@semantic-release/npm": "^13.1.5",
65
+ "@semantic-release/release-notes-generator": "^14.1.0",
66
+ "@vitest/coverage-v8": "^4.1.0",
67
+ "eslint": "^10.0.3",
68
+ "semantic-release": "^25.0.3",
69
+ "typescript": "^5.7.0",
70
+ "typescript-eslint": "^8.57.0",
71
+ "vitest": "^4.1.0"
72
+ }
73
+ }
@@ -0,0 +1,398 @@
1
+ [
2
+ {
3
+ "name": "simple-credential",
4
+ "description": "Third-party credential attestation with expiration and profession tags",
5
+ "event": {
6
+ "kind": 31000,
7
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
8
+ "created_at": 1700000000,
9
+ "tags": [
10
+ ["d", "credential:def456"],
11
+ ["type", "credential"],
12
+ ["p", "def456"],
13
+ ["expiration", "1735689600"],
14
+ ["summary", "Professional credential verified"],
15
+ ["profession", "attorney"],
16
+ ["jurisdiction", "US-NY"]
17
+ ],
18
+ "content": "{\"proof\":\"ring-sig-abc\"}",
19
+ "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
20
+ "sig": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
21
+ },
22
+ "expected": {
23
+ "kind": 31000,
24
+ "type": "credential",
25
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
26
+ "createdAt": 1700000000,
27
+ "identifier": "def456",
28
+ "subject": "def456",
29
+ "summary": "Professional credential verified",
30
+ "expiration": 1735689600,
31
+ "revoked": false,
32
+ "reason": null,
33
+ "tags": [
34
+ ["d", "credential:def456"],
35
+ ["type", "credential"],
36
+ ["p", "def456"],
37
+ ["expiration", "1735689600"],
38
+ ["summary", "Professional credential verified"],
39
+ ["profession", "attorney"],
40
+ ["jurisdiction", "US-NY"]
41
+ ],
42
+ "content": "{\"proof\":\"ring-sig-abc\"}"
43
+ },
44
+ "valid": true
45
+ },
46
+ {
47
+ "name": "self-attestation",
48
+ "description": "Verifier registration with no p tag — self-attestation",
49
+ "event": {
50
+ "kind": 31000,
51
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
52
+ "created_at": 1700000000,
53
+ "tags": [
54
+ ["d", "verifier:notary"],
55
+ ["type", "verifier"],
56
+ ["summary", "Identity verification service"]
57
+ ],
58
+ "content": "",
59
+ "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
60
+ "sig": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
61
+ },
62
+ "expected": {
63
+ "kind": 31000,
64
+ "type": "verifier",
65
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
66
+ "createdAt": 1700000000,
67
+ "identifier": "notary",
68
+ "subject": null,
69
+ "summary": "Identity verification service",
70
+ "expiration": null,
71
+ "revoked": false,
72
+ "reason": null,
73
+ "tags": [
74
+ ["d", "verifier:notary"],
75
+ ["type", "verifier"],
76
+ ["summary", "Identity verification service"]
77
+ ],
78
+ "content": ""
79
+ },
80
+ "valid": true
81
+ },
82
+ {
83
+ "name": "endorsement-with-app-tags",
84
+ "description": "Endorsement with context and confidence application-specific tags",
85
+ "event": {
86
+ "kind": 31000,
87
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
88
+ "created_at": 1700000000,
89
+ "tags": [
90
+ ["d", "endorsement:def456"],
91
+ ["type", "endorsement"],
92
+ ["p", "def456"],
93
+ ["summary", "Reliable provider"],
94
+ ["context", "plumbing"],
95
+ ["confidence", "high"]
96
+ ],
97
+ "content": "",
98
+ "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
99
+ "sig": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
100
+ },
101
+ "expected": {
102
+ "kind": 31000,
103
+ "type": "endorsement",
104
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
105
+ "createdAt": 1700000000,
106
+ "identifier": "def456",
107
+ "subject": "def456",
108
+ "summary": "Reliable provider",
109
+ "expiration": null,
110
+ "revoked": false,
111
+ "reason": null,
112
+ "tags": [
113
+ ["d", "endorsement:def456"],
114
+ ["type", "endorsement"],
115
+ ["p", "def456"],
116
+ ["summary", "Reliable provider"],
117
+ ["context", "plumbing"],
118
+ ["confidence", "high"]
119
+ ],
120
+ "content": ""
121
+ },
122
+ "valid": true
123
+ },
124
+ {
125
+ "name": "provenance-with-event-ref",
126
+ "description": "Provenance attestation with e tag referencing the original event",
127
+ "event": {
128
+ "kind": 31000,
129
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
130
+ "created_at": 1700000000,
131
+ "tags": [
132
+ ["d", "provenance:evt123"],
133
+ ["type", "provenance"],
134
+ ["e", "evt123"],
135
+ ["summary", "Authenticity verified"],
136
+ ["origin", "Sheffield, UK"],
137
+ ["method", "physical-inspection"]
138
+ ],
139
+ "content": "{\"chain_of_custody\":[\"maker\",\"distributor\",\"retailer\"]}",
140
+ "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
141
+ "sig": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
142
+ },
143
+ "expected": {
144
+ "kind": 31000,
145
+ "type": "provenance",
146
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
147
+ "createdAt": 1700000000,
148
+ "identifier": "evt123",
149
+ "subject": null,
150
+ "summary": "Authenticity verified",
151
+ "expiration": null,
152
+ "revoked": false,
153
+ "reason": null,
154
+ "tags": [
155
+ ["d", "provenance:evt123"],
156
+ ["type", "provenance"],
157
+ ["e", "evt123"],
158
+ ["summary", "Authenticity verified"],
159
+ ["origin", "Sheffield, UK"],
160
+ ["method", "physical-inspection"]
161
+ ],
162
+ "content": "{\"chain_of_custody\":[\"maker\",\"distributor\",\"retailer\"]}"
163
+ },
164
+ "valid": true
165
+ },
166
+ {
167
+ "name": "revocation-with-reason",
168
+ "description": "Revoked credential with reason tag",
169
+ "event": {
170
+ "kind": 31000,
171
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
172
+ "created_at": 1700000000,
173
+ "tags": [
174
+ ["d", "credential:def456"],
175
+ ["type", "credential"],
176
+ ["p", "def456"],
177
+ ["status", "revoked"],
178
+ ["reason", "license-expired"],
179
+ ["summary", "Credential revoked"]
180
+ ],
181
+ "content": "",
182
+ "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
183
+ "sig": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
184
+ },
185
+ "expected": {
186
+ "kind": 31000,
187
+ "type": "credential",
188
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
189
+ "createdAt": 1700000000,
190
+ "identifier": "def456",
191
+ "subject": "def456",
192
+ "summary": "Credential revoked",
193
+ "expiration": null,
194
+ "revoked": true,
195
+ "reason": "license-expired",
196
+ "tags": [
197
+ ["d", "credential:def456"],
198
+ ["type", "credential"],
199
+ ["p", "def456"],
200
+ ["status", "revoked"],
201
+ ["reason", "license-expired"],
202
+ ["summary", "Credential revoked"]
203
+ ],
204
+ "content": ""
205
+ },
206
+ "valid": true
207
+ },
208
+ {
209
+ "name": "revocation-with-effective",
210
+ "description": "Revocation with effective timestamp indicating when revocation takes effect",
211
+ "event": {
212
+ "kind": 31000,
213
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
214
+ "created_at": 1700000000,
215
+ "tags": [
216
+ ["d", "credential:ghi789"],
217
+ ["type", "credential"],
218
+ ["p", "ghi789"],
219
+ ["status", "revoked"],
220
+ ["reason", "compromised"],
221
+ ["effective", "1704067200"]
222
+ ],
223
+ "content": "",
224
+ "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
225
+ "sig": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
226
+ },
227
+ "expected": {
228
+ "kind": 31000,
229
+ "type": "credential",
230
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
231
+ "createdAt": 1700000000,
232
+ "identifier": "ghi789",
233
+ "subject": "ghi789",
234
+ "summary": null,
235
+ "expiration": null,
236
+ "revoked": true,
237
+ "reason": "compromised",
238
+ "tags": [
239
+ ["d", "credential:ghi789"],
240
+ ["type", "credential"],
241
+ ["p", "ghi789"],
242
+ ["status", "revoked"],
243
+ ["reason", "compromised"],
244
+ ["effective", "1704067200"]
245
+ ],
246
+ "content": ""
247
+ },
248
+ "valid": true
249
+ },
250
+ {
251
+ "name": "attestation-with-expiration",
252
+ "description": "NIP-40 expiration tag parsed as a number",
253
+ "event": {
254
+ "kind": 31000,
255
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
256
+ "created_at": 1700000000,
257
+ "tags": [
258
+ ["d", "vouch:def456"],
259
+ ["type", "vouch"],
260
+ ["p", "def456"],
261
+ ["expiration", "1735689600"]
262
+ ],
263
+ "content": "",
264
+ "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
265
+ "sig": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
266
+ },
267
+ "expected": {
268
+ "kind": 31000,
269
+ "type": "vouch",
270
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
271
+ "createdAt": 1700000000,
272
+ "identifier": "def456",
273
+ "subject": "def456",
274
+ "summary": null,
275
+ "expiration": 1735689600,
276
+ "revoked": false,
277
+ "reason": null,
278
+ "tags": [
279
+ ["d", "vouch:def456"],
280
+ ["type", "vouch"],
281
+ ["p", "def456"],
282
+ ["expiration", "1735689600"]
283
+ ],
284
+ "content": ""
285
+ },
286
+ "valid": true
287
+ },
288
+ {
289
+ "name": "attestation-with-labels",
290
+ "description": "NIP-32 L/l namespace label tags alongside standard attestation tags",
291
+ "event": {
292
+ "kind": 31000,
293
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
294
+ "created_at": 1700000000,
295
+ "tags": [
296
+ ["d", "credential:def456"],
297
+ ["type", "credential"],
298
+ ["p", "def456"],
299
+ ["L", "example.com/identity"],
300
+ ["l", "verified", "example.com/identity"]
301
+ ],
302
+ "content": "",
303
+ "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
304
+ "sig": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
305
+ },
306
+ "expected": {
307
+ "kind": 31000,
308
+ "type": "credential",
309
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
310
+ "createdAt": 1700000000,
311
+ "identifier": "def456",
312
+ "subject": "def456",
313
+ "summary": null,
314
+ "expiration": null,
315
+ "revoked": false,
316
+ "reason": null,
317
+ "tags": [
318
+ ["d", "credential:def456"],
319
+ ["type", "credential"],
320
+ ["p", "def456"],
321
+ ["L", "example.com/identity"],
322
+ ["l", "verified", "example.com/identity"]
323
+ ],
324
+ "content": ""
325
+ },
326
+ "valid": true
327
+ },
328
+ {
329
+ "name": "minimal-attestation",
330
+ "description": "Only required tags — d tag has no colon so identifier is null",
331
+ "event": {
332
+ "kind": 31000,
333
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
334
+ "created_at": 1700000000,
335
+ "tags": [
336
+ ["d", "credential"],
337
+ ["type", "credential"]
338
+ ],
339
+ "content": "",
340
+ "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
341
+ "sig": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
342
+ },
343
+ "expected": {
344
+ "kind": 31000,
345
+ "type": "credential",
346
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
347
+ "createdAt": 1700000000,
348
+ "identifier": null,
349
+ "subject": null,
350
+ "summary": null,
351
+ "expiration": null,
352
+ "revoked": false,
353
+ "reason": null,
354
+ "tags": [
355
+ ["d", "credential"],
356
+ ["type", "credential"]
357
+ ],
358
+ "content": ""
359
+ },
360
+ "valid": true
361
+ },
362
+ {
363
+ "name": "attestation-with-json-content",
364
+ "description": "Structured proof payload in content field",
365
+ "event": {
366
+ "kind": 31000,
367
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
368
+ "created_at": 1700000000,
369
+ "tags": [
370
+ ["d", "credential:def456"],
371
+ ["type", "credential"],
372
+ ["p", "def456"]
373
+ ],
374
+ "content": "{\"signatures\":[{\"type\":\"schnorr\",\"value\":\"deadbeef\"}],\"merkle_root\":\"cafebabe\"}",
375
+ "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
376
+ "sig": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
377
+ },
378
+ "expected": {
379
+ "kind": 31000,
380
+ "type": "credential",
381
+ "pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
382
+ "createdAt": 1700000000,
383
+ "identifier": "def456",
384
+ "subject": "def456",
385
+ "summary": null,
386
+ "expiration": null,
387
+ "revoked": false,
388
+ "reason": null,
389
+ "tags": [
390
+ ["d", "credential:def456"],
391
+ ["type", "credential"],
392
+ ["p", "def456"]
393
+ ],
394
+ "content": "{\"signatures\":[{\"type\":\"schnorr\",\"value\":\"deadbeef\"}],\"merkle_root\":\"cafebabe\"}"
395
+ },
396
+ "valid": true
397
+ }
398
+ ]