531 lines
22 KiB
Markdown
531 lines
22 KiB
Markdown
# SSHSIGCHAIN v1
|
||
|
||
Status: Draft 2 (pre-deployment)
|
||
|
||
SSHSIGCHAIN is a transport-independent, OpenSSH-signed authority chain. It is
|
||
intended to be implementable outside geth. A chain can be carried by JSONL,
|
||
HTTP, an object store, Iroh, or another transport; SSH is a signature mechanism,
|
||
not a transport.
|
||
|
||
Version 1 is the first and only format. There is no legacy grammar or downgrade
|
||
mode.
|
||
|
||
## 1. Security goals and non-goals
|
||
|
||
SSHSIGCHAIN provides:
|
||
|
||
- an explicit out-of-band `(chain_id, namespace, root_public_key)` trust tuple;
|
||
- deterministic signing bytes and causal parent hashes, with no sequence number
|
||
or timestamp ordering;
|
||
- a mandatory public authority state machine for devices, keys, causal
|
||
revocation, permissions, delegation, and anchor policy;
|
||
- proof-of-possession when a key is added;
|
||
- salted commitments for selectively disclosed application-profile payloads;
|
||
- signed head claims and a generic receipt policy for independent anchor
|
||
backends; and
|
||
- fail-closed rollback and fork detection relative to a cached head or multiple
|
||
verified histories.
|
||
|
||
It does not provide consensus, guaranteed publication, trusted time, anonymity,
|
||
zero-knowledge disclosure, payload encryption, or automatic compromise
|
||
recovery. An authorized key can perform every transition its effective
|
||
permissions allow. Outer links reveal the chain ID, signer, authority
|
||
transition, profile IDs, and payload commitments even when payloads are hidden.
|
||
|
||
## 2. Trust inputs and identifiers
|
||
|
||
A verifier obtains these values through an authenticated channel, never from a
|
||
downloaded chain:
|
||
|
||
1. `chain_id`: 32 independently generated random bytes;
|
||
2. `namespace`: the 1–128 byte printable, non-whitespace SSHSIG namespace
|
||
(`sshsigchain.v1` by default); and
|
||
3. `root_public_key`: canonical OpenSSH public-key text.
|
||
|
||
Canonical key text is exactly:
|
||
|
||
```text
|
||
<key-type> SP <canonical-base64-key-blob>
|
||
```
|
||
|
||
Comments, leading/trailing whitespace, and additional fields are prohibited.
|
||
Keys are identified by:
|
||
|
||
```text
|
||
BLAKE3("sshsigchain.key-id.v1\0" || canonical_public_key)
|
||
```
|
||
|
||
Protocol identifiers are 1–128 ASCII bytes containing letters, digits, `.`,
|
||
`-`, `_`, `:`, or `/`.
|
||
|
||
## 3. Outer-link model
|
||
|
||
A record contains:
|
||
|
||
| Field | Meaning |
|
||
| --- | --- |
|
||
| `chain_id` | Exact configured 32-byte chain ID. |
|
||
| `previous` | Absent only for genesis; otherwise the preceding record hash. |
|
||
| `signer_key_id` | Hash-derived ID of `signer_public_key`. |
|
||
| `signer_public_key` | Canonical OpenSSH public key used for SSHSIG verification. |
|
||
| `authority` | Exactly one public authority transition. |
|
||
| `extensions` | Strictly profile-ID-sorted payload commitments and optional disclosures. |
|
||
| `signature` | OpenSSH SSHSIG signature over the canonical signing bytes. |
|
||
|
||
There is deliberately no sequence field. A record's position and causality are
|
||
fully determined by genesis plus the exact `previous` link. Record counts are
|
||
local indexing metadata and are not signed protocol state.
|
||
|
||
The record hash named by its child is:
|
||
|
||
```text
|
||
BLAKE3("sshsigchain.record-hash.v1\0" || signing_bytes)
|
||
```
|
||
|
||
The signature is verified but excluded from record identity. This prevents two
|
||
valid encodings of a signature over identical outer content from creating
|
||
different chain identities.
|
||
|
||
## 4. Binary signing grammar
|
||
|
||
Integers are unsigned big-endian. `string16` and `bytes16` are a `u16` byte
|
||
length followed by that many bytes; `bytes32` uses a `u32` length. Lists begin
|
||
with a `u16` element count. No alignment or terminator bytes are present.
|
||
|
||
```text
|
||
"SSCS" 4 bytes
|
||
0x01 protocol version
|
||
chain_id 32 bytes
|
||
has_previous u8: 0 or 1
|
||
previous 32 bytes when present
|
||
signer_key_id 32 bytes
|
||
signer_public_key string16
|
||
authority_transition transition (section 5)
|
||
extension_count u16
|
||
repeated extension_count times:
|
||
profile_id string16
|
||
payload_commitment 32 bytes
|
||
```
|
||
|
||
Transport-only disclosure bytes and the SSHSIG signature are not part of these
|
||
bytes. The `signer_key_id` MUST match the canonical public key. Extensions MUST
|
||
be strictly sorted by profile ID with no duplicates.
|
||
|
||
### 4.1 Base signing vector
|
||
|
||
For an unsigned structural link with a zero `chain_id`, absent `previous`,
|
||
signer `ssh-ed25519 AQID`, `Noop` authority, and no extensions, the signing
|
||
bytes are:
|
||
|
||
```text
|
||
5353435301000000000000000000000000000000000000000000000000000000000000000000eea8e117bae085d4a9793d8b7d726a89558b63e515a6e1ca32d526f93b24c8a200107373682d656432353531392041514944080000
|
||
```
|
||
|
||
The derived signer key ID in that vector is
|
||
`eea8e117bae085d4a9793d8b7d726a89558b63e515a6e1ca32d526f93b24c8a2`.
|
||
This vector fixes the outer grammar; it is not a valid chain genesis because a
|
||
valid first record must carry `Genesis` authority.
|
||
|
||
## 5. Mandatory Authority v1 state machine
|
||
|
||
Authority is part of the protocol suite, not an optional application profile.
|
||
Every record contains exactly one transition. Transition tags and fields are:
|
||
|
||
| Tag | Transition | Fields after tag |
|
||
| --- | --- | --- |
|
||
| `0` | `Genesis` | device ID, root authority key, anchor policy |
|
||
| `1` | `DeviceAdd` | device ID, permission ceiling |
|
||
| `2` | `DeviceRevoke` | device ID |
|
||
| `3` | `KeyAdd` | device ID, authority key, proof `bytes32` |
|
||
| `4` | `KeyRevoke` | key ID |
|
||
| `5` | `PermissionGrant` | key ID, permissions, delegable permissions |
|
||
| `6` | `PermissionRevoke` | key ID, permissions, delegable permissions |
|
||
| `7` | `AnchorPolicySet` | anchor policy |
|
||
| `8` | `Noop` | no fields |
|
||
|
||
An authority key encodes canonical public-key `string16`, permissions, then
|
||
delegable permissions. Permission lists MUST be strictly sorted according to
|
||
their encoded `(tag, profile ID)` ordering and contain no duplicates.
|
||
|
||
### 5.1 Genesis
|
||
|
||
Genesis is the only record without `previous`. Its record signer and embedded
|
||
root key MUST equal the pinned root. The root key starts on the named root
|
||
device and MUST have both `All` authority and delegable `All` authority. Its
|
||
outer signature proves root-key possession. Genesis selects the initial anchor
|
||
policy.
|
||
|
||
Another genesis is invalid. The pinned root has no permanent bypass: it can be
|
||
revoked like another key after genesis.
|
||
|
||
### 5.2 Devices and keys
|
||
|
||
Device IDs are stable opaque identifiers. `DeviceAdd` creates a device once and
|
||
sets its immutable permission ceiling. `DeviceRevoke` is causal and permanent;
|
||
it also disables every key attached to that device. IDs and keys are never
|
||
reused or replaced in place.
|
||
|
||
`KeyAdd` declares the new key's device, active permissions, delegable
|
||
permissions, and a proof-of-possession SSHSIG made by the new key. The proof
|
||
uses namespace `sshsigchain.key-proof.v1` and these bytes:
|
||
|
||
```text
|
||
"SSKP" || 0x01 || chain_id || device_id:string16 || key_id:32 ||
|
||
public_key:string16 || permissions || delegable_permissions
|
||
```
|
||
|
||
The adding signer must be allowed to add a key to its own device or any device,
|
||
as applicable. Every granted and delegable permission must be within both the
|
||
signer's delegation scope and the target device ceiling. `KeyRevoke` is causal
|
||
and permanent.
|
||
|
||
### 5.3 Permissions
|
||
|
||
Permission tags are:
|
||
|
||
| Tag | Permission |
|
||
| --- | --- |
|
||
| `0` | `All` |
|
||
| `1` | `DeviceAdd` |
|
||
| `2` | `DeviceRevoke` |
|
||
| `3` | `KeyAddSelf` |
|
||
| `4` | `KeyAddAny` |
|
||
| `5` | `KeyRevokeSelf` |
|
||
| `6` | `KeyRevokeAny` |
|
||
| `7` | `ManagePermissions` |
|
||
| `8` | `AnchorPolicy` |
|
||
| `9` | `AnchorAttest` |
|
||
| `10` | `ProfileWrite(profile_id)` |
|
||
| `11` | `ProfileDelegate(profile_id)` |
|
||
|
||
`All` satisfies every permission and is intended for bootstrap/recovery keys.
|
||
An effective key is active only when both it and its device are active.
|
||
|
||
`PermissionGrant` requires `ManagePermissions`; every new permission must also
|
||
be within the signer's delegable set and the device ceiling. A delegable
|
||
`ProfileDelegate(x)` authorizes delegation of `ProfileWrite(x)` without
|
||
granting write access by itself. Revocation requires `ManagePermissions` and
|
||
takes effect before the next record is authorized.
|
||
|
||
An implementation MUST authorize each record from the state after its parent,
|
||
never from a final-state key set or a payload timestamp.
|
||
|
||
## 6. Selective disclosure
|
||
|
||
Each extension contains:
|
||
|
||
```text
|
||
profile_id
|
||
commitment = BLAKE3(
|
||
"sshsigchain.payload.v1\0" ||
|
||
profile_id:string16 || nonce:32 || payload:bytes32
|
||
)
|
||
optional transport disclosure { nonce, payload }
|
||
```
|
||
|
||
The 32-byte nonce MUST be generated unpredictably for each payload. It prevents
|
||
offline dictionary attacks against small hidden values; it is not encryption.
|
||
A disclosure verifies only when recomputation equals the signed commitment.
|
||
Removing or adding a valid disclosure does not change signing bytes or the
|
||
record hash.
|
||
|
||
Authority transitions are always public. Profile payloads MUST NOT create,
|
||
remove, revoke, authorize, or grant permissions to devices or keys. A verifier
|
||
can therefore verify chain authority without disclosures. If any needed
|
||
disclosure is absent, the affected profile state is `incomplete`; absence MUST
|
||
NOT be interpreted as an empty payload, deletion, or successful full replay.
|
||
|
||
## 7. Anchor policies and head claims
|
||
|
||
Anchoring makes rollback or equivocation observable under a chosen policy; it
|
||
does not make a backend a consensus system.
|
||
|
||
An anchor policy contains separately weighted attesters and publication
|
||
backends. Both lists and the required-class list are strictly sorted and unique:
|
||
|
||
```text
|
||
attester_threshold u16
|
||
attester_count u16
|
||
per attester:
|
||
key_id 32 bytes
|
||
weight u16 (non-zero)
|
||
required u8: 0 or 1
|
||
required_class_count u16
|
||
required classes repeated string16
|
||
backend_threshold u16
|
||
backend_count u16
|
||
per backend:
|
||
backend_id string16
|
||
class string16
|
||
locator string16
|
||
weight u16 (non-zero)
|
||
required u8: 0 or 1
|
||
```
|
||
|
||
Attester key IDs and backend IDs are unique, and neither threshold can exceed
|
||
its corresponding total weight. Required attesters must publish valid claims;
|
||
required backends must have valid receipts independently of thresholds. At
|
||
least one valid receipt must come from every required backend class. Classes
|
||
such as `transparency`, `nostr`, `blockchain`, and `http` are policy labels;
|
||
adapter-specific receipt verification defines their evidence. An empty
|
||
attester list with threshold zero permits any active `AnchorAttest` key, but at
|
||
least one valid claim is still required for an anchored history.
|
||
|
||
A key with `AnchorAttest` signs a `HeadClaim` under namespace
|
||
`sshsigchain.anchor.v1`:
|
||
|
||
```text
|
||
"SSAH" || 0x01 || chain_id:32 || head:32 || signer_key_id:32 ||
|
||
signer_public_key:string16
|
||
```
|
||
|
||
Every claim signer must be active and authorized at the claimed head. Distinct
|
||
signers are counted once against the attester policy. A receipt binds a backend
|
||
ID, one claim hash, and backend-defined evidence. A backend is counted once if
|
||
it has a valid receipt for any accepted claim. Claim identity is
|
||
`BLAKE3("sshsigchain.head-claim.v1\0" || claim_signing_bytes)`.
|
||
|
||
Adapters conceptually implement:
|
||
|
||
```text
|
||
publish(HeadClaim) -> AnchorReceipt
|
||
fetch(chain_id) -> HeadClaim[]
|
||
verify_receipt(policy_backend, HeadClaim, AnchorReceipt) -> bool
|
||
```
|
||
|
||
They do not choose a canonical head. After verifying claims, chains, authority,
|
||
and receipts, a client:
|
||
|
||
1. satisfies required/weighted attesters and backend evidence under the policy
|
||
which governs that head;
|
||
2. rejects histories that do not contain its cached accepted head;
|
||
3. selects the furthest valid descendant;
|
||
4. rejects incomparable valid histories as a fork; and
|
||
5. persists the newly accepted head before trusting later descendants.
|
||
|
||
The exact head containing `AnchorPolicySet` MUST satisfy the previous policy.
|
||
The new policy applies to its descendants. This prevents an authority from
|
||
weakening anchoring and treating the weakening record as already witnessed only
|
||
under the new policy.
|
||
|
||
Nostr replaceable/addressable events and ordinary mutable HTTP resources are
|
||
discovery conveniences, not strong rollback evidence by themselves. Prefer
|
||
immutable events plus the SSH-signed claim, local head caching, independent
|
||
witnesses, or a transparency log with verifiable inclusion/consistency proofs.
|
||
A blockchain receipt proves inclusion under the configured backend rules; it
|
||
does not decide between forked SSHSIGCHAIN histories.
|
||
|
||
## 8. Verification algorithm
|
||
|
||
A conforming verifier MUST:
|
||
|
||
1. reject an empty chain, over-limit inputs, noncanonical keys/lists, and
|
||
malformed commitments;
|
||
2. require the exact configured chain ID, absent genesis parent, and exact
|
||
parent hash thereafter;
|
||
3. require genesis to install the pinned root and initial authority state;
|
||
4. verify every outer SSHSIG using the configured namespace;
|
||
5. authorize the signer and transition from only the parent authority state;
|
||
6. verify new-key possession proofs and apply the authority transition;
|
||
7. require `ProfileWrite(profile_id)` for every extension and verify every
|
||
supplied disclosure; and
|
||
8. compute the record hash for the next link.
|
||
|
||
Records are never reordered by timestamps, transport arrival, identifiers, or
|
||
record counts. A verifier without all trust inputs fails closed.
|
||
|
||
## 9. Canonical objects and on-disk bundle
|
||
|
||
The canonical on-disk and full-snapshot distribution format is a single
|
||
SSHSIGCHAIN Canonical Bundle (`.sscb`). It contains records, disclosures, head
|
||
claims, and anchor receipts while deliberately omitting the root public key.
|
||
The root remains an out-of-band trust input.
|
||
|
||
All objects start with four magic bytes and version `0x01`. `bytes32` means a
|
||
big-endian `u32` length followed by that many bytes.
|
||
|
||
```text
|
||
record object:
|
||
"SSCR" || 0x01 || record_signing_bytes:bytes32 || signature:bytes32
|
||
|
||
disclosure object:
|
||
"SSCD" || 0x01 || record_hash:32 || profile_id:string16 ||
|
||
nonce:32 || payload:bytes32
|
||
|
||
head-claim object:
|
||
"SSHC" || 0x01 || claim_signing_bytes:bytes32 || signature:bytes32
|
||
|
||
anchor-receipt object:
|
||
"SSRC" || 0x01 || backend_id:string16 || claim_hash:32 ||
|
||
evidence:bytes32
|
||
```
|
||
|
||
The bundle grammar is:
|
||
|
||
```text
|
||
"SSCB" 4 bytes
|
||
0x01 bundle version
|
||
chain_id 32 bytes
|
||
namespace string16
|
||
record_count u32
|
||
records record_count objects, each wrapped as bytes32
|
||
disclosure_count u32
|
||
disclosures disclosure_count objects, each wrapped as bytes32
|
||
claim_count u32
|
||
claims claim_count objects, each wrapped as bytes32
|
||
receipt_count u32
|
||
receipts receipt_count objects, each wrapped as bytes32
|
||
```
|
||
|
||
Canonical ordering and uniqueness rules are:
|
||
|
||
- records occur in causal genesis-to-head order and every parent must equal the
|
||
preceding record hash;
|
||
- disclosures are sorted by `(record_hash, profile_id)` and that pair is unique;
|
||
- claims are sorted by claim hash and claim hashes are unique;
|
||
- receipts are sorted by `(claim_hash, backend_id)` and that pair is unique;
|
||
- disclosures must open a commitment in the named bundled record;
|
||
- claims must name a record in the bundled history; and
|
||
- receipts must name a bundled claim.
|
||
|
||
A decoder MUST reject trailing bytes, out-of-order objects, duplicate objects,
|
||
noncanonical embedded signing bytes, mixed chain IDs, unknown references, or a
|
||
bundle which does not encode byte-for-byte identically when re-encoded. The
|
||
namespace is self-description only and MUST exactly match the locally configured
|
||
trust tuple before record verification.
|
||
|
||
Bundle identity is:
|
||
|
||
```text
|
||
BLAKE3("sshsigchain.bundle.v1\0" || canonical_bundle_bytes)
|
||
```
|
||
|
||
Bundle identity is not chain identity: adding a valid disclosure or receipt
|
||
changes the bundle hash without changing any record hash. A canonical bundle is
|
||
therefore suitable as the canonical single-file on-disk snapshot, attachment,
|
||
cache object, or static-server artifact. Implementations may maintain indexes
|
||
or databases internally, but export/import MUST round-trip through this format.
|
||
|
||
The reference maximum bundle size is 256 MiB and the maximum embedded object
|
||
size is 5 MiB. Implementations MUST enforce bounds while streaming, before
|
||
allocating lengths supplied by an untrusted bundle.
|
||
|
||
### 9.1 Base bundle vector
|
||
|
||
The following structural vector contains the section 4.1 zero-chain `Noop`
|
||
record, signature bytes `01`, namespace `sshsigchain.v1`, and no disclosures,
|
||
claims, or receipts:
|
||
|
||
```text
|
||
53534342010000000000000000000000000000000000000000000000000000000000000000000e737368736967636861696e2e7631000000010000006953534352010000005b5353435301000000000000000000000000000000000000000000000000000000000000000000eea8e117bae085d4a9793d8b7d726a89558b63e515a6e1ca32d526f93b24c8a200107373682d6564323535313920415149440800000000000101000000000000000000000000
|
||
```
|
||
|
||
It fixes bundle and record-object framing. Like the section 4.1 outer vector,
|
||
it is not a valid verified chain because the first transition is not `Genesis`
|
||
and `01` is not an OpenSSH SSHSIG signature.
|
||
|
||
## 10. Distribution profiles
|
||
|
||
Distribution is untrusted availability and routing. It does not grant
|
||
authority, choose a canonical head, replace the root trust tuple, or satisfy an
|
||
anchor policy merely because a file was fetched successfully.
|
||
|
||
A distribution endpoint has a local/configured `backend_id`, `class`, and
|
||
opaque `locator`. Backends conceptually implement:
|
||
|
||
```text
|
||
fetch_bundle(endpoint, chain_id, accepted_head) -> none | canonical_bundle_bytes
|
||
```
|
||
|
||
The `accepted_head` permits an incremental P2P backend to optimize its response,
|
||
but every returned result must decode to a complete verifiable snapshot that
|
||
contains the accepted head. HTTP, Iroh, removable-media, object-store, IPFS, or
|
||
application-specific backends can implement the same interface. Fetch failures
|
||
affect availability only. Results enter the same signature, authority,
|
||
anchoring, cached-head, and fork checks.
|
||
|
||
### 10.1 Static HTTP profile
|
||
|
||
The static HTTP profile needs no server application or directory listing. For
|
||
chain ID `<lowercase-chain-id>`, publish the bundle at:
|
||
|
||
```text
|
||
/.well-known/sshsigchain/v1/<lowercase-chain-id>/chain.sscb
|
||
```
|
||
|
||
The response media type SHOULD be:
|
||
|
||
```text
|
||
application/vnd.sshsigchain.bundle.v1
|
||
```
|
||
|
||
A client performs an ordinary `GET`, accepts a successful full response, caps
|
||
bytes while streaming, decodes the canonical bundle, requires its chain ID and
|
||
namespace to match local trust, verifies the chain, verifies applicable head
|
||
claims and receipts, then applies cached-head/fork rules. `404` means the source
|
||
currently has no bundle. Servers SHOULD replace `chain.sscb` atomically.
|
||
|
||
Servers MAY expose the lowercase bundle hash as a strong `ETag`; clients MAY use
|
||
`If-None-Match`. HTTP cache validators are performance hints, not trust. HTTPS
|
||
protects privacy, locators, and availability against network interference, but
|
||
SSHSIGCHAIN verification remains mandatory even over authenticated HTTPS.
|
||
Plain HTTP remains integrity-safe only in the narrow sense that tampering is
|
||
detected; it offers no confidentiality or reliable availability.
|
||
|
||
This full-snapshot profile intentionally favors deployment with a static file
|
||
server. A later chunked profile may add immutable hash-addressed objects and a
|
||
small mutable manifest, but it must preserve the object encodings and all
|
||
verification rules above.
|
||
|
||
## 11. JSONL interchange
|
||
|
||
The reference transport uses one JSON object per nonblank line with exactly the
|
||
record fields in section 3. Byte arrays use JSON arrays of octets; enum objects
|
||
use the names in this document. Object order and whitespace are irrelevant.
|
||
Unknown or duplicate fields are rejected.
|
||
|
||
Reference limits are: 16 KiB canonical key, 64 KiB signature/proof, 1 MiB one
|
||
profile payload, 1,024 extensions per record, 100,000 records, 5 MiB one JSONL
|
||
line, and 64 MiB one JSONL input. Implementations may impose smaller limits.
|
||
|
||
JSONL is a human-facing interchange representation, not the canonical on-disk
|
||
format. It carries records and optional disclosures only; canonical bundles are
|
||
required to preserve head claims and receipts.
|
||
|
||
## 12. OpenSSH interoperability
|
||
|
||
Outer records use commands equivalent to:
|
||
|
||
```sh
|
||
ssh-keygen -Y sign -f <private-key> -n sshsigchain.v1 <signing-bytes-file>
|
||
ssh-keygen -Y verify -f <one-key-allowed-signers> -I sshsigchain \
|
||
-n sshsigchain.v1 -s <signature-file> < <signing-bytes-file>
|
||
```
|
||
|
||
The one-key `allowed_signers` file is only an input to cryptographic signature
|
||
verification. It never establishes authorization or root trust.
|
||
|
||
## 13. Security considerations
|
||
|
||
- Use independent random chain IDs and protocol-specific SSHSIG namespaces.
|
||
- A stolen active key retains its scoped authority until a causally prior
|
||
revocation is in the accepted history. There is no trusted signature time.
|
||
- Device ceilings and delegation checks prevent permission amplification but
|
||
cannot protect against a legitimately authorized `All` key.
|
||
- Selective disclosure leaks outer metadata and payload equality only when a
|
||
nonce is reused. Always use a fresh unpredictable nonce.
|
||
- Anchor strength is no greater than the configured independent backends and
|
||
receipt verifiers. Cached-head checks remain mandatory.
|
||
- Distribution sources and HTTP cache metadata are untrusted. A fetched bundle
|
||
must contain the cached head and pass the complete verifier before use.
|
||
- Resource bearer secrets, discovery records, and profile payloads cannot
|
||
mutate SSHSIGCHAIN authority.
|
||
- Implementations should apply a local OpenSSH algorithm policy and reject
|
||
unsupported keys or SSHSIG algorithms.
|
||
|
||
## References
|
||
|
||
- [OpenSSH `PROTOCOL.sshsig`](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig)
|
||
- [OpenBSD `ssh-keygen(1)`](https://man.openbsd.org/ssh-keygen.1)
|
||
- [Nostr NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md)
|
||
- [Sigstore Rekor](https://github.com/sigstore/rekor)
|