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 +21 -0
- package/README.md +131 -0
- package/dist/builders.d.ts +12 -0
- package/dist/builders.d.ts.map +1 -0
- package/dist/builders.js +71 -0
- package/dist/builders.js.map +1 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +14 -0
- package/dist/constants.js.map +1 -0
- package/dist/filters.d.ts +19 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +44 -0
- package/dist/filters.js.map +1 -0
- package/dist/helpers.d.ts +3 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +6 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers.d.ts +12 -0
- package/dist/parsers.d.ts.map +1 -0
- package/dist/parsers.js +41 -0
- package/dist/parsers.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validators.d.ts +7 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +37 -0
- package/dist/validators.js.map +1 -0
- package/package.json +73 -0
- package/vectors/attestations.json +398 -0
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"}
|
package/dist/builders.js
ADDED
|
@@ -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":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAGxC;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAyB;IACzD,IAAI,CAAC,MAAM,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAA;IAC3D,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAA;IAE9E,MAAM,IAAI,GAAe,EAAE,CAAA;IAE3B,yEAAyE;IACzE,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,OAAO,CAAA;IACtD,IAAI,UAAU,EAAE,CAAC;QACf,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IACtD,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;IAC/B,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;IAEhC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;IAClC,CAAC;IAED,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;IACxC,CAAC;IAED,IAAI,MAAM,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAA;QAC9F,IAAI,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;IACtD,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAC9B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAChB,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,IAAI;QACJ,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;KAC9B,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAwB;IACvD,MAAM,IAAI,GAAe;QACvB,CAAC,GAAG,EAAE,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;QAChD,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC;QACrB,CAAC,QAAQ,EAAE,SAAS,CAAC;KACtB,CAAA;IAED,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;IAClC,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;IACtC,CAAC;IAED,IAAI,MAAM,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAA;QAC5F,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;IACpD,CAAC;IAED,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,IAAI;QACJ,OAAO,EAAE,EAAE;KACZ,CAAA;AACH,CAAC"}
|
|
@@ -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"}
|
package/dist/filters.js
ADDED
|
@@ -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 @@
|
|
|
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"}
|
package/dist/helpers.js
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/parsers.js
ADDED
|
@@ -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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
]
|