Skip to content

RIK Contract ​

RIK is the ERC-721 contract at the heart of the protocol. Each token represents an on-chain identity for a single GitHub repository.

Source: contracts/src/RIK.sol.

Identity & inheritance ​

solidity
contract RIK is ERC721, Ownable {
    constructor(address initialOwner)
        ERC721("Repository Identity Key", "RIK")
        Ownable(initialOwner) {}
}
  • Name: Repository Identity Key
  • Symbol: RIK
  • Inherits: ERC721, Ownable (both OpenZeppelin).
  • Constructor: takes the initial owner address. The deploy script (see Deployments) sets this to vm.addr(PRIVATE_KEY).

Storage ​

_keys: mapping(bytes32 => RSAKey) ​

The set of trusted GitHub OIDC signing keys.

solidity
struct RSAKey {
    bytes modulus;
    bytes exponent;
    bool active;
}

Indexed by kid, the keccak256(utf8(...)) of GitHub's published key ID. Stored as a 32-byte hash so the mapping key is fixed-size.

_repos: mapping(uint256 => Repo) ​

The per-registration record.

solidity
struct Repo {
    uint64 githubRepoId;   // == tokenId, kept for clarity
    uint64 githubOwnerId;  // GitHub numeric owner id at registration
    uint64 registeredAt;   // block.timestamp, truncated to uint64
    address registrant;    // who called register()
}

Indexed by the full uint256 repoId (which equals the GitHub repository_id and the ERC-721 tokenId).

EXPECTED_ISS ​

solidity
string public constant EXPECTED_ISS = "https://token.actions.githubusercontent.com";

The pinned issuer the JWT must declare. There is a TODO to make this owner-updatable in the future; today it is hardcoded.

Events ​

solidity
event RepoRegistered(
    uint256 indexed repoId,
    address indexed registrant,
    uint64  githubOwnerId,
    uint64  registeredAt
);

event KeyAdded(bytes32 indexed kid);
event KeyRevoked(bytes32 indexed kid);

RepoRegistered is what the Registry indexer and fcf list consume.

Errors ​

solidity
error AlreadyRegistered(uint256 repoId);
error UnknownKid(bytes32 kid);
error BadJwt();

Additional errors come from the JsonClaim library:

solidity
error ClaimMissing(string claim);
error ClaimMismatch(string claim);

Two more revert via require(...) strings:

  • "token expired", exp is in the past.
  • "token not yet valid", nbf is in the future.

See Troubleshooting for guidance on each.

register(...) ​

solidity
function register(
    bytes32 kid,
    bytes calldata headerB64,
    bytes calldata payloadB64,
    bytes calldata signature,
    uint256 repoId,
    uint64  githubOwnerId
) external;

Validates a GitHub OIDC JWT and mints a RIK if everything matches.

ArgumentMeaning
kidkeccak256(utf8(jwt.header.kid)).
headerB64JWT header as base64url bytes (not decoded).
payloadB64JWT payload as base64url bytes (not decoded).
signatureJWT signature, raw bytes (base64url decoded).
repoIdGitHub repository_id. Becomes the ERC-721 tokenId.
githubOwnerIdGitHub repository_owner_id at the time of registration.

The verification chain is documented in detail at JWT Verification. In summary:

  1. kid is active.
  2. RSA-PKCS#1 v1.5 signature over sha256(headerB64 + "." + payloadB64) verifies.
  3. aud claim equals Strings.toHexString(uint160(msg.sender), 20).
  4. repository_id claim equals repoId.
  5. repository_owner_id claim equals githubOwnerId.
  6. iss claim equals EXPECTED_ISS.
  7. exp > block.timestamp.
  8. nbf < block.timestamp.
  9. _ownerOf(repoId) == address(0) (not already registered).

If all checks pass, the contract:

  • Stores a new Repo record at _repos[repoId].
  • Emits RepoRegistered(repoId, msg.sender, githubOwnerId, block.timestamp).
  • Mints the ERC-721 token: _mint(msg.sender, repoId).

tokenIdOf(uint64) ​

solidity
function tokenIdOf(uint64 githubRepoId) external pure returns (uint256);

Returns uint256(githubRepoId). Identity mapping, intentionally explicit so callers can use this without remembering the equality.

repoOf(uint256 tokenId) ​

solidity
function repoOf(uint256 tokenId) external view returns (Repo memory);

Returns the stored Repo for the given tokenId. Reverts with "not registered" if the token has never been minted.

addKey(bytes32 kid, bytes calldata n, bytes calldata e) ​

Owner-only. Registers a new trusted GitHub signing key.

solidity
function addKey(bytes32 kid, bytes calldata n, bytes calldata e) external onlyOwner;

Emits KeyAdded(kid). See fcf keys sync for the helper that batches this against GitHub's current JWKS.

revokeKey(bytes32 kid) ​

Owner-only. Marks a key inactive. The mapping entry is preserved (modulus and exponent stay), but active is set to false, which causes _verifyJwt to revert with UnknownKid for tokens signed with that key.

solidity
function revokeKey(bytes32 kid) external onlyOwner;

Emits KeyRevoked(kid).

What the contract does not allow ​

  • No transfers blocked by the contract itself, the standard ERC-721 transfer functions are available. If you want to transfer your RIK to a different address you can, but the historical registrant field stays the same.
  • No owner override on mint. The owner cannot mint a RIK on someone else's behalf.
  • No update path on Repo. Once registered, the stored fields are immutable.
  • No burn. RIK V0 is one-per-repo, forever. Burn might be added in a future version with explicit governance.

Continue ​

Released under the Apache 2.0 License.