Files
mono/core/scripts/sign_assets.py
2026-02-24 23:09:39 +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()