This directory contains example scripts demonstrating how to integrate with the Scittles transparency service.
Install the required dependencies:
pip install httpx pycose cbor2Or install from the project requirements:
pip install -r requirements.txtA comprehensive example demonstrating the full workflow:
- Get service configuration - Discover service endpoints
- Generate signing key - Create a P-256 EC2 key for signing
- Create signed statement - Sign artifact metadata as COSE Sign1
- Register statement - Submit to transparency service
- Retrieve receipt - Get the transparency receipt
- Retrieve statement - Get the original signed statement
- Verify receipt - Basic signature verification
Make sure the Scittles service is running:
# Using Docker
docker-compose up -d
# Or locally
python -m src.mainThen run the example:
python examples/client_example.py======================================================================
Scittles Transparency Service Integration Example
======================================================================
Step 1: Getting service configuration...
Service URL: https://transparency.example
Registration endpoint: https://transparency.example/entries
Receipt endpoint: https://transparency.example/entries/{entry_id}
Step 2: Generating signing key...
Generated P-256 EC2 key
Step 3: Creating signed statement...
Payload: {'hash': 'sha256:abc123...', 'name': 'example-package', 'timestamp': '2024-01-07T12:00:00Z', 'type': 'artifact', 'version': '1.0.0'}
Created COSE Sign1 message (XXX bytes)
Step 4: Registering statement with transparency service...
Success! Entry ID: abc123def456...
Receipt size: XXX bytes
Step 5: Retrieving receipt...
Retrieved receipt (XXX bytes)
Step 6: Retrieving original signed statement...
Retrieved statement (XXX bytes)
Statement matches original!
Step 7: Verifying receipt...
Receipt signature is valid!
======================================================================
Example completed successfully!
======================================================================
Entry ID: abc123def456...
View receipt: http://localhost:8000/entries/abc123def456...
View statement: http://localhost:8000/signed-statements/abc123def456...
import httpx
from pycose.messages import Sign1Message
from pycose.keys.ec2 import EC2Key
from pycose.keys.curves import P256
from pycose.algorithms import Es256
# Create key and sign payload
key = EC2Key.generate_key(crv=P256)
msg = Sign1Message(phdr={Algorithm: Es256}, payload=b"Your payload")
msg.key = key
cose_sign1 = msg.encode()
# Register with service
async with httpx.AsyncClient() as client:
response = await client.post(
"http://localhost:8000/entries",
content=cose_sign1,
headers={"Content-Type": "application/cose"},
)
entry_id = response.headers["Location"].split("/")[-1]
receipt = response.contentasync def register_multiple_statements(statements: list[bytes], base_url: str):
"""Register multiple statements."""
async with httpx.AsyncClient() as client:
results = []
for statement in statements:
response = await client.post(
f"{base_url}/entries",
content=statement,
headers={"Content-Type": "application/cose"},
)
if response.status_code == 201:
entry_id = response.headers["Location"].split("/")[-1]
results.append((entry_id, response.content))
return resultsfrom pycose.messages import Sign1Message
def verify_receipt(receipt_bytes: bytes, service_key: EC2Key) -> bool:
"""Verify a receipt signature."""
receipt_msg = Sign1Message.decode(receipt_bytes)
receipt_msg.key = service_key
return receipt_msg.verify_signature()GET /.well-known/transparency-configuration- Get service configuration (CBOR)POST /entries- Register a signed statement (COSE Sign1)GET /entries/{entry_id}- Get receipt for an entry (COSE Sign1)GET /signed-statements/{entry_id}- Get original signed statement (COSE Sign1)GET /metrics- Prometheus metrics endpoint
The service returns errors in RFC 9290 Concise Problem Details format (CBOR):
import cbor2
try:
response = await client.post(...)
response.raise_for_status()
except httpx.HTTPStatusError as e:
error = cbor2.loads(e.response.content)
# error is a dict with keys -1 (title) and -2 (detail)
print(f"Error: {error.get(-1)} - {error.get(-2)}")