An IETF SCRAPI-compatible transparency service implementation using SQLite as the immutable storage backend.
Scittles implements the SCITT (Supply Chain Integrity, Transparency and Trust) architecture, providing a distributed ledger service for registering and verifying cryptographically signed statements with Merkle tree inclusion proofs.
- SCRAPI-Compatible REST API: Full implementation of the SCRAPI transparency service endpoints
- RFC 9162 Merkle Trees: Cryptographic inclusion proofs with domain separation
- COSE Sign1 Receipts: ES256-signed receipts with verifiable data structure proofs
- SQLite Backend: Lightweight, append-only storage with async support
- Zero External Dependencies: No external databases or services required
# Clone the repository
git clone https://github.com/vcon-dev/scittles.git
cd scittles
# Create virtual environment
python3 -m venv .venv
source .venv/bin/activate
# Install dependencies
pip install -e .
# For development
pip install -e ".[dev]"# Start the transparency service
python -m src.main
# Or using the entry point
scittlesThe service starts on http://localhost:8000 by default.
Scittles can be run as a Docker container for easy deployment and isolation.
# Build and start the service
docker-compose up -d
# View logs
docker-compose logs -f
# Stop the service
docker-compose downThe service will be available at http://localhost:8000. The database is persisted in the ./data directory on the host.
# Build the image
docker build -t scittles:latest .
# Run the container
docker run -d \
--name scittles \
-p 8000:8000 \
-v $(pwd)/data:/app/data \
-e SCITT_SERVICE_URL=https://your-service-url.example \
scittles:latestConfigure the service using environment variables (prefix SCITT_):
Core Configuration:
SCITT_DB_PATH- Database file path (default:/app/data/transparency.db)SCITT_SERVICE_URL- Public service URL (required for production)SCITT_HOST- Bind address (default:0.0.0.0)SCITT_PORT- Server port (default:8000)
Observability Configuration:
SCITT_LOG_LEVEL- Logging level:DEBUG,INFO,WARNING,ERROR(default:INFO)SCITT_LOG_FORMAT- Log format:jsonortext(default:jsonin Docker)SCITT_OTEL_ENABLED- Enable OpenTelemetry (default:true)SCITT_OTEL_SERVICE_NAME- Service name for traces (default:scittles)SCITT_OTEL_EXPORTER- Comma-separated exporters:console,otlp,prometheus(default:prometheus,console)SCITT_OTEL_ENDPOINT- OTLP endpoint URL (e.g.,http://otel-collector:4317)SCITT_OTEL_HEADERS- OTLP headers as comma-separatedkey=valuepairsSCITT_PROMETHEUS_PORT- Prometheus port configuration (metrics served at/metricson main port)
When the Prometheus exporter is enabled (default in Docker), metrics are available at:
curl http://localhost:8000/metricsThe metrics endpoint exposes HTTP, database, Merkle tree, receipt, and entry registration metrics compatible with Prometheus scraping.
The service includes a health check endpoint:
curl http://localhost:8000/.well-known/transparency-configurationDocker Compose automatically configures health checks using this endpoint.
The database is stored in ./data/transparency.db on the host, ensuring data persists across container restarts. Make sure to back up this directory in production.
For more detailed Docker documentation, see docker/README.md.
Configure via environment variables (prefix SCITT_):
| Variable | Default | Description |
|---|---|---|
SCITT_DATABASE_PATH |
scittles.db |
SQLite database file path |
SCITT_SERVICE_URL |
https://transparency.example |
Public service URL |
SCITT_HOST |
0.0.0.0 |
Server bind address |
SCITT_PORT |
8000 |
Server port |
GET /.well-known/transparency-configurationReturns CBOR-encoded service configuration including registration and receipt endpoints.
POST /entries
Content-Type: application/cose
<COSE_Sign1 message>Registers a COSE Sign1 signed statement. Returns:
201 Created: Registration successful, receipt in response body303 See Other: Registration pending (async processing)400 Bad Request: Invalid statement or already registered
Response includes Location header with the entry URL.
GET /entries/{entry_id}Returns a fresh COSE receipt with the current Merkle tree state and inclusion proof.
GET /signed-statements/{entry_id}Returns the original COSE Sign1 signed statement.
scittles/
├── src/
│ ├── api/ # FastAPI REST endpoints
│ │ ├── endpoints.py
│ │ └── models.py
│ ├── core/ # Cryptographic operations
│ │ ├── merkle.py # RFC 9162 Merkle tree
│ │ ├── receipts.py # COSE receipt generation
│ │ └── verification.py
│ ├── storage/ # Persistence layer
│ │ ├── base.py # Abstract interface
│ │ ├── sqlite_store.py
│ │ └── schema.sql
│ ├── config.py # Configuration management
│ └── main.py # Application entry point
├── examples/ # Client integration examples
│ ├── client_example.py
│ └── README.md
└── tests/ # Test suite
Implements binary Merkle trees with:
- Domain-separated hashing (0x00 for leaves, 0x01 for nodes)
- Inclusion proof generation and verification
- Support for incomplete trees (non-power-of-2 sizes)
Creates COSE Sign1 receipts containing:
- Verifiable Data Structure identifier (RFC 9162)
- Inclusion proofs in unprotected header
- Claims (issuer, subject) in protected header
Append-only log with:
- Entries table (statement hash, COSE message, metadata)
- Merkle node cache for efficient proof generation
- Service state persistence
See the examples/ directory for complete working examples demonstrating integration with the Scittles service.
import httpx
from pycose.messages import Sign1Message
from pycose.headers import Algorithm
from pycose.keys.ec2 import EC2Key
from pycose.keys.curves import P256
# Create a signed statement
key = EC2Key.generate_key(crv=P256)
msg = Sign1Message(
phdr={Algorithm: -7}, # ES256
payload=b"My artifact metadata"
)
msg.key = key
cose_bytes = msg.encode()
# Register with transparency service
async with httpx.AsyncClient() as client:
response = await client.post(
"http://localhost:8000/entries",
content=cose_bytes,
headers={"Content-Type": "application/cose"}
)
if response.status_code == 201:
entry_id = response.headers["Location"].split("/")[-1]
receipt = response.content
print(f"Registered: {entry_id}")# Start the service (if not already running)
docker-compose up -d
# Run the example client
python3 examples/client_example.pyFor more detailed examples and integration patterns, see examples/README.md.
# Get service configuration
curl http://localhost:8000/.well-known/transparency-configuration \
-H "Accept: application/cbor" | python -c "import cbor2, sys; print(cbor2.loads(sys.stdin.buffer.read()))"
# Register a statement (assuming you have a COSE file)
curl -X POST http://localhost:8000/entries \
-H "Content-Type: application/cose" \
--data-binary @statement.cose
# Retrieve a receipt
curl http://localhost:8000/entries/<entry_id> \
-H "Accept: application/cose" -o receipt.cose# Run all tests
pytest
# Run with coverage
pytest --cov=src --cov-report=term-missing
# Run specific test file
pytest tests/test_merkle.py -v# Format code
black src tests
# Lint
ruff check src tests
# Type checking
mypy src- SCRAPI: draft-ietf-scitt-scrapi
- RFC 9162: Certificate Transparency Version 2.0 (Merkle trees)
- RFC 9052: CBOR Object Signing and Encryption (COSE)
- RFC 9290: Concise Problem Details for CoAP APIs
MIT License - see LICENSE for details.
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.