docs(nips): NIP-GS — Git Object Signing with Nostr Keys#455
Merged
Conversation
Define a signature format and verification protocol for signing git commits
and tags with Nostr secp256k1 keys via git's pluggable x509 signing interface.
- Domain-separated hash: SHA-256("nostr:git:v1:" || t || ":" || payload)
- BIP-340 Schnorr signatures with the signer's nostr key
- Full GNUPG status protocol compatibility (SIG_CREATED, GOODSIG, VALIDSIG)
- Deterministic test vector with reproducible signature
- Comprehensive security analysis (domain separation, key exposure, replay)
Third piece of the sovereign git story: NIP-98 authenticates the pusher,
branch protections authorize the push, and now NIP-GS authenticates the
committer. One identity, one key, across all surfaces.
…raphic binding - Add optional 'oa' field embedding NIP-OA owner attestation in signature envelope - Bind oa to signing hash (oa_binding in preimage) — stripping/modifying oa invalidates sig - Enforce strict canonical JSON serialization — byte-for-byte reconstruction check in verification - Require UTF-8 validation, reject non-canonical JSON formatting - Malformed oa is a hard failure (ERRSIG) — prevents garbage injection - Full deterministic test vector with oa (owner=0x01, agent=0x03) - Comprehensive security analysis: envelope immutability, malleability prevention - Scored 10/10 by Gemini 3.1 Pro after 3 iterations (5.4 → 9.0 → 10)
The hex dump of the test payload had '343631303031' (decodes to '461001') where it should be '3436313031' (decodes to '46101'). This made the hex dump decode to 171 bytes instead of the stated 170. The text representation, hash computations, and signatures were all correct — only the hex dump display had the extra byte.
tlongwell-block
added a commit
that referenced
this pull request
May 3, 2026
…keys
Adds git-sign-nostr, a standalone binary that implements NIP-GS (Git Object
Signing with Nostr Keys). Git invokes it as gpg.x509.program to produce and
verify BIP-340 Schnorr signatures over commit/tag objects.
Key features:
- Sign mode: reads private key, computes SHA-256 signing hash with domain
separation ("nostr:git:v1:"), produces armored base64 JSON signature
- Verify mode: validates BIP-340 signature, checks NIP-OA delegation if
present, emits GPG-compatible status lines for git's trust framework
- NIP-OA support: optional owner attestation binds agent keys to owner
keys with temporal conditions (created_at<, created_at>)
- Security hardening: O_NOFOLLOW+fstat keyfile handling, Zeroizing<String>
for secrets, bounded reads everywhere, fcntl fd validation, env_remove
on subprocesses, fail-closed on invalid auth tags
Also wires the binary into the agent spawn path and extends the e2e test
suite with signing, auth bypass, HMAC tampering, and hook integrity tests.
Implements: NIP-GS (PR #455)
Related: git-credential-nostr (PR #451)
Tests: 31 unit tests, 16 e2e tests, zero clippy warnings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
The sovereign git story has three layers: transport (NIP-98 authenticates the pusher), authorization (role-based push permissions enforce who can push where), and authorship (commit signatures prove who wrote the code). PRs #419 and #441 delivered the first two. PR #451 wired credential helpers into agent processes. This NIP defines the third layer — signing git commits and tags with the same nostr key used for everything else.
Agents already have a nostr keypair for relay auth, channel membership, and owner attestation (NIP-OA). This NIP lets that same key sign their commits. One identity, one key, across all surfaces — from relay messages to code authorship.
What this PR adds
A new NIP specification at
docs/nips/NIP-GS.md(642 lines). No code changes.The protocol
Git's pluggable signing interface (
gpg.format=x509,gpg.x509.program) lets us drop in a custom signing program. The program:SHA-256("nostr:git:v1:" || timestamp || ":" || payload)), produces a BIP-340 Schnorr signature, outputs an armored JSON envelope[GNUPG:]status lines that git's parser understandsKey design decisions
gpg.format=x509gpg-interface.c, but uses-----BEGIN SIGNED MESSAGE-----markers that don't collide with PGP/SSH. No extra verify args. Proven by sigstore/gitsign (1,000+ ⭐)."nostr:git:v1:"prefix prevents cross-protocol replay — a NIP-98 HTTP auth sig or NIP-01 event sig can't be reused as a commit signature. Timestamp bound into the hash.GIT_CONFIG_COUNTenv varsWhat's specified
[GNUPG:]status protocol (SIG_CREATED, GOODSIG, BADSIG, ERRSIG, VALIDSIG, TRUST_*)Relationship to prior work
Together they close the loop from
VISION_SOVEREIGN.md: agents clone repos, push code, and sign their commits — all through the relay, all with nostr identity.Review notes
docs/nips/(NIP-OA, NIP-RS)