Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ make deploy-idcard # IdCard contract to Base
| Contract | Both Chains |
|---|---|
| Semaphore | `0x8A1fd199516489B0Fb7153EB5f075cDAC83c693D` |
| CredentialRegistry | `0xbF9b2556e6Dd64D60E08E3669CeF2a4293e006db` |
| DefaultScorer | `0x315044578dd9480Dd25427E4a4d94b0fc2Fa4f8c` |
| ScorerFactory | `0xAa03996D720C162Fdff246E1D3CEecc792986750` |
| CredentialRegistry | `0x17a22f130d4e1c4ba5C20a679a5a29F227083A62` |
| DefaultScorer | `0x6791B588dAdeb4323bc1C3d987130bC13cBe3625` |
| ScorerFactory | `0x016bC46169533a8d3284c5D8DD590C91783C8C06` |

Owner: `0x6F0CDcd334BA91A5E221582665Cce0431aD4Fc0b`
Owner: `0x677112864ED447866f8D461ABe284E5e907bB4F8`
Trusted verifier (Sepolia): `0x3c50f7055D804b51e506Bc1EA7D082cB1548376C`
Trusted verifier (mainnet): `0x9186aA65288bFfa67fB58255AeeaFfc4515535d9`

Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ Contract addresses are identical on both chains (same deployer, same nonce).
| Contract | Address |
|---|---|
| Semaphore | [`0x8A1fd199516489B0Fb7153EB5f075cDAC83c693D`](https://basescan.org/address/0x8A1fd199516489B0Fb7153EB5f075cDAC83c693D) |
| CredentialRegistry | [`0xbF9b2556e6Dd64D60E08E3669CeF2a4293e006db`](https://basescan.org/address/0xbF9b2556e6Dd64D60E08E3669CeF2a4293e006db) |
| DefaultScorer | [`0x315044578dd9480Dd25427E4a4d94b0fc2Fa4f8c`](https://basescan.org/address/0x315044578dd9480Dd25427E4a4d94b0fc2Fa4f8c) |
| ScorerFactory | [`0xAa03996D720C162Fdff246E1D3CEecc792986750`](https://basescan.org/address/0xAa03996D720C162Fdff246E1D3CEecc792986750) |
| CredentialRegistry | [`0x17a22f130d4e1c4ba5C20a679a5a29F227083A62`](https://basescan.org/address/0x17a22f130d4e1c4ba5C20a679a5a29F227083A62) |
| DefaultScorer | [`0x6791B588dAdeb4323bc1C3d987130bC13cBe3625`](https://basescan.org/address/0x6791B588dAdeb4323bc1C3d987130bC13cBe3625) |
| ScorerFactory | [`0x016bC46169533a8d3284c5D8DD590C91783C8C06`](https://basescan.org/address/0x016bC46169533a8d3284c5D8DD590C91783C8C06) |

### Base Sepolia (chain ID 84532)

| Contract | Address |
|---|---|
| Semaphore | [`0x8A1fd199516489B0Fb7153EB5f075cDAC83c693D`](https://sepolia.basescan.org/address/0x8A1fd199516489B0Fb7153EB5f075cDAC83c693D) |
| CredentialRegistry | [`0xbF9b2556e6Dd64D60E08E3669CeF2a4293e006db`](https://sepolia.basescan.org/address/0xbF9b2556e6Dd64D60E08E3669CeF2a4293e006db) |
| DefaultScorer | [`0x315044578dd9480Dd25427E4a4d94b0fc2Fa4f8c`](https://sepolia.basescan.org/address/0x315044578dd9480Dd25427E4a4d94b0fc2Fa4f8c) |
| ScorerFactory | [`0xAa03996D720C162Fdff246E1D3CEecc792986750`](https://sepolia.basescan.org/address/0xAa03996D720C162Fdff246E1D3CEecc792986750) |
| CredentialRegistry | [`0x17a22f130d4e1c4ba5C20a679a5a29F227083A62`](https://sepolia.basescan.org/address/0x17a22f130d4e1c4ba5C20a679a5a29F227083A62) |
| DefaultScorer | [`0x6791B588dAdeb4323bc1C3d987130bC13cBe3625`](https://sepolia.basescan.org/address/0x6791B588dAdeb4323bc1C3d987130bC13cBe3625) |
| ScorerFactory | [`0x016bC46169533a8d3284c5D8DD590C91783C8C06`](https://sepolia.basescan.org/address/0x016bC46169533a8d3284c5D8DD590C91783C8C06) |

### Credential Groups

Expand Down
4 changes: 4 additions & 0 deletions contracts/interfaces/Events.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ event AttestationValidityDurationSet(uint256 duration);
/// @param status The new credential group status.
event CredentialGroupStatusChanged(uint256 indexed credentialGroupId, ICredentialRegistry.CredentialGroupStatus status);

/// @notice Emitted when the future attestation buffer is updated.
/// @param buffer The new buffer duration in seconds.
event FutureAttestationBufferSet(uint256 buffer);

/// @notice Emitted when the registry-level default Merkle tree duration is updated.
/// @param duration The new default duration in seconds.
event DefaultMerkleTreeDurationSet(uint256 indexed duration);
Expand Down
4 changes: 4 additions & 0 deletions contracts/interfaces/ICredentialRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ interface ICredentialRegistry {
/// @param verifier_ The verifier address to remove.
function removeTrustedVerifier(address verifier_) external;

/// @notice Updates the forward-tolerance buffer for future attestation timestamps.
/// @param buffer_ New buffer in seconds (0 to disable tolerance).
function setFutureAttestationBuffer(uint256 buffer_) external;

/// @notice Updates the registry-level default Merkle tree duration for new Semaphore groups.
/// @param duration_ New default Merkle tree duration in seconds.
function setDefaultMerkleTreeDuration(uint256 duration_) external;
Expand Down
2 changes: 1 addition & 1 deletion contracts/registry/base/AttestationVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ abstract contract AttestationVerifier is RegistryStorage {
if (apps[attestation_.appId].status != AppStatus.ACTIVE) revert AppNotActive();
if (attestation_.registry != address(this)) revert WrongRegistryAddress();
if (attestation_.chainId != block.chainid) revert WrongChain();
if (attestation_.issuedAt > block.timestamp) revert FutureAttestation();
if (attestation_.issuedAt > block.timestamp + futureAttestationBuffer) revert FutureAttestation();
if (block.timestamp > attestation_.issuedAt + attestationValidityDuration) revert AttestationExpired();

signer = keccak256(abi.encode(attestation_)).toEthSignedMessageHash().recover(v, r, s);
Expand Down
7 changes: 7 additions & 0 deletions contracts/registry/base/RegistryAdmin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ abstract contract RegistryAdmin is RegistryStorage {
emit AttestationValidityDurationSet(duration_);
}

/// @notice Updates the forward-tolerance buffer for future attestation timestamps.
/// @param buffer_ New buffer in seconds (0 to disable tolerance).
function setFutureAttestationBuffer(uint256 buffer_) public onlyOwner {
futureAttestationBuffer = buffer_;
emit FutureAttestationBufferSet(buffer_);
}

/// @notice Updates the registry-level default Merkle tree duration for new Semaphore groups.
/// @dev Does not propagate to existing groups. Only affects groups created after this call.
/// @param duration_ New duration in seconds (must be > 0).
Expand Down
5 changes: 5 additions & 0 deletions contracts/registry/base/RegistryStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ abstract contract RegistryStorage is ICredentialRegistry, Ownable2Step, Pausable
/// @notice Maximum age (in seconds) an attestation is accepted. Default 30 minutes.
uint256 public attestationValidityDuration = 30 minutes;

/// @notice Forward-tolerance buffer (in seconds) for attestation issuedAt timestamps.
/// On L2s a sequencer's block.timestamp can lag behind real-world time,
/// causing valid attestations to be rejected as "future". Default 10 minutes.
uint256 public futureAttestationBuffer = 10 minutes;

/// @notice Array of all registered credential group IDs (for enumeration).
uint256[] public credentialGroupIds;

Expand Down
6 changes: 3 additions & 3 deletions docs/app-manager-specs.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A web dashboard for third-party app developers to self-manage their BringID inte

All interactions go to two contracts on Base (mainnet 8453 / Sepolia 84532):

### CredentialRegistry (`0xbF9b2556e6Dd64D60E08E3669CeF2a4293e006db`)
### CredentialRegistry (`0x17a22f130d4e1c4ba5C20a679a5a29F227083A62`)

| Function | Access | Description |
|---|---|---|
Expand All @@ -31,7 +31,7 @@ All interactions go to two contracts on Base (mainnet 8453 / Sepolia 84532):
| `credentialGroups(uint256 id)` | View | Returns `(status, validityDuration, familyId)`. |
| `getCredentialGroupIds()` | View | Returns all registered credential group IDs. |

### DefaultScorer (`0x315044578dd9480Dd25427E4a4d94b0fc2Fa4f8c`)
### DefaultScorer (`0x6791B588dAdeb4323bc1C3d987130bC13cBe3625`)

Read-only from the dashboard's perspective (only BringID owner can write):

Expand All @@ -41,7 +41,7 @@ Read-only from the dashboard's perspective (only BringID owner can write):
| `getScores(uint256[] credentialGroupIds)` | View | Scores for multiple groups. |
| `getAllScores()` | View | All group IDs + scores. |

### ScorerFactory (`0xAa03996D720C162Fdff246E1D3CEecc792986750`)
### ScorerFactory (`0x016bC46169533a8d3284c5D8DD590C91783C8C06`)

Deploys DefaultScorer instances owned by the caller. Same address on both chains.

Expand Down
8 changes: 4 additions & 4 deletions docs/fetching-scores.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The `DefaultScorer` contract stores global scores for each credential group. Scores can be read on-chain in a single call — no iteration or multicall needed.

**DefaultScorer address (Base mainnet & Sepolia):** `0x315044578dd9480Dd25427E4a4d94b0fc2Fa4f8c`
**DefaultScorer address (Base mainnet & Sepolia):** `0x6791B588dAdeb4323bc1C3d987130bC13cBe3625`

## Get All Scores

Expand All @@ -11,7 +11,7 @@ The `DefaultScorer` contract stores global scores for each credential group. Sco
### cast (Foundry)

```bash
cast call 0x315044578dd9480Dd25427E4a4d94b0fc2Fa4f8c \
cast call 0x6791B588dAdeb4323bc1C3d987130bC13cBe3625 \
"getAllScores()(uint256[],uint256[])" \
--rpc-url $BASE_RPC_URL
```
Expand All @@ -20,7 +20,7 @@ cast call 0x315044578dd9480Dd25427E4a4d94b0fc2Fa4f8c \

```js
const scorer = new ethers.Contract(
"0x315044578dd9480Dd25427E4a4d94b0fc2Fa4f8c",
"0x6791B588dAdeb4323bc1C3d987130bC13cBe3625",
["function getAllScores() view returns (uint256[], uint256[])"],
provider
);
Expand All @@ -33,7 +33,7 @@ const [groupIds, scores] = await scorer.getAllScores();

```js
const [groupIds, scores] = await publicClient.readContract({
address: "0x315044578dd9480Dd25427E4a4d94b0fc2Fa4f8c",
address: "0x6791B588dAdeb4323bc1C3d987130bC13cBe3625",
abi: [{
name: "getAllScores",
type: "function",
Expand Down
115 changes: 115 additions & 0 deletions docs/integration-test/test-verifier-prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Task: Test the TLSN Verifier `/verify/oauth` endpoint

Write and run a Node.js script (using ethers v6 which is already installed in this repo) to test the verifier's `/verify/oauth` endpoint running at `http://localhost:3000`.

## What the endpoint does

It accepts an OAuth credential message + signature, validates it, and returns a signed attestation. The server is running in **dev mode**, which means signer validation is skipped — but the signature must still be a valid ECDSA signature over the correct message hash.

## How to construct the request

### 1. Create a signer (any random wallet works in dev mode)

```js
const { ethers } = require("ethers");
const wallet = ethers.Wallet.createRandom();
```

### 2. Build the OAuth message

```js
const message = {
domain: "github.com",
userId: "testuser123",
score: "30", // must be >= credential group's min score (uint256 as string)
timestamp: "1700000000" // uint256 as string
};
```

### 3. Sign the message

The signature must be over `keccak256(abi.encode(string, string, uint256, uint256))`:

```js
const encoded = ethers.AbiCoder.defaultAbiCoder().encode(
["string", "string", "uint256", "uint256"],
[message.domain, message.userId, message.score, message.timestamp]
);
const hash = ethers.keccak256(encoded);
const signature = await wallet.signMessage(ethers.getBytes(hash));
```

### 4. Send the request

```js
const body = {
message: message,
signature: signature,
registry: "0x17a22f130d4e1c4ba5C20a679a5a29F227083A62",
chain_id: "84532", // Base Sepolia
credential_group_id: "5", // github.com, min score 30
app_id: "1",
semaphore_identity_commitment: "12345"
};

const res = await fetch("http://localhost:3000/verify/oauth", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
```

### 5. Validate the response

The response should be:

```json
{
"attestation": {
"registry": "0x17a22f130d4e1c4ba5c20a679a5a29f227083a62",
"chain_id": 84532,
"credential_group_id": "...",
"credential_id": "0x...",
"app_id": "1",
"semaphore_identity_commitment": "12345",
"issued_at": 1740268800
},
"verifier_hash": "0x...",
"signature": "0x..."
}
```

Verify:
- `attestation.chain_id` is `84532` (number, not string)
- `attestation.registry` is the address you sent
- `attestation.credential_group_id` is `"5"`
- `attestation.app_id` is `"1"`
- `attestation.semaphore_identity_commitment` is `"12345"`
- `attestation.issued_at` is a recent unix timestamp (number)
- `verifier_hash` matches `keccak256(abi.encode(registry, chainId, credentialGroupId, credentialId, appId, semaphoreIdentityCommitment, issuedAt))` with Solidity types `(address, uint256, uint256, bytes32, uint256, uint256, uint256)`
- `signature` recovers to verifier address `0x3c50f7055D804b51e506Bc1EA7D082cB1548376C`

### 6. Test error cases

Also test these should fail:
- **Missing `chain_id`** → should return 422
- **Invalid `chain_id: "1"`** → should return 400 with "unsupported chain_id"
- **Wrong domain** (e.g. `domain: "x.com"` with `credential_group_id: "5"` which expects `github.com`) → should return 400

## Available credential groups for testing

| ID | Domain | Min Score |
|----|--------|-----------|
| 1 | farcaster.xyz | 10 |
| 4 | github.com | 10 |
| 5 | github.com | 30 |
| 7 | x.com | 10 |
| 10 | zkpassport.id | 100 |
| 11 | self.xyz | 100 |

## Important notes

- Run the script from `/home/claude/credential-registry` (that's where `ethers` is installed)
- All uint256 values in the request body are **strings**
- `chain_id` in the request is a **string**, but in the response `attestation.chain_id` is a **number**
- The script should print clear pass/fail results for each check
8 changes: 4 additions & 4 deletions docs/migration-guide-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ Contract addresses are identical on both chains (same deployer, same nonce).
| Contract | Address | Chains |
|---|---|---|
| Semaphore | `0x8A1fd199516489B0Fb7153EB5f075cDAC83c693D` | mainnet (8453), Sepolia (84532) |
| CredentialRegistry | `0xbF9b2556e6Dd64D60E08E3669CeF2a4293e006db` | mainnet (8453), Sepolia (84532) |
| DefaultScorer | `0x315044578dd9480Dd25427E4a4d94b0fc2Fa4f8c` | mainnet (8453), Sepolia (84532) |
| ScorerFactory | `0xAa03996D720C162Fdff246E1D3CEecc792986750` | mainnet (8453), Sepolia (84532) |
| CredentialRegistry | `0x17a22f130d4e1c4ba5C20a679a5a29F227083A62` | mainnet (8453), Sepolia (84532) |
| DefaultScorer | `0x6791B588dAdeb4323bc1C3d987130bC13cBe3625` | mainnet (8453), Sepolia (84532) |
| ScorerFactory | `0x016bC46169533a8d3284c5D8DD590C91783C8C06` | mainnet (8453), Sepolia (84532) |

Owner: `0x6F0CDcd334BA91A5E221582665Cce0431aD4Fc0b`
Owner: `0x677112864ED447866f8D461ABe284E5e907bB4F8`
Trusted verifier (Sepolia): `0x3c50f7055D804b51e506Bc1EA7D082cB1548376C`
Trusted verifier (mainnet): `0x9186aA65288bFfa67fB58255AeeaFfc4515535d9`

Expand Down
2 changes: 1 addition & 1 deletion docs/migration-instructions/task-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Update user-facing error messages:
### 9. Environment Variables

Add or update:
- `REGISTRY_ADDRESS` — new CredentialRegistry address: `0xbF9b2556e6Dd64D60E08E3669CeF2a4293e006db` (same on both Base mainnet 8453 and Sepolia 84532)
- `REGISTRY_ADDRESS` — new CredentialRegistry address: `0x17a22f130d4e1c4ba5C20a679a5a29F227083A62` (same on both Base mainnet 8453 and Sepolia 84532)

## No Changes Required

Expand Down
4 changes: 2 additions & 2 deletions docs/migration-instructions/verify-proof-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ Contract addresses are identical on both chains (same deployer, same nonce).
```diff
export const chainRegistries: Record<number, string[]> = {
- 84532: ['0x0b2Ab187a6FD2d2F05fACc158611838c284E3a9c'],
+ 84532: ['0xbF9b2556e6Dd64D60E08E3669CeF2a4293e006db'],
+ 8453: ['0xbF9b2556e6Dd64D60E08E3669CeF2a4293e006db'],
+ 84532: ['0x17a22f130d4e1c4ba5C20a679a5a29F227083A62'],
+ 8453: ['0x17a22f130d4e1c4ba5C20a679a5a29F227083A62'],
}
```

Expand Down
Loading