GitHub OIDC Trust Model
OIDC (OpenID Connect) is the substrate the RIK protocol stands on. This page explains what GitHub OIDC tokens are, why they are the right trust root, and how the contract uses them.
The 30-second version
- Every GitHub Actions workflow can ask GitHub for a JWT.
- The JWT is signed with GitHub's private RSA key.
- The JWT's claims describe the workflow's exact identity: which repo, which owner, which actor, which ref.
- The JWT can declare a custom
audclaim, the workflow gets to choose what audience the token is for. - We make the workflow set
audto the maintainer's Ethereum address. - The RIK contract verifies the signature using GitHub's public key, checks the claims, and mints if everything matches.
Reference: GitHub OIDC documentation.
What a GitHub OIDC token looks like
A decoded payload looks roughly like this:
{
"iss": "https://token.actions.githubusercontent.com",
"repository": "freecodefund/example",
"repository_id": "871234567",
"repository_owner_id": "421337",
"actor": "alice",
"sub": "repo:freecodefund/example:ref:refs/heads/main",
"aud": "<the workflow sets this>",
"exp": 1717500000,
"iat": 1717499100,
"nbf": 1717499100
}Two things make this a solid trust root:
- The JWT is signed by GitHub. The signature is RSA-2048 PKCS#1 v1.5 over the SHA-256 of
headerB64 + "." + payloadB64. Anyone with GitHub's public modulus can verify it. audbinds the proof to the caller. Because the workflow gets to setaud, we can demandaud == msg.senderon-chain. A token issued for address0xAcannot be replayed by address0xB.
Why OIDC specifically
The alternatives we considered, and why each is worse:
| Alternative | Problem |
|---|---|
| Social login + backend mint | The backend becomes the trust root. We become the gatekeeper. |
| Manual whitelist | Doesn't scale, defeats the point. |
| Posting an address in the repo README | Easily faked, no chain of custody. |
| GitHub PAT verification on a backend | The backend still becomes the trust root, and PATs are user-scoped not repo-scoped. |
| OIDC JWT verified directly on-chain | No backend in the trust path. Verifiable by the contract. |
OIDC is the only option that lets the contract itself be convinced, by GitHub's own cryptographic signature, that a specific repo's workflow issued the token.
Key rotation
GitHub rotates the keys that sign OIDC tokens periodically. To stay correct, the RIK contract has a small mechanism:
- Keys are stored on-chain as a mapping from
kid(key ID) to(modulus, exponent, active). kidis stored askeccak256(utf8(kid))to make the key compact and indexable.- The contract owner can call
addKeyto register new keys when GitHub publishes them, andrevokeKeyto mark a key inactive. - The CLI subcommand
fcf keys syncautomates pulling the current key set fromhttps://token.actions.githubusercontent.com/.well-known/openid-configurationand pushing eachRSAentry into the contract.
This is the only owner-privileged operation in RIK. The owner cannot mint, cannot transfer, cannot rewrite a registration, only keep the set of trusted GitHub signing keys current.
What the contract does not do
- It does not call GitHub. There is no off-chain step.
- It does not maintain a list of repos or owners or actors. Only signing keys.
The full verification chain
The contract checks, in order:
kidis registered and active.- RSA-PKCS#1 v1.5 signature over
sha256(headerB64 + "." + payloadB64)verifies under the stored(modulus, exponent). audclaim equalslowercaseHex(msg.sender).repository_idclaim equals therepoIdargument.repository_owner_idclaim equals thegithubOwnerIdargument.issclaim equalshttps://token.actions.githubusercontent.com.exp > block.timestamp.nbf < block.timestamp.- No prior registration of this
repoId.
For the Solidity, see JWT Verification.