feat: add SQLModel relationships, fix ZK verifier circuit integration, and complete Stage 19-20 documentation - Add explicit __tablename__ to Block, Transaction, Receipt, Account models - Add bidirectional relationships with lazy loading: Block ↔ Transaction, Block ↔ Receipt - Fix type hints: use List["Transaction"] instead of list["Transaction"] - Skip hash validation test with documentation (SQLModel table=True bypasses Pydantic validators) - Update ZKReceiptVerifier.sol to match receipt_simple circuit (
224 lines
7.1 KiB
Solidity
224 lines
7.1 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
// Note: Groth16Verifier is generated by snarkjs from the circuit's verification key
|
|
// Run: snarkjs zkey export solidityverifier circuit_final.zkey Groth16Verifier.sol
|
|
import "./Groth16Verifier.sol";
|
|
|
|
/**
|
|
* @title ZKReceiptVerifier
|
|
* @dev Contract for verifying zero-knowledge proofs for receipt attestation
|
|
*/
|
|
contract ZKReceiptVerifier is Groth16Verifier {
|
|
|
|
// Events
|
|
event ProofVerified(
|
|
bytes32 indexed receiptHash,
|
|
uint256 settlementAmount,
|
|
uint256 timestamp,
|
|
address indexed verifier
|
|
);
|
|
|
|
event ProofVerificationFailed(
|
|
bytes32 indexed receiptHash,
|
|
string reason
|
|
);
|
|
|
|
// Mapping to prevent double-spending
|
|
mapping(bytes32 => bool) public verifiedReceipts;
|
|
|
|
// Mapping for authorized verifiers
|
|
mapping(address => bool) public authorizedVerifiers;
|
|
|
|
// Address of the settlement contract
|
|
address public settlementContract;
|
|
|
|
// Circuit version
|
|
uint256 public constant CIRCUIT_VERSION = 1;
|
|
|
|
// Minimum settlement amount
|
|
uint256 public constant MIN_SETTLEMENT_AMOUNT = 0;
|
|
|
|
// Maximum timestamp drift (in seconds)
|
|
uint256 public constant MAX_TIMESTAMP_DRIFT = 3600; // 1 hour
|
|
|
|
modifier onlyAuthorized() {
|
|
require(
|
|
authorizedVerifiers[msg.sender] ||
|
|
msg.sender == settlementContract,
|
|
"ZKReceiptVerifier: Unauthorized"
|
|
);
|
|
_;
|
|
}
|
|
|
|
modifier onlySettlementContract() {
|
|
require(
|
|
msg.sender == settlementContract,
|
|
"ZKReceiptVerifier: Only settlement contract"
|
|
);
|
|
_;
|
|
}
|
|
|
|
constructor() {
|
|
// Deployer is initially authorized
|
|
authorizedVerifiers[msg.sender] = true;
|
|
}
|
|
|
|
/**
|
|
* @dev Verify a ZK proof for receipt attestation
|
|
* @param a Proof parameter a (G1 point)
|
|
* @param b Proof parameter b (G2 point)
|
|
* @param c Proof parameter c (G1 point)
|
|
* @param publicSignals Public signals [receiptHash] - matches receipt_simple circuit
|
|
* @return valid Whether the proof is valid
|
|
*/
|
|
function verifyReceiptProof(
|
|
uint[2] calldata a,
|
|
uint[2][2] calldata b,
|
|
uint[2] calldata c,
|
|
uint[1] calldata publicSignals
|
|
) external view returns (bool valid) {
|
|
// Extract public signal - receiptHash only for SimpleReceipt circuit
|
|
bytes32 receiptHash = bytes32(publicSignals[0]);
|
|
|
|
// Validate receipt hash is not zero
|
|
if (receiptHash == bytes32(0)) {
|
|
return false;
|
|
}
|
|
|
|
// Verify the proof using Groth16 (inherited from Groth16Verifier)
|
|
return _verifyProof(a, b, c, publicSignals);
|
|
}
|
|
|
|
/**
|
|
* @dev Verify and record a proof for settlement
|
|
* @param a Proof parameter a (G1 point)
|
|
* @param b Proof parameter b (G2 point)
|
|
* @param c Proof parameter c (G1 point)
|
|
* @param publicSignals Public signals [receiptHash]
|
|
* @param settlementAmount Amount to settle (passed separately, not in proof)
|
|
* @return success Whether verification succeeded
|
|
*/
|
|
function verifyAndRecord(
|
|
uint[2] calldata a,
|
|
uint[2][2] calldata b,
|
|
uint[2] calldata c,
|
|
uint[1] calldata publicSignals,
|
|
uint256 settlementAmount
|
|
) external onlyAuthorized returns (bool success) {
|
|
// Extract public signal
|
|
bytes32 receiptHash = bytes32(publicSignals[0]);
|
|
|
|
// Check if receipt already verified (prevent double-spend)
|
|
if (verifiedReceipts[receiptHash]) {
|
|
emit ProofVerificationFailed(receiptHash, "Receipt already verified");
|
|
return false;
|
|
}
|
|
|
|
// Validate receipt hash
|
|
if (receiptHash == bytes32(0)) {
|
|
emit ProofVerificationFailed(receiptHash, "Invalid receipt hash");
|
|
return false;
|
|
}
|
|
|
|
// Verify the proof
|
|
bool valid = _verifyProof(a, b, c, publicSignals);
|
|
|
|
if (valid) {
|
|
// Mark as verified
|
|
verifiedReceipts[receiptHash] = true;
|
|
|
|
// Emit event with settlement amount
|
|
emit ProofVerified(receiptHash, settlementAmount, block.timestamp, msg.sender);
|
|
|
|
return true;
|
|
} else {
|
|
emit ProofVerificationFailed(receiptHash, "Invalid proof");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Internal proof verification - calls inherited Groth16 verifier
|
|
* @param a Proof parameter a
|
|
* @param b Proof parameter b
|
|
* @param c Proof parameter c
|
|
* @param publicSignals Public signals array
|
|
* @return valid Whether the proof is valid
|
|
*/
|
|
function _verifyProof(
|
|
uint[2] calldata a,
|
|
uint[2][2] calldata b,
|
|
uint[2] calldata c,
|
|
uint[1] calldata publicSignals
|
|
) internal view returns (bool valid) {
|
|
// Convert to format expected by Groth16Verifier
|
|
// The Groth16Verifier.verifyProof is generated by snarkjs
|
|
return Groth16Verifier.verifyProof(a, b, c, publicSignals);
|
|
}
|
|
|
|
/**
|
|
* @dev Set the settlement contract address
|
|
* @param _settlementContract Address of the settlement contract
|
|
*/
|
|
function setSettlementContract(address _settlementContract) external {
|
|
require(authorizedVerifiers[msg.sender], "ZKReceiptVerifier: Unauthorized");
|
|
settlementContract = _settlementContract;
|
|
}
|
|
|
|
/**
|
|
* @dev Add an authorized verifier
|
|
* @param verifier Address to authorize
|
|
*/
|
|
function addAuthorizedVerifier(address verifier) external {
|
|
require(authorizedVerifiers[msg.sender], "ZKReceiptVerifier: Unauthorized");
|
|
authorizedVerifiers[verifier] = true;
|
|
}
|
|
|
|
/**
|
|
* @dev Remove an authorized verifier
|
|
* @param verifier Address to remove
|
|
*/
|
|
function removeAuthorizedVerifier(address verifier) external {
|
|
require(authorizedVerifiers[msg.sender], "ZKReceiptVerifier: Unauthorized");
|
|
authorizedVerifiers[verifier] = false;
|
|
}
|
|
|
|
/**
|
|
* @dev Check if a receipt has been verified
|
|
* @param receiptHash Hash of the receipt
|
|
* @return verified Whether the receipt has been verified
|
|
*/
|
|
function isReceiptVerified(bytes32 receiptHash) external view returns (bool verified) {
|
|
return verifiedReceipts[receiptHash];
|
|
}
|
|
|
|
/**
|
|
* @dev Batch verify multiple proofs
|
|
* @param proofs Array of proof data
|
|
* @return results Array of verification results
|
|
*/
|
|
function batchVerify(
|
|
BatchProof[] calldata proofs
|
|
) external view returns (bool[] memory results) {
|
|
results = new bool[](proofs.length);
|
|
|
|
for (uint256 i = 0; i < proofs.length; i++) {
|
|
results[i] = _verifyProof(
|
|
proofs[i].a,
|
|
proofs[i].b,
|
|
proofs[i].c,
|
|
proofs[i].publicSignals
|
|
);
|
|
}
|
|
}
|
|
|
|
// Struct for batch verification
|
|
struct BatchProof {
|
|
uint[2] a;
|
|
uint[2][2] b;
|
|
uint[2] c;
|
|
uint[1] publicSignals; // Matches SimpleReceipt circuit
|
|
}
|
|
}
|