feat(git-sign-nostr): implement NIP-GS git object signing with Nostr keys (#459)

This commit is contained in:
tlongwell-block
2026-05-03 10:05:30 -04:00
committed by GitHub
parent f946e9b68a
commit 1feb18e2e0
6 changed files with 3413 additions and 356 deletions
Generated
+27
View File
@@ -1361,6 +1361,19 @@ dependencies = [
"zeroize",
]
[[package]]
name = "git-sign-nostr"
version = "0.1.0"
dependencies = [
"base64",
"chrono",
"hex",
"libc",
"nostr",
"serde_json",
"zeroize",
]
[[package]]
name = "h2"
version = "0.4.13"
@@ -5679,6 +5692,20 @@ name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zerotrie"
+1
View File
@@ -19,6 +19,7 @@ members = [
"crates/sprout-sdk",
"crates/sprout-persona",
"crates/git-credential-nostr",
"crates/git-sign-nostr",
]
exclude = ["desktop/src-tauri"]
resolver = "2"
+41
View File
@@ -0,0 +1,41 @@
[package]
name = "git-sign-nostr"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "NIP-GS git commit/tag signing program using Nostr secp256k1 keys"
readme = "README.md"
publish = false # internal workspace tool, not published to crates.io
[[bin]]
name = "git-sign-nostr"
path = "src/main.rs"
[dependencies]
# Base64 armor encoding/decoding for NIP-GS signature envelopes.
# Not in workspace deps — each crate pins independently (same pattern as
# sprout-relay, sprout-mcp, sprout-cli, git-credential-nostr).
base64 = "0.22"
# Hex encoding for BIP-340 signatures and public keys.
hex = { workspace = true }
# Secret key zeroization on drop.
zeroize = { workspace = true, features = ["derive"] }
# Nostr key parsing (nsec/npub bech32), secp256k1 Schnorr signing, SHA-256.
# Uses the full default feature set because we need: Keys, PublicKey,
# FromBech32, and the re-exported bitcoin::secp256k1 + bitcoin::hashes.
nostr = { workspace = true }
# JSON parsing for NIP-OA auth tag and envelope verification.
serde_json = { workspace = true }
# Timestamp formatting for GnuPG VALIDSIG status lines.
chrono = { workspace = true }
# Unix-specific: O_NOFOLLOW for keyfile open, fcntl for fd validation.
[target.'cfg(unix)'.dependencies]
libc = "0.2"
+46
View File
@@ -0,0 +1,46 @@
# git-sign-nostr
NIP-GS signing program — signs git commits and tags with Nostr secp256k1 keys
using BIP-340 Schnorr signatures.
## Usage
```bash
# Configure git to use nostr signing
git config gpg.format x509
git config gpg.x509.program /path/to/git-sign-nostr
git config commit.gpgsign true
git config tag.gpgsign true
git config user.signingkey <hex-pubkey>
# Set the private key (env var)
export NOSTR_PRIVATE_KEY=<hex-or-nsec>
# Optional: NIP-OA owner attestation
export SPROUT_AUTH_TAG='["auth","<owner-pk>","<conditions>","<owner-sig>"]'
# Commits are now automatically signed
git commit -m "signed with nostr"
# Verify
git verify-commit HEAD
```
## Key Loading Priority
1. `NOSTR_PRIVATE_KEY` environment variable
2. `SPROUT_PRIVATE_KEY` environment variable
3. Keyfile at path from `git config nostr.keyfile`
Keys may be hex (64 chars) or NIP-19 bech32 (`nsec1...`).
## How It Works
Git invokes this program as a signing/verification backend:
- **Sign:** `git-sign-nostr --status-fd=2 -bsau <keyid>` — reads payload from
stdin, writes armored signature to stdout, status lines to fd 2 (stderr)
- **Verify:** `git-sign-nostr --status-fd=1 --verify <sigfile> -` — reads
payload from stdin, verifies signature from file, status lines to fd 1 (stdout)
See [NIP-GS](../../docs/nips/NIP-GS.md) for the full specification.
File diff suppressed because it is too large Load Diff
+754 -356
View File
File diff suppressed because it is too large Load Diff