| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
A comprehensive Ethereum smart contract system for managing user consent and data provenance on-chain. The system provides GDPR-compliant consent tracking, Kantara Initiative-compliant consent receipts, and complete data lineage management.
Testnet deployment: The DataProvenance contract is currently deployed on Base Sepolia (testnet). The other contracts in this repository (ConsentReceipt, Kantara, etc.) are available for local development but not yet deployed on a public network.
The system consists of interconnected smart contracts organized in layers:
┌─────────────────────────────────────────────────────────────────┐
│ Application Layer │
├─────────────────────────────────────────────────────────────────┤
│ IntegratedConsentProvenanceSystem │
│ (Coordinator linking consent verification with data operations)│
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ConsentReceipt │ │DataProvenance │ │ Supporting │
│ (Basic) │ │ (Tracking) │ │ Contracts │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Kantara │ │DataAccessCtrl │ │ ConsentProxy │
│ConsentReceipt │ │ │ │ (Delegation) │
└───────────────┘ └───────────────┘ └───────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ DataDeletion │ │ AuditLog │ │PurposeRegistry│
└───────────────┘ └───────────────┘ └───────────────┘
| Contract | Purpose |
|---|---|
| ConsentReceipt | Basic consent tracking with EIP-712 meta-transactions |
| KantaraConsentReceipt | Full Kantara Initiative-compliant consent receipts |
| DataProvenance | Data ownership, transformation, and access tracking with RBAC |
| IntegratedConsentProvenanceSystem | Coordinator enforcing consent before data operations |
| Contract | Purpose |
|---|---|
| DataAccessControl | Granular access level management (Read/Transform/Full) |
| ConsentProxy | Purpose-scoped delegation with time limits |
| ConsentAuditLog | Immutable audit trail for compliance |
| DataDeletion | GDPR right-to-erasure with deletion proofs |
| PurposeRegistry | Standardized purpose definitions |
# Clone the repository
git clone https://github.com/datafund/ConsentsBasedDataProvenance.git
cd ConsentsBasedDataProvenance
# Install dependencies
npm install
# Compile contracts
npm run compile# Start the Hardhat node
docker-compose up -d
# Compile contracts
docker-compose exec hardhat npx hardhat compile
# Run tests
docker-compose exec hardhat npx hardhat test# Using npm
npm run node
# Using Docker
docker-compose up# Using npm
npm run deploy:local
# Using Docker
docker-compose exec hardhat npx hardhat run scripts/deploy.ts --network localhostimport { ethers } from "hardhat";
// Get deployed contracts
const consentReceipt = await ethers.getContractAt("ConsentReceipt", CONSENT_ADDRESS);
const dataProvenance = await ethers.getContractAt("DataProvenance", PROVENANCE_ADDRESS);
// Give consent for a purpose
await consentReceipt.giveConsent("analytics");
// Register data with consent verification
const dataHash = ethers.keccak256(ethers.toUtf8Bytes("my-data"));
await integratedSystem.registerDataWithConsent(dataHash, "document", "analytics");Basic consent management with purpose-based tracking and EIP-712 signature support.
// Give consent for a purpose
function giveConsent(string memory _purpose) public;
function giveConsent(string memory _purpose, uint256 _expiryTime) public;
// Give consent via meta-transaction (gasless)
function giveConsentBySig(
address _user,
string memory _purpose,
uint256 _expiryTime,
uint256 _deadline,
uint8 _v, bytes32 _r, bytes32 _s
) public;
// Revoke consent
function revokeConsent(uint256 _index) public;
// Check consent status
function getConsentStatus(address _user, string memory _purpose) public view returns (bool);
// Batch operations
function batchGiveConsent(string[] memory _purposes, uint256[] memory _expiryTimes) public;
function batchRevokeConsent(uint256[] memory _indices) public;Full Kantara Initiative specification with 30 consent types and 70+ purpose categories.
// Give comprehensive consent
function giveConsent(
address _dataController,
Purpose[] memory _purposes,
bytes32[] memory _piCategories,
ConsentType _consentType,
uint256 _expiryTime,
bool _thirdPartyDisclosure,
string memory _policyUrl
) public returns (bytes32 receiptId);
// Check consent validity
function hasValidConsent(
address _dataSubject,
address _dataController,
Purpose _purpose
) public view returns (bool);
// Revoke consent
function revokeConsent(bytes32 _receiptId) public;Consent Types Include:
Purpose Categories Include:
Data lineage tracking with RBAC, delegated ownership, and bidirectional lineage traversal.
// Register data
function registerData(bytes32 _dataHash, string memory _dataType) public;
function registerData(bytes32 _dataHash, string memory _dataType, bytes32 _storageRef) public;
function registerDataFor(bytes32 _dataHash, string memory _dataType, address _actualOwner) public;
function registerDataFor(bytes32 _dataHash, string memory _dataType, address _actualOwner, bytes32 _storageRef) public;
// Storage reference lookup (e.g. Swarm hash → content hash)
function getDataHashByStorageRef(bytes32 _storageRef) public view returns (bytes32);
// Set storage reference after registration (set-once, immutable after)
function setStorageRef(bytes32 _dataHash, bytes32 _storageRef) public;
// Record data transformation (single source → derived output)
function recordTransformation(
bytes32 _originalDataHash,
bytes32 _newDataHash,
string memory _transformation
) public;
// Record merge/join transformation (multiple sources → single output)
function recordMergeTransformation(
bytes32[] memory _sourceDataHashes,
bytes32 _newDataHash,
string memory _transformation,
string memory _newDataType
) public;
// Lineage traversal
function getTransformationLinks(bytes32 _dataHash) public view returns (TransformationLink[] memory); // Forward: parent → children (with descriptions)
function getChildHashes(bytes32 _dataHash) public view returns (bytes32[] memory); // Forward: parent → children (hashes only)
function getTransformationParents(bytes32 _dataHash) public view returns (bytes32[] memory); // Reverse: child → parents
// Record data access
function recordAccess(bytes32 _dataHash) public;
// Ownership management
function transferDataOwnership(bytes32 _dataHash, address _newOwner) public;
function setDataStatus(bytes32 _dataHash, DataStatus _newStatus) public;
// Delegation
function setDelegate(address _delegate, bool _authorized) public;
// RBAC
function grantRole(bytes32 role, address account) public; // Admin only
function revokeRole(bytes32 role, address account) public; // Admin only
function operatorSetDataStatus(bytes32 _dataHash, DataStatus _newStatus) public; // Operator role
// Batch operations
function batchRegisterData(bytes32[] memory _dataHashes, string[] memory _dataTypes) public;
function batchRegisterData(bytes32[] memory _dataHashes, string[] memory _dataTypes, bytes32[] memory _storageRefs) public;
function batchRecordAccess(bytes32[] memory _dataHashes) public;Lineage Traversal:
| Direction | Method | Returns |
|---|---|---|
| Forward (parent → children) | getChildHashes(hash) | bytes32[] of child hashes |
| Forward (with descriptions) | getTransformationLinks(hash) | TransformationLink[] (hash + description) |
| Reverse (child → parents) | getTransformationParents(hash) | bytes32[] — 1 element for transforms, N for merges, empty for roots |
Data Statuses:
Roles:
Enforces consent verification before all data operations.
// Consent-verified operations
function registerDataWithConsent(
bytes32 _dataHash,
string memory _dataType,
string memory _consentPurpose
) public;
function registerDataWithConsent(
bytes32 _dataHash,
string memory _dataType,
string memory _consentPurpose,
bytes32 _storageRef
) public;
function registerDataForWithConsent(
bytes32 _dataHash,
string memory _dataType,
string memory _consentPurpose,
address _actualOwner
) public;
function registerDataForWithConsent(
bytes32 _dataHash,
string memory _dataType,
string memory _consentPurpose,
address _actualOwner,
bytes32 _storageRef
) public;
function accessDataWithConsent(bytes32 _dataHash, string memory _consentPurpose) public;
function transformDataWithConsent(
bytes32 _originalDataHash,
bytes32 _newDataHash,
string memory _transformation,
string memory _consentPurpose
) public;
function mergeDataWithConsent(
bytes32[] memory _sourceDataHashes,
bytes32 _newDataHash,
string memory _transformation,
string memory _newDataType,
string memory _consentPurpose
) public;
// Restrict data after consent revocation
function restrictDataForPurpose(string memory _consentPurpose) public;// User gives consent for marketing
await consentReceipt.connect(user)["giveConsent(string)"]("marketing");
// Check if consent exists
const hasConsent = await consentReceipt.getConsentStatus(user.address, "marketing");
console.log("Has marketing consent:", hasConsent); // true
// Revoke consent
await consentReceipt.connect(user).revokeConsent(0);// Give consent that expires in 30 days
const thirtyDays = 30 * 24 * 60 * 60;
const block = await ethers.provider.getBlock("latest");
const expiryTime = block.timestamp + thirtyDays;
await consentReceipt.connect(user)["giveConsent(string,uint256)"]("analytics", expiryTime);// User signs consent off-chain
const domain = {
name: "ConsentReceipt",
version: "1",
chainId: (await ethers.provider.getNetwork()).chainId,
verifyingContract: await consentReceipt.getAddress()
};
const types = {
Consent: [
{ name: "user", type: "address" },
{ name: "purpose", type: "string" },
{ name: "expiryTime", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" }
]
};
const nonce = await consentReceipt.getNonce(user.address);
const deadline = Math.floor(Date.now() / 1000) + 3600;
const value = {
user: user.address,
purpose: "analytics",
expiryTime: 0,
nonce: nonce,
deadline: deadline
};
const signature = await user.signTypedData(domain, types, value);
const sig = ethers.Signature.from(signature);
// Relayer submits on behalf of user
await consentReceipt.connect(relayer).giveConsentBySig(
user.address,
"analytics",
0,
deadline,
sig.v, sig.r, sig.s
);// User gives consent
await consentReceipt.connect(user)["giveConsent(string)"]("analytics");
// Register data (requires consent)
const dataHash = ethers.keccak256(ethers.toUtf8Bytes("user-document-content"));
await integratedSystem.connect(user).registerDataWithConsent(
dataHash,
"document",
"analytics"
);
// Track transformation
const newHash = ethers.keccak256(ethers.toUtf8Bytes("transformed-content"));
await integratedSystem.connect(user).transformDataWithConsent(
dataHash,
newHash,
"anonymization",
"analytics"
);// Owner authorizes delegate
await dataProvenance.connect(owner).setDelegate(delegate.address, true);
await dataProvenance.connect(owner).setDelegate(await integratedSystem.getAddress(), true);
// Owner gives consent
await consentReceipt.connect(owner)["giveConsent(string)"]("research");
// Delegate registers data on owner's behalf
const dataHash = ethers.keccak256(ethers.toUtf8Bytes("research-data"));
await integratedSystem.connect(delegate).registerDataForWithConsent(
dataHash,
"research-data",
"research",
owner.address
);// Give comprehensive consent following Kantara specification
const tx = await kantaraConsent.connect(user).giveConsent(
dataControllerAddress,
[0, 1], // ServiceProvision, ContractFulfillment
[ethers.keccak256(ethers.toUtf8Bytes("email")), ethers.keccak256(ethers.toUtf8Bytes("name"))],
10, // ExplicitAffirmative consent type
expiryTime,
true, // Allow third-party disclosure
"https://example.com/privacy-policy"
);
const receipt = await tx.wait();
const receiptId = receipt.logs[0].args[0]; // Get receipt ID from event
// Verify consent
const isValid = await kantaraConsent.hasValidConsent(
user.address,
dataControllerAddress,
0 // ServiceProvision
);The examples/ directory contains 40+ runnable TypeScript scripts demonstrating real-world usage across multiple industries:
| Category | Examples | Description |
|---|---|---|
| Getting Started | 4 | Basic consent, expiry, revocation, integrated system |
| Healthcare | 5 | Patient consent, HIPAA, medical records, deletion |
| Financial Services | 5 | KYC, credit checks, regulatory reporting |
| Marketing | 5 | Email consent, cookies, GDPR/CCPA compliance |
| Research | 4 | Study enrollment, anonymization, multi-institution |
| IoT & Supply Chain | 4 | Device registration, sensor data, manufacturing |
| Advanced Patterns | 5 | Meta-transactions, delegation, RBAC, batch ops |
| Compliance & Audit | 5 | GDPR, Kantara, audit queries, breach investigation |
| Integration Patterns | 4 | Events, backend services, error handling |
# Start local blockchain
docker-compose up -d
# Run any example via npm script
npm run example:basic-consent
npm run example:healthcare:hipaa
npm run example:compliance:gdpr
npm run example:integration:events
# Or run directly
npx hardhat run examples/01-getting-started/04-integrated-system.ts --network localhostSee examples/README.md for the complete guide.
# Using npm
npm test
# Using Docker
docker-compose exec hardhat npx hardhat test# Run security tests
npx hardhat test test/Security.test.ts
# Run integration tests
npx hardhat test test/Integration.test.ts
# Run with gas reporting
REPORT_GAS=true npx hardhat testnpm run test:coveragetest/ ├── ConsentReceipt.test.ts # Basic consent tests ├── KantaraConsentReceipt.test.ts # Kantara compliance tests ├── DataProvenance.test.ts # Data tracking tests ├── IntegratedConsentProvenanceSystem.test.ts ├── Integration.test.ts # End-to-end tests ├── Security.test.ts # Security/attack tests ├── ConsentAuditLog.test.ts ├── ConsentProxy.test.ts ├── DataAccessControl.test.ts ├── DataDeletion.test.ts └── PurposeRegistry.test.ts
Current Test Count: 299 tests
All contracts enforce limits to control gas costs and prevent abuse. These are per-record or per-call — they don't limit the overall system size.
| Limit | Value | Meaning |
|---|---|---|
| MAX_TRANSFORMATIONS | 100 | Max transformations branching from a single data record. Each new version is its own record with its own budget, so provenance chains can be arbitrarily deep. |
| MAX_MERGE_SOURCES | 50 | Max source datasets in a single recordMergeTransformation call. |
| MAX_ACCESSORS | 1000 | Max unique addresses recorded as accessors of a single data record. Duplicates are deduplicated automatically. |
| MAX_ACCESS_DURATION | 2 years | Longest access grant via DataAccessControl. |
| MAX_DELEGATION_DURATION | 1 year | Longest consent delegation via ConsentProxy. |
| Field | Max Length |
|---|---|
| dataType | 64 chars |
| transformation | 256 chars |
| purpose (ConsentReceipt) | 256 chars |
| policyUrl (KantaraConsentReceipt) | 512 chars |
| Operation | Max Items per Call |
|---|---|
| batchRegisterData | 50 |
| batchRecordAccess | 100 |
| batchSetDataStatus | 50 |
| batchGiveConsent | 50 |
| batchRevokeConsent | 50 |
| Kantara batchRevokeConsent | 50 |
| Kantara batchCheckConsent | 100 |
| Kantara purposes per receipt | 20 |
| Kantara PI categories per receipt | 50 |
Input Validation
Bounded Arrays
Access Control
Replay Protection
State Management
The security test suite (test/Security.test.ts) includes 91 tests covering:
event ConsentGiven(address indexed user, string purpose, uint256 timestamp, uint256 expiryTime);
event ConsentRevoked(address indexed user, string purpose, uint256 timestamp);
event ConsentGivenBySig(address indexed user, string purpose, address indexed relayer);event DataRegistered(bytes32 indexed dataHash, address indexed owner, string dataType);
event StorageRefLinked(bytes32 indexed dataHash, bytes32 indexed storageRef);
event DataTransformed(bytes32 indexed originalDataHash, bytes32 indexed newDataHash, string transformation);
event DataMerged(bytes32 indexed newDataHash, bytes32[] sourceDataHashes, string transformation);
event DataAccessed(bytes32 indexed dataHash, address indexed accessor);
event DataStatusChanged(bytes32 indexed dataHash, DataStatus oldStatus, DataStatus newStatus);
event DataOwnershipTransferred(bytes32 indexed dataHash, address indexed previousOwner, address indexed newOwner);
event DelegateAuthorized(address indexed owner, address indexed delegate, bool authorized);
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);event ConsentGiven(bytes32 indexed receiptId, address indexed dataSubject, address indexed dataController, uint256 expiryTime);
event ConsentRevoked(bytes32 indexed receiptId, address indexed dataSubject);
event ConsentUpdated(bytes32 indexed receiptId, address indexed dataSubject);| Error | Contract | Meaning |
|---|---|---|
| Purpose cannot be empty | ConsentReceipt | Empty purpose string provided |
| Purpose too long | ConsentReceipt | Purpose exceeds 256 characters |
| Expiry must be in future | ConsentReceipt | Expiry timestamp is in the past |
| Invalid consent index | ConsentReceipt | Index out of bounds |
| Consent already revoked | ConsentReceipt | Attempting to revoke inactive consent |
| Signature expired | ConsentReceipt | EIP-712 signature deadline passed |
| Invalid signature | ConsentReceipt | Signature verification failed |
| Invalid data hash | DataProvenance | Zero hash provided |
| Data type cannot be empty | DataProvenance | Empty data type string |
| Data already registered | DataProvenance | Hash already exists |
| Not the owner | DataProvenance | Caller is not data owner |
| Data is not active | DataProvenance | Data status is Restricted or Deleted |
| Max transformations reached | DataProvenance | 100 transformation limit hit |
| Max accessors reached | DataProvenance | 1000 accessor limit hit |
| Storage ref already mapped | DataProvenance | Storage reference already linked to another data hash |
| Storage ref already set | DataProvenance | Storage reference is immutable once set (set-once) |
| Storage ref cannot equal data hash | DataProvenance | Storage reference must differ from the data hash |
| Invalid storage ref | DataProvenance | Storage reference cannot be zero hash |
| Storage refs length mismatch | DataProvenance | Batch storageRefs array length doesn't match data hashes |
| Not authorized delegate | DataProvenance | Caller not authorized to act for owner |
| AccessControl: admin role required | DataProvenance | Caller lacks admin role |
| No valid consent for this purpose | IntegratedSystem | Required consent not found |
| Consent still valid | IntegratedSystem | Cannot restrict while consent active |
# Legacy deploy script
npm run deploy:local
# Hardhat Ignition (deploys all 9 contracts with dependency ordering)
npm run node # Start local node in another terminal
npm run deploy:ignition:localSupported testnets: Sepolia, Gnosis Chiado, Base Sepolia.
# 1. Copy .env.example and configure
cp .env.example .env
# Edit .env: set TESTNET_DEPLOYER_PRIVATE_KEY and (optionally) RPC URLs / API keys
# 2. Deploy to a testnet
npm run deploy:ignition:sepolia
npm run deploy:ignition:chiado
npm run deploy:ignition:baseSepoliaIgnition deploys all 9 contracts in dependency order:
Deployed addresses are saved to ignition/deployments/chain-<chainId>/deployed_addresses.json and a human-readable summary is auto-generated at deployments.md.
Contracts can be verified on Blockscout (no API key needed) — verification is manual, not automatic. Base Sepolia and Chiado use Blockscout. Sepolia uses Etherscan (requires ETHERSCAN_API_KEY).
# Verify individual contracts on Blockscout (no API key needed)
npx hardhat verify --network baseSepolia <CONTRACT_ADDRESS>
npx hardhat verify --network chiado <CONTRACT_ADDRESS>
# Verify on Etherscan (requires ETHERSCAN_API_KEY in .env)
npx hardhat verify --network sepolia <CONTRACT_ADDRESS>See deployments.md for the auto-generated reference. Summary:
| Network | Contract | Address | Explorer |
|---|---|---|---|
| Base Sepolia | ConsentAuditLog | 0x656b9B4727098660B30cFd815cF46CEc24afA4fF | Blockscout |
| Base Sepolia | ConsentProxy | 0x5BC4bbe441955cb49641A1aC6F7a7741c90D2b59 | Blockscout |
| Base Sepolia | ConsentReceipt | 0x94C77AF951a767C4DF17AD167b1e55A2da97AAD4 | Blockscout |
| Base Sepolia | DataAccessControl | 0xDE1A5B2bB0Ad8927085104E88A66B036717E291b | Blockscout |
| Base Sepolia | DataDeletion | 0x7C2fF5E24f464038FcF19Ba0136d81C108CE5d7a | Blockscout |
| Base Sepolia | DataProvenance | 0x3945aDfd5Df9ab2F5cB4Ca0eb3D4384CC3650322 | Blockscout |
| Base Sepolia | IntegratedConsentProvenanceSystem | 0x94737bD199Fd7E5AdF4316615B246409d08397Ce | Blockscout |
| Base Sepolia | KantaraConsentReceipt | 0x66418Fa9b3644d567D77111079cB6FF9A0CBaDBf | Blockscout |
| Base Sepolia | PurposeRegistry | 0xB4b74917920b83B7f7f101B2CaAB288c4F21135D | Blockscout |
| Network | Contract | Address | Explorer | Notes |
|---|---|---|---|---|
| Base Sepolia | All contracts (v3) | See below | storageRef without setStorageRef | |
| Base Sepolia | DataProvenance (v3) | 0xb309dDe00819668ac74075813108659d4332D2cC | Blockscout | |
| Base Sepolia | IntegratedConsentProvenanceSystem (v3) | 0x4AC4bBB4E12C4450209C9b8C82a13428C2f21db5 | Blockscout | |
| Base Sepolia | All contracts (v2) | See below | Pre-storageRef | |
| Base Sepolia | DataProvenance (v2) | 0xD4a724CD7f5C4458cD2d884C2af6f011aC3Af80a | Blockscout | |
| Base Sepolia | DataProvenance (v1) | 0x9a3c6F47B69211F05891CCb7aD33596290b9fE64 | Blockscout | Pre-TransformationLink struct, standalone deploy |
ABIs are available from multiple sources:
MIT License - see LICENSE file for details.
| Back | FazBrowse Home | New Git URL |