A validator contract that integrates BringID's privacy-preserving identity verification with EIP-8004 Trustless Agents, enabling Sybil resistance for AI agents by proving the uniqueness of the human operator behind each agent.
Full Specification: See docs/SPEC.md for the complete technical specification.
About BringID: See docs/bringid-technical-overview.md for how BringID verification works.
The core approach: Each BringID credential becomes a separate EIP-8004 validation:
response= credential score (0-100)responseHash= nullifier (for Sybil tracking)
Consuming apps sum scores from all validations and track nullifiers to prevent reuse across agents. No new EIP-8004 contracts or interfaces are required.
┌─────────────────────────────────────────────────────────────────────────┐
│ EIP-8004 Validation Registry │
│ │
│ validationRequest(validator, agentId, requestURI, requestHash) │
│ validationResponse(requestHash, response, "", responseHash, tag) │
│ │
│ getValidationStatus() returns: │
│ (validatorAddress, agentId, response, responseHash, tag, lastUpdate) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
▲ ▲ │
│ │ │
validationRequest validationResponse getValidationStatus()
(per proof) (per proof) getAgentValidations()
└────────┬────────┘ │
│ (atomic) │
┌──────────────────────┴──────────┐ ┌───────────▼───────────────────┐
│ BringID Validator │ │ Consuming Apps │
│ │ │ │
│ validate(agentId, proof) │ │ • Query validations │
│ validateBatch(agentId, proofs) │ │ • Sum scores (response) │
│ │ │ • Track nullifiers │
│ Per proof (atomic): │ │ (responseHash) │
│ 1. Validate proof │ │ │
│ 2. Get credential score │ │ │
│ 3. Encode proof → requestURI │ │ │
│ 4. Call validationRequest │ │ │
│ 5. Call validationResponse │ │ │
│ │ │ │
└───────────┬─────────────────────┘ └───────────────────────────────┘
│
▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ BringID CredentialRegistry│ │ EIP-8004 Identity Registry│
│ (Semaphore) │ │ (ERC-721) │
│ │ │ │
│ • validateProof() │ │ • setApprovalForAll() │
│ • credentialGroupScore() │ │ (approve validator) │
│ • Nullifier tracking │ │ │
└───────────────────────────┘ └───────────────────────────┘
The operator proves control of real web accounts (GitHub, Uber, Airbnb, etc.) using BringID verification. Each verified account belongs to a credential group with an associated score and Semaphore proof.
The agent owner approves the BringIDValidator contract as an operator on the Identity Registry:
identityRegistry.setApprovalForAll(bringIdValidator, true);Submit all credentials in a single transaction:
// Submit all proofs atomically - if any fails, entire transaction reverts
bringIdValidator.validateBatch(agentId, proofs);
// Or submit one at a time
bringIdValidator.validate(agentId, proof);Apps use EIP-8004's getValidationStatus() to get scores AND nullifiers directly:
bytes32[] memory hashes = validationRegistry.getAgentValidations(agentId);
(address validator, uint256 agentId, uint8 response, bytes32 responseHash, string memory tag, uint256 lastUpdate)
= validationRegistry.getValidationStatus(requestHash);
// responseHash = nullifier for Sybil trackingbringid-validator-8004/
├── src/
│ ├── BringIDValidator8004.sol # Main validator contract
│ └── interfaces/
│ ├── IValidationRegistry.sol # EIP-8004 interface
│ └── ICredentialRegistry.sol # BringID interface
├── test/
│ ├── BringIDValidator8004.t.sol # Comprehensive tests
│ └── mocks/
│ ├── MockValidationRegistry.sol
│ └── MockCredentialRegistry.sol
├── script/
│ └── Deploy.s.sol # Deployment scripts
├── docs/
│ └── SPEC.md # Full specification
├── foundry.toml
└── README.md
git clone https://github.com/BringID/bringid-validator-8004.git
cd bringid-validator-8004
forge installforge build# Run all tests
forge test
# Run tests with verbosity
forge test -vvv
# Run with gas reporting
forge test --gas-report
# Check coverage
forge coverageCreate a .env file:
PRIVATE_KEY=<your-deployer-private-key>
VALIDATION_REGISTRY=<eip8004-validation-registry-address>
CREDENTIAL_REGISTRY=<bringid-credential-registry-address>source .env
forge script script/Deploy.s.sol:Deploy --rpc-url $RPC_URL --broadcast --verifyimport { BringID } from "bringid";
import { ethers } from "ethers";
async function registerWithSybilResistance() {
const bringid = new BringID();
const signer = await getSigner();
// 1. Register agent via EIP-8004 Identity Registry (ERC-721)
const identityRegistry = new ethers.Contract(IDENTITY_REGISTRY, IDENTITY_ABI, signer);
const tx = await identityRegistry.register("ipfs://Qm...");
const receipt = await tx.wait();
const agentId = parseAgentId(receipt);
// 2. Generate credential proofs with agentId as message (frontrun protection)
const proofs = await bringid.generateProofs({ message: agentId });
// 3. Approve BringIDValidator as operator (one-time)
await identityRegistry.setApprovalForAll(BRINGID_VALIDATOR, true);
// 4. Submit all validations in one transaction (atomic)
const bringidValidator = new ethers.Contract(BRINGID_VALIDATOR, BRINGID_ABI, signer);
await bringidValidator.validateBatch(agentId, proofs);
return agentId;
}Install the SDK:
npm install @bringid/validator8004 viemQuery agent scores:
import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";
import { Validator8004Client } from "@bringid/validator8004";
const client = createPublicClient({
chain: mainnet,
transport: http(),
});
const validator8004 = new Validator8004Client(client, {
validationRegistry: VALIDATION_REGISTRY,
bringIdValidator: BRINGID_VALIDATOR,
});
// Get score and nullifiers for an agent
const { totalScore, nullifiers } = await validator8004.getAgentScore(BigInt(agentId));Agents with BringID verification should include "bringid-operator-humanity" in their supportedTrust array:
{
"type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
"name": "MyTradingAgent",
"description": "DeFi strategy executor with verified operator",
"supportedTrust": [
"reputation",
"bringid-operator-humanity"
]
}Validates a single BringID credential and submits to EIP-8004.
Validates multiple BringID credentials atomically. If any proof fails, the entire transaction reverts.
event OperatorHumanityVerified(
uint256 indexed agentId,
bytes32 indexed requestHash,
uint256 credentialGroupId,
uint256 score,
bytes32 nullifier
);-
Frontrunning Protection: The Semaphore proof's
messagefield must equal theagentId. This cryptographically binds each proof to a specific agent, preventing attackers from stealing proofs from the mempool and using them for different agents. -
One Credential = One Agent: Each credential (nullifier) can only be used for one agent ever. The CredentialRegistry uses
context=0(constant) for global nullifier tracking. -
Atomic Execution: Both
validate()andvalidateBatch()are atomic. For batch operations, if any proof fails validation, the entire transaction reverts. -
Operator Approval: Agent owners must approve the BringIDValidator as an operator on the Identity Registry.
-
Proof Auditability: Each proof is encoded as a base64 data URI (
requestURI) withrequestHash = keccak256(requestURI). -
Nullifier in responseHash: The nullifier is stored in EIP-8004's
responseHashfield, queryable viagetValidationStatus(). Consuming apps can track nullifiers across their platform. -
Validation Age: Apps should check
lastUpdatefromgetValidationStatus()and may reject stale validations. -
Privacy Preservation: BringID verification happens off-chain. Only scores and nullifiers are stored on-chain.
- EIP-8004: Trustless Agents
- BringID Technical Overview
- BringID Whitepaper
- BringID CredentialRegistry
- BringID SDK
- Semaphore Protocol
MIT