This repository contains the code for the back-end of Fact Fortress, implemented in Ethereum.
The Fact Fortress framework incorporates smart contracts into its architecture to ensure transparency and accountability in data access. Certified data providers can securely store their sensitive data and set data access policies on how the data must be handled. Data analysts can request access to the data based on these policies to perform an analysis and compute the zero-knowledge proof (ZKP) locally or delegate the data analysis to the smart contract, which returns the ZKP and result directly to them. The framework defines a library of functions that can be computed on data of any form, and each function has an on-chain verifier that can validate a proof submitted by anyone. The verifier ensures that the proof was generated by the function claimed by the data analyst, the data used to generate the proof has not been tampered with, and the claimed result is the correct result of the function applied to the data. Analysts can confidently publish the results together with the proof, which anyone can publicly verify on-chain.
Fact Fortress is a blockchain-based framework that uses zero-knowledge proofs for trustworthy and private fact-checking. It ensures trustworthy data handling and computation by using proofs of data provenance and auditable data access policies. The solution democratizes circuit construction and deployment with a circuit compiler that supports various data formats and source authentication, and facilitates on-chain verification. This preserves sensitive data privacy while ensuring accountability and transparency in data handling and computation. It achieves this by enabling on-chain verification of computation and data provenance without revealing any information about the data itself.
Our framework provides a comprehensive solution that covers the entire process from circuit generation to proof generation, while facilitating collaboration among data analysts, data providers, external verifiers, and policy auditors.
For more information, check out our website at: https://pierg.github.io/fact-fortress-web/.
- Fact Fortress Dapp
- What is Fact Fortress
- Smart Contracts Backend
- Related Repositories
- Prerequisites
- Run
- Backend End-to-End Flows
- Flow 1. Generate and Verify a Proof
- 1 | Generate the public/private keys pair
- 2 | Authorize the data provider to upload its public key (On-Chain)
- 3 | Upload the public key (On-Chain)
- 3b (optional) | Get the public key (On-Chain)
- 4 | Hash and Sign Data
- 5 | Store the signature (On-Chain)
- 6 | Generate the Proof
- 7 | [ZKP::Proof of Provenance] Verify the Public Inputs (On-Chain)
- 8 | [ZKP::Proof of Provenance] Verify the Proof of Provenance (On-Chain)
- Flow 2. Manage Authorizations (NFTs)
- 1 | Check All Access Policies (Default Policy)
- 2 | Check Unauthorized Data Provider's Token ID (No Token)
- 3 | Check Unauthorized Data Analyst's Token ID (No Token)
- 4 | Authorize a Data Provider
- 5 | Set all data access policies
- 6 | Authorize a Data Analyst
- 7 | Check Authorized Data Analyst's Token ID
- 8 | Check All Access Policies
- Flow 1. Generate and Verify a Proof
- Circuits generator: https://github.com/pierg/fact-fortress-compiler
- Frontend: https://github.com/pierg/fact-fortress-web
- NodeJS LTS https://nodejs.org/en/download
- pnpm https://pnpm.io/fr/installation
- make
Install the backend and the frontend:
git clone [email protected]:pierg/fact-fortress-dapp.git
cd fact-fortress-dapp && pnpm install
git clone [email protected]:pierg/fact-fortress-frontend.git
cd fact-fortress-frontend && pnpm install
To deploy the backend and the frontend together, run the following command from the root directory:
make run
This command launches the backend, then opens the frontend in the browser (http://localhost:8080
).
From the root directory, run:
pnpm backend
(By default, the backend runs on port 3000
).
A Postman collection is provided to interact with the backend: tools/Postman_collection/Fact_Fortress.postman_collection.json
Once the backend is running (â–ş server started on port 3000 âś“
), from the root directory run:
pnpm frontend
Then, open http://localhost:8080
on your browser.
(By default, the backend runs on port 8080
).
From the root directory, run:
pnpm run test
These tests notably contain an end-to-end flow, from the authorization of data providers to the on-chain verification of the proof of Schnorr signature.
Data providers generate a private/public key pair based on the Grumpkin elliptic curve used by Noir.
WARNING: This action should be performed offline. This endpoint is just a helper. Data providers are expected to generate the keys themselves. |
---|
GET http://localhost:3000/key_pair
- Input
- (None)
- Output
public_key
Public key based on the Grumpkin curve, used by Noirprivate_key
Random private key
Example
curl --location 'http://localhost:3000/key_pair'
{
"public_key": "0x0dd7811f6af9d473c41376affb8660aba00e255c49844b31182f54bc0ab3e2ae1b23bd0e9afdb8275f880934b115057ed86f075048d4d8bd9fa8d92670dc6892",
"private_key": "ca1a2b52a7405f06f71c03cbaada78559aa86a0e2d01321540012f3762a12818"
}
Data providers have to be authorized to upload their public keys on the blockchain (otherwise, anyone could do it). To do so, an NFT-based mechanism is used. The owner of the NFT smart contract has to authorize data providers once by sending them NFTs for this purpose.
GET http://localhost:3000/authorize_provider
- Caller
- Owner of the contract
- Input
- (Header)
from: owner
Only the owner of the contract can mint - (Parameter)
address
Address of the data provider about to receive the NFT
- (Header)
- Output
address
Address of the data provider having received the NFTtoken_id
NFT token ID
Example
curl --location 'http://localhost:3000/authorize_provider?address=0x98526c571e324028250B0f5f247Ca4F1b575fadB' \
--header 'from: owner'
{
"address": "0x98526c571e324028250B0f5f247Ca4F1b575fadB",
"token_id": "1"
}
Data providers upload their public key (for the first time or when they generate a new one). This process enables the verification of the public inputs in the context of the proof of provenance.
PUT http://localhost:3000/publickey
- Caller
- Provider
- Input
- (Header)
from: owner
Only the data provider owner of an NFT can upload its public key - (Parameter)
name
Name associated with the public key - (Parameter)
public_key
Grumpkin-based public key
- (Header)
- Output
name
Name associated with the public keypublic_key
Grumpkin-based public keypublic_key_version
Version of the public key
Example
curl --location --request PUT 'http://localhost:3000/publickey?name=ABC&public_key=0x0dd7811f6af9d473c41376affb8660aba00e255c49844b31182f54bc0ab3e2ae1b23bd0e9afdb8275f880934b115057ed86f075048d4d8bd9fa8d92670dc6892' \
--header 'from: providerA'
{
"name": "ABC",
"public_key": "0x0dd7811f6af9d473c41376affb8660aba00e255c49844b31182f54bc0ab3e2ae1b23bd0e9afdb8275f880934b115057ed86f075048d4d8bd9fa8d92670dc6892",
"public_key_version": "0"
}
Using this endpoint, anyone (including the verifiers) can get the public keys of data providers.
GET http://localhost:3000/publickey
- Caller
- Anyone
- Input
- (Parameter)
name
Name associated with the public key - (Parameter)
version
Version of the public key
- (Parameter)
- Output
public_key
Grumpkin-based public key
Example
curl --location 'http://localhost:3000/publickey?name=ABC&version=0'
{
"public_key": "0x0dd7811f6af9d473c41376affb8660aba00e255c49844b31182f54bc0ab3e2ae1b23bd0e9afdb8275f880934b115057ed86f075048d4d8bd9fa8d92670dc6892"
}
Data providers have to (SHA-256) hash and sign (using the Grumpkin elliptic curve) the Data.
WARNING: This action should be performed offline. This endpoint is just a helper. Data providers are expected to hash and sign the Data themselves. |
---|
POST http://localhost:3000/sign_message
- Input
- (Body)
private_key
Private key associated with the public key that will be used for the proof - (Body)
message
Data to hash and sign
- (Body)
- Output
hash
SHA-256 hash of the data (hex)signature
Signature of the hash (bytes)
Example
curl --location 'http://localhost:3000/sign_message' \
--header 'Content-Type: application/json' \
--data '{
"private_key": "98f73670b22c67c1c2b092c5167d1317b661d82db9777751dd6b310efa7c4e17",
"message": {
"d1": [ 2, 1, 2, 0, 0, 0, 2, 1, 2, 0, 0, 2, 0, 2, 2, 1, 1, 0, 0, 0, 2, 0, 0, 0, 1, 1, 2, 2, 0, 2, 0, 0],
"d2": [ 23, 5, 15, 29]
}
}'
{
"hash": "e51b88c9ef2ee7a084f676a4d07313895e2850f6789e1bb1aa9845c3d2dd6dea",
"signature": [
30,
149,
144,
. . .
10,
179,
103
]
}
Data providers store the signature on the blockchain. That enables the verification of the proof of provenance.
GET http://localhost:3000/upload_signature
- Caller
- Provider
- Input
- (Parameter)
public_key
Grumpkin-based public key signature
Signature of the hash (bytes)
- (Parameter)
- Output
stored
Status:true
if the signature has been stored,false
otherwise
Example
curl --location 'http://localhost:3000/upload_signature?public_key=0x077418dea85cb9695990062d64d4d4add4a4d8cbbed3a5f9e5d5f299766bcdf22a10a3540173df59a3e03533011d867c7a8d879dc3819c8c4857ef3a04a6b103' \
--header 'from: ProviderA' \
--header 'Content-Type: application/json' \
--data '{
"hash": "e51b88c9ef2ee7a084f676a4d07313895e2850f6789e1bb1aa9845c3d2dd6dea",
"signature": [
30,
149,
144,
. . .
10,
179,
103
]
}'
{
"stored": true
}
Data analysts generate the proof (should be done online).
WARNING: This action should be performed offline. This endpoint is just a helper. Data analysts are expected to generate the proofs themselves. |
---|
POST http://localhost:3000/generate_proof
- Input
- (Parameter)
public_key
Grumpkin-based public key - (Body)
hash
Data hash (hex; copied from step 4) - (Body)
signature
Signature of the hash (bytes; copied from step 4)
- (Parameter)
- Output
- Proof (bytes)
Example
curl --location 'http://localhost:3000/generate_proof?public_key=0x0dd7811f6af9d473c41376affb8660aba00e255c49844b31182f54bc0ab3e2ae1b23bd0e9afdb8275f880934b115057ed86f075048d4d8bd9fa8d92670dc6892' \
--header 'Content-Type: application/json' \
--data '{
"hash": "e51b88c9ef2ee7a084f676a4d07313895e2850f6789e1bb1aa9845c3d2dd6dea",
"signature": [
30,
149,
144,
. . .
10,
179,
103
]
}'
[
13,
215,
129,
. . .
71,
13,
23
]
Verifiers verify the public inputs of the proof of provenance. This is a preliminary step to the verification of the proof of provenance itself (step 8). This step ensures that the data analyst has used the expected public key and signature as public inputs. It can also be performed off-chain.
POST http://localhost:3000/verify_public_inputs
- Input
- (Parameter)
public_key
Grumpkin-based public key - (Body) Proof (bytes)
- (Parameter)
- Output
public_input_match
:true
(public inputs match) orfalse
(public inputs do not match)
Example
curl --location 'http://localhost:3000/verify_public_inputs?public_key=0x0dd7811f6af9d473c41376affb8660aba00e255c49844b31182f54bc0ab3e2ae1b23bd0e9afdb8275f880934b115057ed86f075048d4d8bd9fa8d92670dc6892' \
--header 'Content-Type: application/json' \
--data '[
13,
215,
129,
. . .
71,
13,
23
]'
{
"public_input_match": true
}
Verifiers verify the proof of provenance that ensures that the Data comes from a data provider.
POST http://localhost:3000/verify_public_inputs
- Input
- (Body) Proof (bytes)
- Output
valid_proof_of_provenance
:true
(valid proof) orfalse
(invalid proof)
Example
curl --location 'http://localhost:3000/verify_proof' \
--header 'Content-Type: application/json' \
--data '[
13,
215,
129,
. . .
71,
13,
23
]'
{
"valid_proof_of_provenance": true
}
WARNING: Before running this flow, ensure to reset the accounts and authorizations using the frontend helper (implemented for demonstration purposes only): GET http://localhost:3000/reset_accounts |
---|
Get registered access policies when no data analyst has been authorized yet: only the default access policy is returned.
GET http://localhost:3000/all_access_policies
- Input
- (None)
- Output
access_policies
List of all registered data policies
Example
curl --location 'http://localhost:3000/all_access_policies'
{
"access_policies": [
"default_policy"
]
}
An unauthorized data provider has no token ID
GET http://localhost:3000/provider_token_id
- Input
- (parameter)
address
Address of the data provider
- (parameter)
- Output
error
When the data provider is unauthorized:Address does not have a token
Example
curl --location 'http://localhost:3000/provider_token_id?address=0x98526c571e324028250B0f5f247Ca4F1b575fadB'
{
"error": "Address does not have a token"
}
An unauthorized data analyst has no token ID
GET http://localhost:3000/analyst_token_id
- Input
- (parameter)
address
Address of the data provider
- (parameter)
- Output
error
When the data provider is unauthorized:Address does not have a token
Example
curl --location 'http://localhost:3000/analyst_token_id?address=0x5455280E6c20A01de3e846d683562AdeA6891026'
{
"error": "Address does not have a token"
}
Authorize a data provider
GET http://localhost:3000/authorize_provider
- Input
- (header)
from: owner
Only the owner of the smart contract can call the underlying function - (parameter)
address
Address of the data provider
- (header)
- Output
address
Address of the data providertoken_id
ID of the NFT sent to the data provider
Example
curl --location 'http://localhost:3000/analyst_token_id?address=0x5455280E6c20A01de3e846d683562AdeA6891026'
{
"address": "0x98526c571e324028250B0f5f247Ca4F1b575fadB",
"token_id": "1"
}
Define all data access policies
POST http://localhost:3000/all_access_policies
- Input
- (header)
from: owner
Only the owner of the smart contract can call the underlying function - (body)
access_policies
Set of all access policies
- (header)
- Output
access_policies
Set of all access policies
Example
curl --location 'http://localhost:3000/all_access_policies' \
--header 'from: owner' \
--header 'Content-Type: application/json' \
--data '{
"access_policies": [
"TYPE_A",
"TYPE_B",
"TYPE_C",
]
}'
{
"access_policies": [
"TYPE_A",
"TYPE_B",
"TYPE_C",
]
}
Authorize a data analyst with a set of access policies
POST http://localhost:3000/authorize_analyst
- Input
- (header)
from: owner
Only the owner of the smart contract can call the underlying function - (parameter)
address
Address of the data analyst - (body)
access_policies
Set of access policies for this data analyst (should be a subset of all access policies)
- (header)
- Output
address
Address of the data providertoken_id
ID of the NFT sent to the data provider
Example
curl --location 'http://localhost:3000/authorize_analyst?address=0x5455280E6c20A01de3e846d683562AdeA6891026' \
--header 'from: owner' \
--header 'Content-Type: application/json' \
--data '{
"access_policies": [
"TYPE_A",
"TYPE_B",
"TYPE_C",
]
}'
{
"address": "0x98526c571e324028250B0f5f247Ca4F1b575fadB",
"token_id": "1"
}
Now that the data analyst has been approved, her token ID can be retrieved
GET http://localhost:3000/analyst_token_id
- Input
- (parameter)
address
Address of the data analyst
- (parameter)
- Output
address
Address of the data analysttoken_id
ID of the NFT sent to the data provideraccess_policies
Set of access policies for this data analyst (should be a subset of all access policies)
Example
curl --location 'http://localhost:3000/analyst_token_id?address=0x5455280E6c20A01de3e846d683562AdeA6891026'
{
"address": "0x5455280E6c20A01de3e846d683562AdeA6891026",
"token_id": "1",
"access_policies": [
"TYPE_A",
"TYPE_B",
"TYPE_C",
]
}
Now that at least on data analyst has been authorized, the set of all access policies has been updated by the smart contract
GET http://localhost:3000/all_access_policies
- Input
- (None)
- Output
access_policies
List of all registered data policies
Example
curl --location 'http://localhost:3000/all_access_policies'
{
"access_policies": [
"default_policy",
"TYPE_A",
"TYPE_B",
"TYPE_C",
]
}