def __init__( self, did: str, ident: str, value: str, pk_type: PublicKeyType = None, controller: str = None, authn: bool = False) -> None: """ Retain key specification particulars. Raise BadIdentifier on any bad input DID. :param did: DID of DID document embedding public key :param ident: identifier for public key :param value: key content, encoded as key specification requires :param pk_type: public key type (enum), default ED25519_SIG_2018 :param controller: controller DID (default DID of DID document) :param authn: whether key as has DID authentication privilege (default False) """ self._did = canon_did(did) self._id = canon_ref(self._did, ident) self._value = value self._type = pk_type or PublicKeyType.ED25519_SIG_2018 self._controller = canon_did(controller) if controller else self._did self._authn = authn
def __init__(self, did: str, ident: str, typ: str, recip_keys: Union[Sequence, PublicKey], routing_keys: Union[Sequence, PublicKey], endpoint: str, priority: int = 0): """ Retain service specification particulars. Raise BadIdentifier on bad input controller DID. :param did: DID of DID document embedding service, specified raw (operation converts to URI) :param ident: identifier for service :param typ: service type :param recip_keys: recipient key or keys :param routing_keys: routing key or keys :param endpoint: service endpoint :param priority: service priority """ self._did = canon_did(did) self._id = canon_ref(self._did, ident, ';') self._type = typ self._recip_keys = ([recip_keys] if isinstance(recip_keys, PublicKey) else list(recip_keys) if recip_keys else None) self._routing_keys = ([routing_keys] if isinstance( routing_keys, PublicKey) else list(routing_keys) if routing_keys else None) self._endpoint = endpoint self._priority = priority
def did(self, value: str) -> None: """ Set DID ('id' in DIDDoc context). Raise BadIdentifier for bad input DID. :param value: DID """ self._did = canon_did(value) if value else None
def __init__(self, did: str = None) -> None: """ Initializer. Retain DID ('id' in DIDDoc context); initialize verification keys and services to empty lists. Raise BadIdentifier for bad input DID. :param did: DID for current DIDdoc """ self._did = canon_did(did) if did else None # allow specification post-hoc self._pubkey = {} self._service = {}
async def test_a2a(): print(Ink.YELLOW('\n\n== Testing DID Doc wranglers ==')) # One authn key by reference dd_in = { '@context': 'https://w3id.org/did/v1', 'id': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKey': [ { 'id': '3', 'type': 'RsaVerificationKey2018', 'controller': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKeyPem': '-----BEGIN PUBLIC X...' }, { 'id': '4', 'type': 'RsaVerificationKey2018', 'controller': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKeyPem': '-----BEGIN PUBLIC 9...' }, { 'id': '6', 'type': 'RsaVerificationKey2018', 'controller': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKeyPem': '-----BEGIN PUBLIC A...' } ], 'authentication': [ { 'type': 'RsaSignatureAuthentication2018', 'publicKey': 'did:sov:LjgpST2rjsoxYegQDRm7EL#4' } ], 'service': [ { 'id': '0', 'type': 'Agency', 'serviceEndpoint': 'did:sov:Q4zqM7aXqm7gDQkUVLng9h' } ] } dd = DIDDoc.deserialize(dd_in) assert len(dd.pubkey) == len(dd_in['publicKey']) assert len(dd.authnkey) == len(dd_in['authentication']) dd_out = dd.serialize() print('\n\n== 1 == DID Doc {} on abbreviated identifiers: {}'.format(dd, ppjson(dd_out))) # Exercise JSON, de/serialization dd_json = dd.to_json() dd_copy = dd.from_json(dd_json) assert dd_copy.did == dd.did assert all(dd_copy.authnkey[k].to_dict() == dd.authnkey[k].to_dict() for k in dd_copy.authnkey) assert {k for k in dd_copy.authnkey} == {k for k in dd.authnkey} assert all(dd_copy.pubkey[k].to_dict() == dd.pubkey[k].to_dict() for k in dd_copy.pubkey) assert {k for k in dd_copy.pubkey} == {k for k in dd.pubkey} assert all(dd_copy.service[k].to_dict() == dd.service[k].to_dict() for k in dd_copy.service) assert {k for k in dd_copy.service} == {k for k in dd.service} print('\n\n== 2 == DID Doc de/serialization operates OK:') # Exercise accessors dd.did = dd_out['id'] assert dd.did == canon_did(dd_out['id']) try: dd.set(['neither a service', 'nor a public key']) assert False except BadDIDDocItem: pass assert dd.service[[k for k in dd.service][0]].did == dd.did print('\n\n== 3 == DID Doc accessors operate OK') # One authn key embedded, all possible refs canonical dd_in = { '@context': 'https://w3id.org/did/v1', 'id': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKey': [ { 'id': '3', 'type': 'RsaVerificationKey2018', 'controller': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKeyPem': '-----BEGIN PUBLIC X...' }, { 'id': 'did:sov:LjgpST2rjsoxYegQDRm7EL#4', 'type': 'RsaVerificationKey2018', 'controller': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKeyPem': '-----BEGIN PUBLIC 9...' } ], 'authentication': [ { 'type': 'RsaSignatureAuthentication2018', 'publicKey': 'did:sov:LjgpST2rjsoxYegQDRm7EL#4' }, { 'id': 'did:sov:LjgpST2rjsoxYegQDRm7EL#6', 'type': 'RsaVerificationKey2018', 'controller': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKeyPem': '-----BEGIN PUBLIC A...' } ], 'service': [ { 'id': 'did:sov:LjgpST2rjsoxYegQDRm7EL;0', 'type': 'Agency', 'serviceEndpoint': 'https://www.von.ca' } ] } dd = DIDDoc.deserialize(dd_in) assert len(dd.pubkey) == len(dd_in['publicKey']) + 1 assert len(dd.authnkey) == len(dd_in['authentication']) dd_out = dd.serialize() print('\n\n== 4 == DID Doc on mixed reference styles, embedded and ref style authn keys: {}'.format(ppjson(dd_out))) # All references canonical where possible; one authn key embedded and one by reference dd_in = { '@context': 'https://w3id.org/did/v1', 'id': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKey': [ { 'id': 'did:sov:LjgpST2rjsoxYegQDRm7EL#3', 'type': 'RsaVerificationKey2018', 'controller': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKeyPem': '-----BEGIN PUBLIC X...' }, { 'id': 'did:sov:LjgpST2rjsoxYegQDRm7EL#4', 'type': 'RsaVerificationKey2018', 'controller': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKeyPem': '-----BEGIN PUBLIC 9...' } ], 'authentication': [ { 'type': 'RsaSignatureAuthentication2018', 'publicKey': 'did:sov:LjgpST2rjsoxYegQDRm7EL#4' }, { 'id': 'did:sov:LjgpST2rjsoxYegQDRm7EL#6', 'type': 'RsaVerificationKey2018', 'controller': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKeyPem': '-----BEGIN PUBLIC A...' } ], 'service': [ { 'id': 'did:sov:LjgpST2rjsoxYegQDRm7EL;0', 'type': 'DidMessaging', 'serviceEndpoint': 'https://www.von.ca' } ] } dd = DIDDoc.deserialize(dd_in) assert len(dd.pubkey) == len(dd_in['publicKey']) + 1 assert len(dd.authnkey) == len(dd_in['authentication']) dd_out = dd.serialize() print('\n\n== 5 == DID Doc on canonical refs: {}'.format(ppjson(dd_out))) # Minimal as per indy-agent test suite without explicit identifiers dd_in = { '@context': 'https://w3id.org/did/v1', 'publicKey': [ { 'id': 'LjgpST2rjsoxYegQDRm7EL#keys-1', 'type': 'Ed25519VerificationKey2018', 'controller': 'LjgpST2rjsoxYegQDRm7EL', 'publicKeyBase58': '~XXXXXXXXXXXXXXXX' } ], 'service': [ { 'type': 'DidMessaging', 'recipientKeys': ['~XXXXXXXXXXXXXXXX'], 'serviceEndpoint': 'https://www.von.ca' } ] } dd = DIDDoc.deserialize(dd_in) assert len(dd.pubkey) == len(dd_in['publicKey']) assert len(dd.authnkey) == 0 dd_out = dd.serialize() print('\n\n== 6 == DID Doc miminal style, implcit DID document identifier: {}'.format( ppjson(dd_out))) # Minimal + ids as per indy-agent test suite with explicit identifiers; novel service recipient key on raw base58 dd_in = { '@context': 'https://w3id.org/did/v1', 'id': 'LjgpST2rjsoxYegQDRm7EL', 'publicKey': [ { 'id': 'LjgpST2rjsoxYegQDRm7EL#keys-1', 'type': 'Ed25519VerificationKey2018', 'controller': 'LjgpST2rjsoxYegQDRm7EL', 'publicKeyBase58': '~XXXXXXXXXXXXXXXX' } ], 'service': [ { 'id': 'LjgpST2rjsoxYegQDRm7EL;indy', 'type': 'DidMessaging', 'priority': 1, 'recipientKeys': ['~YYYYYYYYYYYYYYYY'], 'serviceEndpoint': 'https://www.von.ca' } ] } dd = DIDDoc.deserialize(dd_in) assert len(dd.pubkey) == 1 + len(dd_in['publicKey']) assert len(dd.authnkey) == 0 dd_out = dd.serialize() print('\n\n== 7 == DID Doc miminal style plus explicit idents and novel raw base58 service recip key: {}'.format( ppjson(dd_out))) # Minimal + ids as per indy-agent test suite with explicit identifiers; novel service recipient key on raw base58 dd_in = { '@context': 'https://w3id.org/did/v1', 'id': 'LjgpST2rjsoxYegQDRm7EL', 'publicKey': [ { 'id': 'LjgpST2rjsoxYegQDRm7EL#keys-1', 'type': 'Ed25519VerificationKey2018', 'controller': 'LjgpST2rjsoxYegQDRm7EL', 'publicKeyBase58': '~XXXXXXXXXXXXXXXX' }, { 'id': 'LjgpST2rjsoxYegQDRm7EL#keys-2', 'type': 'Ed25519VerificationKey2018', 'controller': 'LjgpST2rjsoxYegQDRm7EL', 'publicKeyBase58': '~YYYYYYYYYYYYYYYY' }, { 'id': 'LjgpST2rjsoxYegQDRm7EL#keys-3', 'type': 'Secp256k1VerificationKey2018', 'controller': 'LjgpST2rjsoxYegQDRm7EL', 'publicKeyHex': '02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71' }, { 'id': 'did:sov:LjgpST2rjsoxYegQDRm7EL#keys-4', 'type': 'RsaVerificationKey2018', 'controller': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKeyPem': '-----BEGIN PUBLIC A...' } ], 'service': [ { 'id': 'LjgpST2rjsoxYegQDRm7EL;indy', 'type': 'DidMessaging', 'priority': 0, 'recipientKeys': ['~ZZZZZZZZZZZZZZZZ'], 'serviceEndpoint': 'did:sov:LjgpST2rjsoxYegQDRm7EL;1' }, { 'id': '1', 'type': 'one', 'priority': 1, 'recipientKeys': [ '~XXXXXXXXXXXXXXXX', 'did:sov:LjgpST2rjsoxYegQDRm7EL#keys-1' ], 'routingKeys': [ 'did:sov:LjgpST2rjsoxYegQDRm7EL#keys-4' ], 'serviceEndpoint': 'LjgpST2rjsoxYegQDRm7EL;2' }, { 'id': '2', 'type': 'two', 'priority': 2, 'recipientKeys': [ '~XXXXXXXXXXXXXXXX', 'did:sov:LjgpST2rjsoxYegQDRm7EL#keys-1' ], 'routingKeys': [ 'did:sov:LjgpST2rjsoxYegQDRm7EL#keys-4' ], 'serviceEndpoint': 'https://www.two.ca/two' } ] } dd = DIDDoc.deserialize(dd_in) assert len(dd.pubkey) == 1 + len(dd_in['publicKey']) assert len(dd.authnkey) == 0 assert {s.priority for s in dd.service.values()} == {0, 1, 2} assert len(dd.service) == 3 assert all(len(dd.service[k].to_dict()['recipientKeys']) == 1 for k in dd.service) assert 'routingKeys' not in dd.service['did:sov:LjgpST2rjsoxYegQDRm7EL;indy'].to_dict() assert all(len(dd.service[k].to_dict()['routingKeys']) == 1 for k in ('did:sov:LjgpST2rjsoxYegQDRm7EL;1', 'did:sov:LjgpST2rjsoxYegQDRm7EL;2')) dd_out = dd.serialize() print('\n\n== 8 == DID Doc on mixed service routing and recipient keys: {}'.format(ppjson(dd_out))) pk = PublicKey( dd.did, '99', '~AAAAAAAAAAAAAAAA', PublicKeyType.ED25519_SIG_2018, dd.did, True) dd.set(pk) assert len(dd.pubkey) == 2 + len(dd_in['publicKey']) assert canon_ref(dd.did, '99', '#') in dd.pubkey assert len(dd.authnkey) == 1 service = Service( dd.did, 'abc', 'IndyAgent', [pk], [pk], 'http://www.abc.ca/123' ) dd.set(service) assert len(dd.service) == 4 assert canon_ref(dd.did, 'abc', ';') in dd.service print('\n\n== 9 == DID Doc adds public key and service via set() OK') # Exercise missing service recipient key dd_in = { '@context': 'https://w3id.org/did/v1', 'id': 'LjgpST2rjsoxYegQDRm7EL', 'publicKey': [ { 'id': 'LjgpST2rjsoxYegQDRm7EL#keys-1', 'type': 'Ed25519VerificationKey2018', 'controller': 'LjgpST2rjsoxYegQDRm7EL', 'publicKeyBase58': '~XXXXXXXXXXXXXXXX' } ], 'service': [ { 'id': 'LjgpST2rjsoxYegQDRm7EL;indy', 'type': 'DidMessaging', 'priority': 1, 'recipientKeys': [ 'did:sov:LjgpST2rjsoxYegQDRm7EL#keys-3' ], 'serviceEndpoint': 'https://www.von.ca' } ] } try: dd = DIDDoc.deserialize(dd_in) assert False except AbsentDIDDocItem: pass print('\n\n== 10 == DID Doc on underspecified service key fails as expected') # Minimal as per W3C Example 2, draft 0.12 dd_in = { '@context': 'https://w3id.org/did/v1', 'id': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'authentication': [ { 'id': 'LjgpST2rjsoxYegQDRm7EL#keys-1', 'type': 'Ed25519VerificationKey2018', 'controller': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKeyBase58': '~XXXXXXXXXXXXXXXX' } ], 'service': [ { 'type': 'DidMessaging', 'serviceEndpoint': 'https://example.com/endpoint/8377464' } ] } dd = DIDDoc.deserialize(dd_in) assert len(dd.pubkey) == 1 assert len(dd.authnkey) == 1 assert len(dd.service) == 1 dd_out = dd.serialize() print('\n\n== 11 == Minimal DID Doc (no pubkey except authentication) as per W3C spec parses OK: {}'.format( ppjson(dd_out))) # Exercise no-identifier case dd_in = { '@context': 'https://w3id.org/did/v1', 'authentication': [ { 'type': 'Ed25519VerificationKey2018', 'controller': 'did:sov:LjgpST2rjsoxYegQDRm7EL', 'publicKeyBase58': '~XXXXXXXXXXXXXXXX' } ], 'service': [ { 'type': 'DidMessaging', 'serviceEndpoint': 'https://example.com/endpoint/8377464' } ] } try: dd = DIDDoc.deserialize(dd_in) assert False except AbsentDIDDocItem: pass print('\n\n== 12 == DID Doc without identifier rejected as expected') # Exercise reference canonicalization, including failure paths try: canon_ref('not-a-DID', ref=dd.did, delimiter='#') assert False except BadIdentifier: pass try: canon_ref(dd.did, ref='did:sov:not-a-DID', delimiter='#') assert False except BadIdentifier: pass urlref = 'https://www.clafouti-quasar.ca:8443/supply-management/fruit/index.html' assert canon_ref(dd.did, ref=urlref) == urlref print('\n\n== 13 == Reference canonicalization operates as expected') assert PublicKeyType.get('no-such-type') is None pubkey0 = dd.pubkey[[k for k in dd.pubkey][0]] was_authn = pubkey0.authn pubkey0.authn = not was_authn assert pubkey0.authn != was_authn print('\n\n== 14 == Changed authentication setting for DIDDoc {} in public key {}, now {}'.format( pubkey0.did, pubkey0.id, repr(pubkey0)))
def deserialize(cls, did_doc: dict) -> 'DIDDoc': """ Construct DIDDoc object from dict representation. Raise BadIdentifier for bad DID. Raise AbsentDIDDocItem for missing mandatory item. :param did_doc: DIDDoc dict reprentation. :return: DIDDoc from input json. """ rv = None if 'id' in did_doc: rv = DIDDoc(did_doc['id']) else: # heuristic: get DID to serve as DID document identifier from first OK-looking public key for section in ('publicKey', 'authentication'): if rv is None and section in did_doc: for key_spec in did_doc[section]: try: pubkey_did = canon_did( resource(key_spec.get('id', ''))) if ok_did(pubkey_did): rv = DIDDoc(pubkey_did) break except BadIdentifier: # no identifier here, move on to next candidate break if rv is None: LOGGER.debug( 'DIDDoc.deserialize <!< no identifier in DID document') raise AbsentDIDDocItem('No identifier in DID document') for pubkey in did_doc.get( 'publicKey', {} ): # include all public keys, authentication pubkeys by reference pubkey_type = PublicKeyType.get(pubkey['type']) authn = any( canon_ref(rv.did, ak) == canon_ref(rv.did, pubkey['id']) for ak in did_doc.get('authentication', '') if isinstance(ak, str)) key = PublicKey( # initialization canonicalizes id rv.did, pubkey['id'], pubkey[pubkey_type.specifier], pubkey_type, canon_did(pubkey['controller']), authn) rv.pubkey[key.id] = key for akey in did_doc.get('authentication', {}): # include embedded authentication keys if isinstance(akey, collections.Mapping): pubkey_type = PublicKeyType.get(akey['type']) key = PublicKey( # initialization canonicalized id rv.did, akey['id'], akey[pubkey_type.specifier], pubkey_type, canon_did(akey['controller']), True) rv.pubkey[key.id] = key for service in did_doc.get('service', {}): endpoint = service['serviceEndpoint'] svc = Service( # initialization canonicalizes id rv.did, service.get( 'id', canon_ref(rv.did, 'assigned-service-{}'.format(len(rv.service)), ';')), service['type'], rv.add_service_pubkeys(service, 'recipientKeys'), rv.add_service_pubkeys(service, ['mediatorKeys', 'routingKeys']), canon_ref(rv.did, endpoint, ';') if ';' in endpoint else endpoint, service.get('priority', None)) rv.service[svc.id] = svc return rv