Files
rose-ash/artdag/core/scripts/sign_assets.py
giles 1a74d811f7
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m33s
Incorporate art-dag-mono repo into artdag/ subfolder
Merges full history from art-dag/mono.git into the monorepo
under the artdag/ directory. Contains: core (DAG engine),
l1 (Celery rendering server), l2 (ActivityPub registry),
common (shared templates/middleware), client (CLI), test (e2e).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

git-subtree-dir: artdag
git-subtree-mainline: 1a179de547
git-subtree-split: 4c2e716558
2026-02-27 09:07:23 +00:00

144 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""
Sign assets in the registry with giles's private key.
Creates ActivityPub Create activities with RSA signatures.
"""
import base64
import hashlib
import json
import sys
import uuid
from datetime import datetime, timezone
from pathlib import Path
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
def load_private_key(path: Path):
"""Load private key from PEM file."""
pem_data = path.read_bytes()
return serialization.load_pem_private_key(pem_data, password=None, backend=default_backend())
def sign_data(private_key, data: str) -> str:
"""Sign data with RSA private key, return base64 signature."""
signature = private_key.sign(
data.encode(),
padding.PKCS1v15(),
hashes.SHA256(),
)
return base64.b64encode(signature).decode()
def create_activity(actor_id: str, asset_name: str, cid: str, asset_type: str, domain: str = "artdag.rose-ash.com"):
"""Create a Create activity for an asset."""
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
return {
"activity_id": str(uuid.uuid4()),
"activity_type": "Create",
"actor_id": actor_id,
"object_data": {
"type": asset_type_to_ap(asset_type),
"name": asset_name,
"id": f"https://{domain}/objects/{cid}",
"contentHash": {
"algorithm": "sha3-256",
"value": cid
},
"attributedTo": actor_id
},
"published": now,
}
def asset_type_to_ap(asset_type: str) -> str:
"""Convert asset type to ActivityPub type."""
type_map = {
"image": "Image",
"video": "Video",
"audio": "Audio",
"effect": "Application",
"infrastructure": "Application",
}
return type_map.get(asset_type, "Document")
def sign_activity(activity: dict, private_key, actor_id: str, domain: str = "artdag.rose-ash.com") -> dict:
"""Add signature to activity."""
# Create canonical string to sign
to_sign = json.dumps(activity["object_data"], sort_keys=True, separators=(",", ":"))
signature_value = sign_data(private_key, to_sign)
activity["signature"] = {
"type": "RsaSignature2017",
"creator": f"{actor_id}#main-key",
"created": activity["published"],
"signatureValue": signature_value
}
return activity
def main():
username = "giles"
domain = "artdag.rose-ash.com"
actor_id = f"https://{domain}/users/{username}"
# Load private key
private_key_path = Path.home() / ".artdag" / "keys" / f"{username}.pem"
if not private_key_path.exists():
print(f"Private key not found: {private_key_path}")
print("Run setup_actor.py first.")
sys.exit(1)
private_key = load_private_key(private_key_path)
print(f"Loaded private key: {private_key_path}")
# Load registry
registry_path = Path.home() / "artdag-registry" / "registry.json"
with open(registry_path) as f:
registry = json.load(f)
# Create signed activities for each asset
activities = []
for asset_name, asset_data in registry["assets"].items():
print(f"\nSigning: {asset_name}")
print(f" Hash: {asset_data['cid'][:16]}...")
activity = create_activity(
actor_id=actor_id,
asset_name=asset_name,
cid=asset_data["cid"],
asset_type=asset_data["asset_type"],
domain=domain,
)
signed_activity = sign_activity(activity, private_key, actor_id, domain)
activities.append(signed_activity)
print(f" Activity ID: {signed_activity['activity_id']}")
print(f" Signature: {signed_activity['signature']['signatureValue'][:32]}...")
# Save activities
activities_path = Path.home() / "artdag-registry" / "activities.json"
activities_data = {
"version": "1.0",
"activities": activities
}
with open(activities_path, "w") as f:
json.dump(activities_data, f, indent=2)
print(f"\nSaved {len(activities)} signed activities to: {activities_path}")
if __name__ == "__main__":
main()