Source code for aries_cloudagent.messaging.jsonld.create_verify_data

"""Contains the functions needed to produce and verify a json-ld signature.

This file was ported from
https://github.com/transmute-industries/Ed25519Signature2018/blob/master/src/createVerifyData/index.js
"""

import datetime
import hashlib

from pyld import jsonld

from .error import (
    DroppedAttributeError,
    MissingVerificationMethodError,
    SignatureTypeError,
)


def _canonize(data, document_loader=None):
    return jsonld.normalize(
        data,
        {
            "algorithm": "URDNA2015",
            "format": "application/n-quads",
            **{opt: document_loader for opt in ["documentLoader"] if document_loader},
        },
    )


def _sha256(data):
    return hashlib.sha256(data.encode("utf-8")).hexdigest()


def _canonize_signature_options(signatureOptions, document_loader=None):
    _signatureOptions = {**signatureOptions, "@context": "https://w3id.org/security/v2"}
    _signatureOptions.pop("jws", None)
    _signatureOptions.pop("signatureValue", None)
    _signatureOptions.pop("proofValue", None)
    return _canonize(_signatureOptions, document_loader)


def _canonize_document(doc, document_loader=None):
    _doc = {**doc}
    _doc.pop("proof", None)
    return _canonize(_doc, document_loader)


def _created_at():
    """Creation Timestamp."""

    stamp = datetime.datetime.now(datetime.timezone.utc)
    return stamp.strftime("%Y-%m-%dT%H:%M:%SZ")


[docs]def create_verify_data(data, signature_options, document_loader=None): """Encapsulate process of constructing string used during sign and verify.""" signature_options["type"] = signature_options.get("type", "Ed25519Signature2018") type_ = signature_options.get("type") if type_ != "Ed25519Signature2018": raise SignatureTypeError(f"invalid signature type {type_}.") if not signature_options.get("verificationMethod"): raise MissingVerificationMethodError( "signature_options.verificationMethod is required" ) signature_options["created"] = signature_options.get("created", _created_at()) [expanded] = jsonld.expand( data, options={ **{opt: document_loader for opt in ["documentLoader"] if document_loader} }, ) framed = jsonld.compact( expanded, "https://w3id.org/security/v2", options={ "skipExpansion": True, **{opt: document_loader for opt in ["documentLoader"] if document_loader}, }, ) # Detect any dropped attributes during the expand/contract step. if len(data) > len(framed): # > check indicates dropped attrs < is a different error # attempt to collect error report data for_diff = jsonld.compact( expanded, data.get("@context"), options={ **{ opt: document_loader for opt in ["documentLoader"] if document_loader } }, ) dropped = set(data.keys()) - set(for_diff.keys()) raise DroppedAttributeError( f"{dropped} attributes dropped. " "Provide definitions in context to correct." ) # Check proof for dropped attributes attr = [ ("proof", "proof"), ("credentialSubject", "https://www.w3.org/2018/credentials#credentialSubject"), ] data_context = data.get("@context") for mapping in attr: data_attribute = data.get(mapping[0], {}) frame_attribute = framed.get(mapping[1], {}) if len(data_attribute) > len(frame_attribute): for_diff = jsonld.compact( expanded, data_context, options={ **{ opt: document_loader for opt in ["documentLoader"] if document_loader } }, ) for_diff_attribute = for_diff.get(mapping[1], {}) dropped = set(data_attribute.keys()) - set(for_diff_attribute.keys()) raise DroppedAttributeError( f"in {mapping[0]}, {dropped} attributes dropped. " "Provide definitions in context to correct." ) canonized_signature_options = _canonize_signature_options( signature_options, document_loader ) hash_of_canonized_signature_options = _sha256(canonized_signature_options) canonized_document = _canonize_document(framed, document_loader) hash_of_canonized_document = _sha256(canonized_document) return (framed, hash_of_canonized_signature_options + hash_of_canonized_document)