A proof-of-concept implementation of decentralized contributor identity verification for open source workflows. This prototype anchors GPG commit authorship to a Hedera DID via a challenge-response handshake, enabling cryptographically verifiable Pull Request status checks on GitHub.
This project is the technical implementation of the concepts discussed in my Medium research. I recommend reading the article to understand the "Why" before diving into the "How":
π The Future of Contributor Trust: Linking DIDs and VCs to the Pull Request Workflow β Medium
| Running Demo | https://contributor-identity-prototype.vercel.app/ |
| Demo Video | full onboarding + PR verification cycle walkthrough |
| Test Repository | repository used to validate real-world GitHub webhook triggers |
This repository is organized in two phases, progressing from isolated cryptographic proof to a full-stack integration:
A self-contained Node.js/TypeScript environment used to validate the core architecture before building the full prototype. Contains:
- The challenge-response handshake simulation
- DID registry mock and resolution logic
- GPG β Ed25519 binding proof-of-concept
- Dockerized test environment for isolated runs
The production-grade POC. A React/NestJS application that bridges GitHub contributions with the Hedera Hashgraph network. This is the implementation described in the rest of this document.
Current GitHub contribution workflows rely on email-based identity and platform accounts for attribution β both of which are trivially spoofable. Commit author metadata can be forged, GPG keys can be registered under any identity, and there is no on-chain binding between a developer's real-world key material and their contributions.
This becomes a supply chain risk, particularly in the context of agentic AI systems capable of flooding open source repositories with impersonating or low-quality contributions at scale.
This prototype addresses this by implementing a cryptographic triangulation model β binding three independent identity factors:
| Factor | Mechanism | What It Proves |
|---|---|---|
| Social | GitHub OAuth | Control of the GitHub account |
| Code Authorship | GPG Signature | Authorship of the specific git commit |
| Decentralized | Ed25519 / Hedera DID | Control of the Web3 identity |
A PR status check passes only when all three are independently verifiable.
The system is composed of three logical layers:
The React/Vite frontend acts as a browser-based edge wallet. It follows a strictly non-custodial model:
- Keypair generation: Ed25519 keypairs are generated client-side using
@noble/ed25519. - Key storage: The private key is encrypted at rest in
localStorageusing AES-256-GCM with a key derived via PBKDF2 (100,000 iterations, SHA-256, random 16-byte salt). - Signing: The private key is decrypted only into volatile memory during active signing sessions. The backend never sees the private key at any point.
- VP construction: Verifiable Presentations are assembled and signed entirely in the browser before transmission.
The backend coordinates verification without ever holding user secrets:
- Challenge issuance: Generates single-use nonces per PR verification task to prevent replay attacks.
- GPG verification: Cross-references the GPG key ID extracted from PR commits against the key registered during onboarding.
- DLT resolution: When verifying a VP, the backend ignores any public key provided in the API payload and independently resolves the user's DID on Hedera to fetch the canonical public key.
- GitHub integration: Listens to
pull_requestwebhooks and writes status checks back to the PR based on verification outcome.
DID Documents and Ed25519 public keys are anchored on Hedera, providing an immutable, independently resolvable source of truth.
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"type": ["VerifiableCredential", "GpgVerificationCredential"],
"issuer": "<ISSUER_DID>",
"issuanceDate": "<ISO_TIMESTAMP>",
"credentialSubject": { "...": "..." },
"proof": {
"type": "Ed25519Signature2018",
"proofPurpose": "assertionMethod",
"verificationMethod": "<ISSUER_DID>#key-1",
"signatureValue": "<BASE64_SIG>"
}
}{
"@context": ["https://www.w3.org/2018/credentials/v1"],
"type": ["VerifiablePresentation"],
"holder": "<USER_DID>",
"verifiableCredential": ["<VC_OBJECT>"],
"proof": {
"type": "Ed25519Signature2020",
"proofPurpose": "authentication",
"verificationMethod": "<USER_DID>#key-1",
"challenge": "<SERVER_NONCE>",
"signatureValue": "<BASE64_SIG>"
}
}The challenge field in the VP proof binds each presentation to a specific server-issued nonce, making intercepted VPs non-replayable.
sequenceDiagram
actor User
participant FE as Frontend Vault
participant BE as Backend API
participant GH as GitHub
participant Hedera as Hedera DLT
User->>GH: Authenticates via OAuth
GH-->>BE: Returns OAuth Profile & Token
BE-->>FE: Issues JWT Session Token
User->>FE: Creates Vault Password
FE->>FE: Generates Ed25519 Keypair (Noble)
FE->>FE: Encrypts PrivKey (AES-256-GCM / PBKDF2)
FE->>BE: Request Challenge Nonce
BE-->>FE: Returns Nonce
User->>FE: Signs Nonce with local GPG Key
FE->>BE: Submit GPG Sig + Ed25519 PubKey
BE->>BE: Verify GPG Signature
BE->>FE: Send didMessage challenge
FE->>FE: Sign didMessage with Ed25519 PrivKey
FE->>BE: Return signed didMessage
BE->>Hedera: Register DID & Anchor PubKey
BE->>BE: Generate Verifiable Credential (VC)
BE-->>FE: Return VC linking GitHub + GPG + DID
sequenceDiagram
actor User
participant GH as GitHub
participant BE as Backend Verifier
participant Hedera as Hedera DLT
participant FE as Frontend Vault
User->>GH: Opens Pull Request (GPG Signed)
GH->>BE: Webhook: pull_request.opened
BE->>BE: Fetch PR Commits & Extract GPG Key ID
BE->>GH: Set PR Status: "Pending Verification"
BE->>BE: Generate Unique Challenge Nonce
User->>FE: Opens Dashboard & Decrypts Vault
FE->>BE: Fetch Pending Tasks
BE-->>FE: Returns Task + Nonce
FE->>FE: Constructs & Signs VP with challenge nonce
FE->>BE: Submit VP
BE->>Hedera: Resolve DID β fetch canonical Ed25519 PubKey
Hedera-->>BE: Returns PubKey
BE->>BE: Verify VP signature & nonce binding
BE->>GH: Set PR Status: "Success β"
The GitHub App is the operational core of the system. It is installed on the target repository and drives the full verification lifecycle via webhook events.
Webhook listeners:
pull_request.openedβ triggers a new verification task on every new PRpull_request.synchronizeβ re-triggers verification when new commits are pushed
Verification pipeline (per PR):
- Extract the committer's GitHub ID from the webhook payload
- Look up the user's registered GPG key in the database
- Fetch the commit's GPG verification data from the GitHub API
- Parse the raw PGP signature to extract the signing Key ID if GitHub doesn't surface it directly
- Compare the commit's signing Key ID against the registered key β fail immediately on mismatch
- If GPG matches, generate a
nonceand create aPENDINGverification task - Set the PR status to
pending, linking to the contributor dashboard - Once the user submits a valid VP from the frontend, transition the PR status to
success
GitHub PR Status Check States:
| State | Trigger | Message |
|---|---|---|
| π‘ Pending | GPG verified, awaiting VP signature | GPG Verified. Waiting for Contributor Identity signature... |
| β Success | Full chain verified (GPG + Ed25519 + Hedera) | Verified: Ed25519 signature & GPG Key match confirmed. |
| β Failure | GPG key mismatch | GPG Mismatch: Commit signed with <keyId>, but linked to <registeredKeyId>... |
| User not registered / unexpected error | User not registered / internal error |
Live Status Check Screenshots:
β Full verification passed β Ed25519 signature and GPG key confirmed on-chain:
β GPG key mismatch β commit was signed with a different key than the registered one:
π‘ Pending β GPG verified, waiting for the contributor to sign the VP from the dashboard:
Replay Attack Prevention: Each PR verification task generates a unique server-side nonce. The VP proof must include and sign this exact nonce (challenge field). A captured VP cannot be replayed against a different task.
Independent DLT Resolution: The verifier ignores the public key in the API request body. It parses the holder DID from the VP and independently resolves the associated key on Hedera. This eliminates key substitution attacks.
Non-Custodial Key Storage: AES-256-GCM with PBKDF2-derived keys means the encrypted blob in localStorage is computationally infeasible to brute-force without the vault password. The plaintext private key never leaves the browser.
Cryptographic Triangulation: The GPG key ID extracted from the PR commit is cross-referenced against the GPG key registered and verified during onboarding. A contributor cannot open a verifiable PR under a different GPG identity than the one they enrolled with.
This is a POC β the following are known constraints and the intended upgrade path:
| Current State | Limitation | Future Implementation |
|---|---|---|
| Custom HTTP API for VP submission | No standardized presentation exchange | OID4VP (OpenID for Verifiable Presentations) |
| Direct VC issuance over REST | No standardized credential issuance protocol | OID4VCI (OpenID for Verifiable Credential Issuance) |
| Raw JSON-LD VC format | Not interoperable with SD-JWT wallets | SD-JWT VC format |
| Edge wallet in browser localStorage | No cross-device portability or native key management | Mobile application (React Native / native secure enclave) |
The current prototype deliberately skips the OID4VC handshake layer to keep the cryptographic core demonstrable within the POC scope. The VC/VP data structures are W3C-compliant JSON-LD and are designed to be forward-compatible with the full OID4VC stack.
backend/src/
βββ auth/ # GitHub OAuth & JWT issuance
βββ user/ # PostgreSQL entities (TypeORM)
βββ hedera/ # Hedera DLT integration (DID registration & resolution)
βββ identity/ # VC issuance & GPG linking logic
βββ github-identity/ # Webhook listeners & core verification engine
βββ verifier.ts # Cryptographic verification logic (GPG + Ed25519 + DLT resolution)
frontend/src/
βββ pages/ # Dashboard, Onboarding, Login
βββ hooks/ # State management
βββ services/
βββ apiService.js # Axios + backend communication
βββ cryptoService.js # Noble/ed25519, AES-GCM vault, VP construction


