From bb10d6a9a82a32c12474ba38822a9f8c17fefd26 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 18:37:35 +0000 Subject: [PATCH 1/2] feat: add SHA-256 contentAddress to identity document Computes SHA-256 over the same JCS-canonicalized bytes used for the Ed25519 signature and appends contentAddress {alg, digest} to the document. contentAddress is added to _OUT_OF_SCOPE_KEYS so verify_identity strips it before canonicalization, keeping verification deterministic. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01GRuyRG2kcHdKwtrqdwMZAK --- sovp/core.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sovp/core.py b/sovp/core.py index f9ac9a6..c9569c3 100644 --- a/sovp/core.py +++ b/sovp/core.py @@ -4,6 +4,7 @@ from __future__ import annotations import base64 +import hashlib import jcs import uuid from datetime import datetime, timezone, timedelta @@ -13,7 +14,7 @@ # Per draft Section 4: integrity_proof is excluded because the signature # cannot cover itself. Per draft V02 item 10: vendor extension objects # (e.g. scan) MUST NOT be included in the signed scope. -_OUT_OF_SCOPE_KEYS = frozenset({"integrity_proof", "scan"}) +_OUT_OF_SCOPE_KEYS = frozenset({"integrity_proof", "scan", "contentAddress"}) from cryptography.hazmat.primitives.asymmetric import ed25519 from cryptography.hazmat.primitives import serialization from cryptography.exceptions import InvalidSignature @@ -181,9 +182,14 @@ def generate_identity_document( private_key = ed25519.Ed25519PrivateKey.from_private_bytes(priv_bytes) canonical_data = jcs.canonicalize(non_proof) signature = base64.b64encode(private_key.sign(canonical_data)).decode("utf-8") + digest = hashlib.sha256(canonical_data).hexdigest() document = { **non_proof, + "contentAddress": { + "alg": "sha256", + "digest": digest, + }, "integrity_proof": { "signature": signature, "created": created, From aa48221e3cfc5717859e201c446eac9dd4c89ca0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 18:39:19 +0000 Subject: [PATCH 2/2] refactor: eliminate duplicate signing path in generate_identity_document generate_identity_document() now delegates to sign_identity() instead of repeating the private-key load and Ed25519 sign call. _OUT_OF_SCOPE_KEYS now has exactly one enforcement point. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01GRuyRG2kcHdKwtrqdwMZAK --- sovp/core.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sovp/core.py b/sovp/core.py index c9569c3..19cb4ef 100644 --- a/sovp/core.py +++ b/sovp/core.py @@ -178,10 +178,8 @@ def generate_identity_document( }, } - priv_bytes = base64.b64decode(private_key_b64) - private_key = ed25519.Ed25519PrivateKey.from_private_bytes(priv_bytes) canonical_data = jcs.canonicalize(non_proof) - signature = base64.b64encode(private_key.sign(canonical_data)).decode("utf-8") + signature = sign_identity(private_key_b64, non_proof) digest = hashlib.sha256(canonical_data).hexdigest() document = {