Blockchain & Smart Contract
Every Tectra-signed image is eventually anchored on the Polygon blockchain via a Merkle tree batch. This creates a permanent, tamper-proof, independently verifiable record of provenance.
Why Polygon?
EVM-compatible
Standard Solidity smart contracts. Auditable by any Ethereum developer.
Low cost
Transaction fees are fractions of a cent, enabling affordable batch anchoring.
Immutable
Once written, records cannot be altered or deleted.
Independent
Anyone can query the contract directly - no Tectra servers required.
Merkle Batch Anchoring
Individual blockchain transactions per image would be expensive. Instead, Tectra batches thousands of image hashes into a single Merkle tree and anchors only the root hash on-chain. Every image still gets a verifiable on-chain proof.
Images hashed this batch:
hash_A = sha256(image_1)
hash_B = sha256(image_2)
hash_C = sha256(image_3)
hash_D = sha256(image_4)
Merkle tree:
root
/ \
AB_hash CD_hash
/ \ / \
hash_A hash_B hash_C hash_D
On-chain: registerBatch(root, 4, signer_pubkey, timestamp)
Proof for image_1:
[ hash_B, CD_hash ] ← sibling nodes up to root
Verification: hash(hash(hash_A, hash_B), CD_hash) == rootEach content record stores its Merkle proof path in PostgreSQL. During verification, the proof is re-computed against the on-chain root - fully trustless.
Smart Contract: ProvenanceRegistry.sol
The contract is deployed on Polygon (Amoy testnet by default). It stores two types of records:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract ProvenanceRegistry {
struct ContentRecord {
bytes32 contentHash;
bytes32 perceptualHash;
bytes signerPubKey;
uint256 timestamp;
address registeredBy;
}
struct BatchRecord {
bytes32 merkleRoot;
uint256 leafCount;
bytes signerPubKey;
uint256 timestamp;
address registeredBy;
}
mapping(bytes32 => ContentRecord) public contentByHash;
mapping(bytes32 => ContentRecord) public contentByPerceptualHash;
mapping(bytes32 => BatchRecord) public batches;
event ContentRegistered(
bytes32 indexed contentHash,
bytes signerPubKey,
uint256 timestamp
);
event BatchRegistered(
bytes32 indexed merkleRoot,
uint256 leafCount,
bytes signerPubKey,
uint256 timestamp
);
// Single content registration (legacy)
function registerContent(
bytes32 contentHash,
bytes32 perceptualHash,
bytes calldata signerPubKey,
uint256 timestamp
) external { ... }
// Batch registration (Merkle root)
function registerBatch(
bytes32 merkleRoot,
uint256 leafCount,
bytes calldata signerPubKey,
uint256 timestamp
) external { ... }
// Read functions - no auth required
function getByContentHash(bytes32 h) external view returns (ContentRecord memory) { ... }
function getByPerceptualHash(bytes32 h) external view returns (ContentRecord memory) { ... }
function getBatch(bytes32 root) external view returns (BatchRecord memory) { ... }
}Verify On-Chain Independently
You can verify any record directly on Polygon - no Tectra servers needed:
from web3 import Web3
import json
# Connect to Polygon
w3 = Web3(Web3.HTTPProvider("https://polygon-rpc.com"))
# Load contract ABI
with open("ProvenanceRegistry_abi.json") as f:
abi = json.load(f)
contract = w3.eth.contract(
address="0xYOUR_CONTRACT_ADDRESS",
abi=abi,
)
# Look up by content hash
import hashlib
with open("image.jpg", "rb") as f:
content_hash = hashlib.sha256(f.read()).hexdigest()
record = contract.functions.getByContentHash(
bytes.fromhex(content_hash)
).call()
print(f"Timestamp: {record['timestamp']}")
print(f"Signer: {record['signerPubKey'].hex()}")# Or via PolygonScan API
curl "https://api.polygonscan.com/api?module=contract&action=call& to=0xCONTRACT&data=0xGETBYHASH_CALLDATA&apikey=YourApiKey"Deploy Your Own Contract
To deploy on mainnet Polygon or a private chain:
# From the backend directory
cd backend
source venv/bin/activate
# Set your deployer wallet
export DEPLOYER_PRIVATE_KEY="0x..."
export POLYGON_RPC_URL="https://polygon-rpc.com" # mainnet
# or: https://rpc-amoy.polygon.technology (testnet)
# Deploy
PYTHONPATH=. python -m app.contracts.deploy
# Output:
# Deploying ProvenanceRegistry...
# Contract deployed at: 0x...
# TX hash: 0x...
# Add CONTRACT_ADDRESS=0x... to your .envYou need testnet MATIC for Amoy, or mainnet MATIC for Polygon mainnet. Get testnet MATIC from the Polygon Faucet.
Blockchain Transaction Status
After signing, the blockchain_status field progresses through states:
pendingRecord is in the Redis batch queue, awaiting the next Celery flush (~10 seconds).
anchoredMerkle batch was submitted to Polygon. TX hash is recorded in the database.
confirmedTransaction received sufficient block confirmations on Polygon.
Next: Verification Guide
Understanding confidence scores and the 4-check verification system.