1
1
import os
2
+ import ast
2
3
import sys
4
+ import base64
3
5
import inspect
4
6
import urllib .parse
5
7
from typing import Optional , Callable
6
8
9
+ import multiformats
10
+
7
11
8
12
def did_web_to_url (
9
13
did_web_string : str ,
@@ -18,20 +22,304 @@ def did_web_to_url(
18
22
)
19
23
20
24
25
+ class DIDKeyInvalidPublicKeyLengthError (ValueError ):
26
+ """
27
+ If the byte length of rawPublicKeyBytes does not match the expected public
28
+ key length for the associated multicodecValue, an invalidPublicKeyLength
29
+ error MUST be raised.
30
+ """
31
+
32
+
21
33
class DIDKeyDecoderNotFoundError (NotImplementedError ):
22
34
"""
23
35
Raised when we don't have a function implemented to decode the given key
24
36
"""
25
37
26
38
39
+ class DIDKeyDecoderError (Exception ):
40
+ """
41
+ Raised when we failed to decode a key from a did:key DID method
42
+ """
43
+
44
+
45
+ class DIDKeyInvalidPublicKeyError (DIDKeyDecoderError ):
46
+ """
47
+ Raised when the raw bytes of a key are invalid during decode
48
+ """
49
+
50
+
27
51
DID_KEY_METHOD = "did:key:"
28
52
29
53
30
- def did_key_to_jwk_dict_is_p_384_startswith_z82 (did_key : str ) -> dict [str , str ]:
31
- did_key = did_key .replace (DID_KEY_METHOD , "" , 1 )
54
+ def did_key_decode (
55
+ did_key_without_method_or_key_type_prefix : str ,
56
+ ) -> tuple [str , bytes ]:
32
57
return
33
58
34
59
60
+ import sys , base58 , multibase , multicodec
61
+ import snoop
62
+
63
+
64
+ def did_key_decode_public_key (multibase_value : str ) -> dict [str , str ]:
65
+ # 3.1.2.3
66
+ # Decode multibaseValue using the base58-btc multibase alphabet and set
67
+ # multicodecValue to the multicodec header for the decoded value.
68
+ multibase_value_decoded = multibase .decode (multibase_value )
69
+ # Implementers are cautioned to ensure that the multicodecValue is set to
70
+ # the result after performing varint decoding.
71
+ multicodec_value = multicodec .extract_prefix (multibase_value_decoded )
72
+ # Set the rawPublicKeyBytes to the bytes remaining after the multicodec
73
+ # header.
74
+ raw_public_key_bytes = multicodec .remove_prefix (multibase_value_decoded )
75
+ # Return multicodecValue and rawPublicKeyBytes as the decodedPublicKey.
76
+ return multicodec_value , raw_public_key_bytes
77
+
78
+
79
+ class _MULTICODEC_VALUE_NOT_FOUND_IN_TABLE :
80
+ pass
81
+
82
+
83
+ MULTICODEC_VALUE_NOT_FOUND_IN_TABLE = _MULTICODEC_VALUE_NOT_FOUND_IN_TABLE ()
84
+
85
+ # Multicodec hexadecimal value, public key, byte length, Description
86
+ MULTICODEC_HEX_SECP256K1_PUBLIC_KEY = 0xE7
87
+ MULTICODEC_HEX_X25519_PUBLIC_KEY = 0xEC
88
+ MULTICODEC_HEX_ED25519_PUBLIC_KEY = 0xED
89
+ MULTICODEC_HEX_P256_PUBLIC_KEY = 0x1200
90
+ MULTICODEC_HEX_P384_PUBLIC_KEY = 0x1201
91
+ MULTICODEC_HEX_P521_PUBLIC_KEY = 0x1202
92
+ MULTICODEC_HEX_RSA_PUBLIC_KEY = 0x1205
93
+
94
+ MULTICODEC_VALUE_TABLE = {
95
+ MULTICODEC_HEX_SECP256K1_PUBLIC_KEY : 33 , # secp256k1-pub - Secp256k1 public key (compressed)
96
+ MULTICODEC_HEX_X25519_PUBLIC_KEY : 32 , # x25519-pub - Curve25519 public key
97
+ MULTICODEC_HEX_ED25519_PUBLIC_KEY : 32 , # ed25519-pub - Ed25519 public key
98
+ MULTICODEC_HEX_P256_PUBLIC_KEY : 33 , # p256-pub - P-256 public key (compressed)
99
+ MULTICODEC_HEX_P384_PUBLIC_KEY : 49 , # p384-pub - P-384 public key (compressed)
100
+ MULTICODEC_HEX_P521_PUBLIC_KEY : None , # p521-pub - P-521 public key (compressed)
101
+ MULTICODEC_HEX_RSA_PUBLIC_KEY : None , # rsa-pub - RSA public key. DER-encoded ASN.1 type RSAPublicKey according to IETF RFC 8017 (PKCS #1)
102
+ }
103
+
104
+ import cryptography .hazmat .primitives .asymmetric .ec
105
+
106
+
107
+ @snoop
108
+ def import_ecc_public_key (raw_public_key_bytes : bytes ):
109
+ # https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
110
+ # E: y^2 + x * y = x^3 + x^2 + b
111
+ # The bytes we have are x, we compute Y using the equation above, b I
112
+ # believe is defined by NIST...
113
+ # D.1.2.4 Curve P-384
114
+ # b = b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112
115
+ # 0314088f 5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef
116
+
117
+ # Annnnnd we're back...
118
+ # This is a “Hazardous Materials” module. You should ONLY use it if you’re
119
+ # 100% absolutely sure that you know what you’re doing because this module
120
+ # is full of land mines, dragons, and dinosaurs with laser guns.
121
+
122
+ # TODO Decode x and y, ?, prophet
123
+
124
+ # https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/#cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers
125
+ ecc_public_numbers = cryptography .hazmat .primitives .asymmetric .ec .EllipticCurvePublicNumbers .from_encoded_point (
126
+ # https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/#cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve
127
+ cryptography .hazmat .primitives .asymmetric .ec .EllipticCurve (
128
+ # The name of the curve. Usually the name used for the ASN.1 OID
129
+ # such as secp256k1.
130
+ #
131
+ # $ openssl ecparam -list_curves
132
+ # secp224r1 : NIST/SECG curve over a 224 bit prime field
133
+ # secp256k1 : SECG curve over a 256 bit prime field
134
+ # secp384r1 : NIST/SECG curve over a 384 bit prime field
135
+ # secp521r1 : NIST/SECG curve over a 521 bit prime field
136
+ # prime256v1: X9.62/SECG curve over a 256 bit prime field
137
+ # brainpoolP256r1: RFC 5639 curve over a 256 bit prime field
138
+ # brainpoolP256t1: RFC 5639 curve over a 256 bit prime field
139
+ # brainpoolP320r1: RFC 5639 curve over a 320 bit prime field
140
+ # brainpoolP320t1: RFC 5639 curve over a 320 bit prime field
141
+ # brainpoolP384r1: RFC 5639 curve over a 384 bit prime field
142
+ # brainpoolP384t1: RFC 5639 curve over a 384 bit prime field
143
+ # brainpoolP512r1: RFC 5639 curve over a 512 bit prime field
144
+ # brainpoolP512t1: RFC 5639 curve over a 512 bit prime field
145
+ name = "secp384r1" ,
146
+ # Size (in bits) of a secret scalar for the curve (as generated by
147
+ # generate_private_key()).
148
+ key_size = 384 ,
149
+ ),
150
+ raw_public_key_bytes ,
151
+ )
152
+
153
+ # XXX WARNING The point represented by this object is not validated in any
154
+ # way until EllipticCurvePublicNumbers.public_key() is called and may not
155
+ # represent a valid point on the curve. You should not attempt to perform
156
+ # any computations using the values from this class until you have either
157
+ # validated it yourself or called public_key() successfully.
158
+ decoded_key = ecc_public_numbers .public_key ()
159
+
160
+ return decoded_key
161
+
162
+
163
+ @snoop
164
+ def did_key_signature_method_creation (
165
+ multibase_value : hex ,
166
+ raw_public_key_bytes : bytes ,
167
+ ) -> dict [str , str ]:
168
+ # Creating a did:key value consists of creating a cryptographic key pair and
169
+ # encoding the public key using the format provided in Section § 2.
170
+ #
171
+ # NOTE The did:key Format. The creation of a DID Document is also
172
+ # performed by taking the public key value and expanding it into DID
173
+ # Document.
174
+ #
175
+ # An example is given below that expands the ed25519 did:key
176
+ # did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK into its
177
+ # associated DID Document:
178
+ #
179
+ # {
180
+ # "@context": [
181
+ # "https://www.w3.org/ns/did/v1",
182
+ # "https://w3id.org/security/suites/ed25519-2020/v1",
183
+ # "https://w3id.org/security/suites/x25519-2020/v1"
184
+ # ],
185
+ # "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
186
+ # "verificationMethod": [{
187
+ # "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
188
+ # "type": "Ed25519VerificationKey2020",
189
+ # "controller": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
190
+ # "publicKeyMultibase": "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
191
+ # }],
192
+ # "authentication": [
193
+ # "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
194
+ # ],
195
+ # "assertionMethod": [
196
+ # "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
197
+ # ],
198
+ # "capabilityDelegation": [
199
+ # "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
200
+ # ],
201
+ # "capabilityInvocation": [
202
+ # "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
203
+ # ],
204
+ # "keyAgreement": [{
205
+ # "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p",
206
+ # "type": "X25519KeyAgreementKey2020",
207
+ # "controller": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
208
+ # "publicKeyMultibase": "z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p"
209
+ # }]
210
+ # }
211
+
212
+ # 3.1.2 https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm
213
+ # Initialize verificationMethod to an empty object.
214
+ verification_method = {}
215
+
216
+ # Set multicodecValue and rawPublicKeyBytes to the result of passing
217
+ # multibaseValue and options to § 3.1.3 Decode Public Key Algorithm.
218
+ # Ensure the proper key length of rawPublicKeyBytes based on the
219
+ # multicodecValue table provided below:
220
+
221
+ # Multicodec hexadecimal value public key byte length Description
222
+ # 0xe7 33 bytes secp256k1-pub - Secp256k1 public key (compressed)
223
+ # 0xec 32 bytes x25519-pub - Curve25519 public key
224
+ # 0xed 32 bytes ed25519-pub - Ed25519 public key
225
+ # 0x1200 33 bytes p256-pub - P-256 public key (compressed)
226
+ # 0x1201 49 bytes p384-pub - P-384 public key (compressed)
227
+ # 0x1202 ?? bytes p521-pub - P-521 public key (compressed)
228
+ # 0x1205 ?? bytes rsa-pub - RSA public key. DER-encoded ASN.1 type RSAPublicKey according to IETF RFC 8017 (PKCS #1)
229
+ public_key_length_MUST_be = MULTICODEC_VALUE_TABLE .get (
230
+ multibase_value , MULTICODEC_VALUE_NOT_FOUND_IN_TABLE
231
+ )
232
+ if public_key_length_MUST_be is MULTICODEC_VALUE_NOT_FOUND_IN_TABLE :
233
+ raise DIDKeyDecoderNotFoundError (
234
+ f"multibase_value { multibase_value !r} not in MULTICODEC_VALUE_NOT_FOUND_IN_TABLE { MULTICODEC_VALUE_NOT_FOUND_IN_TABLE !r} "
235
+ )
236
+
237
+ # If the byte length of rawPublicKeyBytes does not match the expected public key length for the associated multicodecValue, an invalidPublicKeyLength error MUST be raised.
238
+ if public_key_length_MUST_be is not None and public_key_length_MUST_be != len (
239
+ raw_public_key_bytes
240
+ ):
241
+ raise DIDKeyInvalidPublicKeyLengthError (
242
+ f"public_key_length_MUST_be: { public_key_length_MUST_be } != len(raw_public_key_bytes): { len (raw_public_key_bytes )} "
243
+ )
244
+
245
+ # Ensure the rawPublicKeyBytes are a proper encoding of the public key type
246
+ # as specified by the multicodecValue. This validation is often done by a
247
+ # cryptographic library when importing the public key by, for example,
248
+ # ensuring that an Elliptic Curve public key is a specific coordinate that
249
+ # exists on the elliptic curve. If an invalid public key value is detected,
250
+ # an invalidPublicKey error MUST be raised.
251
+ #
252
+ # SPEC ISSUE: Request for feedback on implementability: It is not clear if
253
+ # this particular check is implementable across all public key types. The
254
+ # group is accepting feedback on the implementability of this particular
255
+ # feature.
256
+ try :
257
+ if multibase_value in (
258
+ MULTICODEC_HEX_P256_PUBLIC_KEY ,
259
+ MULTICODEC_HEX_P384_PUBLIC_KEY ,
260
+ MULTICODEC_HEX_P521_PUBLIC_KEY ,
261
+ ):
262
+ decoded_key = import_ecc_public_key (raw_public_key_bytes )
263
+ else :
264
+ raise DIDKeyDecoderNotFoundError (
265
+ f"No importer for multibase_value { multibase_value !r} "
266
+ )
267
+ except Exception as e :
268
+ raise DIDKeyInvalidPublicKeyError (
269
+ f"invalid raw_public_key_bytes: { raw_public_key_bytes !r} "
270
+ ) from e
271
+
272
+ # STRFKR: Atlantis
273
+
274
+ import jwcrypto .jwk
275
+
276
+ return jwcrypto .jwk .JWK (
277
+ kty = "EC" ,
278
+ crv = "P-384" ,
279
+ x = base64 .urlsafe_b64encode ("public_x" ).decode (),
280
+ y = base64 .urlsafe_b64encode ("public_y" ).decode (),
281
+ ).to_dict ()
282
+
283
+ # TODO
284
+
285
+ # Set the verificationMethod.id value by concatenating identifier, a hash
286
+ # character (#), and the multicodecValue. If verificationMethod.id is not a
287
+ # valid DID URL, an invalidDidUrl error MUST be raised.
288
+
289
+ # Set the publicKeyFormat value to the options.publicKeyFormat value.
290
+
291
+ # If publicKeyFormat is not known to the implementation, an
292
+ # unsupportedPublicKeyType error MUST be raised.
293
+
294
+ # If options.enableExperimentalPublicKeyTypes is set to false and
295
+ # publicKeyFormat is not Multikey, JsonWebKey2020, or
296
+ # Ed25519VerificationKey2020, an invalidPublicKeyType error MUST be raised.
297
+
298
+ # Set verificationMethod.type to the publicKeyFormat value.
299
+
300
+ # Set verificationMethod.controller to the identifier value. If
301
+ # verificationMethod.controller is not a valid DID, an invalidDid error MUST
302
+ # be raised.
303
+
304
+ # If publicKeyFormat is Multikey or Ed25519VerificationKey2020, set the
305
+ # verificationMethod.publicKeyMultibase value to multibaseValue.
306
+
307
+ # If publicKeyFormat is JsonWebKey2020, set the
308
+ # verificationMethod.publicKeyJwk value to the result of passing
309
+ # multicodecValue and rawPublicKeyBytes to § 3.1.4 Encode JWK Algorithm.
310
+
311
+ # Return verificationMethod.
312
+ return verification_method
313
+
314
+
315
+ def did_key_to_jwk_dict_is_p_384_startswith_z82 (
316
+ multibase_value , raw_public_key_bytes
317
+ ) -> dict [str , str ]:
318
+ did_key_signature_method_creation (multibase_value , raw_public_key_bytes )
319
+ # TODO To JWK format
320
+
321
+
322
+ @snoop
35
323
def did_key_to_jwk_dict (
36
324
did_key : str ,
37
325
* ,
@@ -46,10 +334,12 @@ def did_key_to_jwk_dict(
46
334
47
335
Examples
48
336
337
+ - P-384: https://github.com/w3c-ccg/did-method-key/blob/f5abee840c31e92cd1ac11737e0b62103ab99d21/test-vectors/nist-curves.json#L112-L166
338
+
49
339
>>> did_key_to_jwk_dict("did:key:invalid")
50
340
Traceback (most recent call last):
51
341
DIDKeyDecoderNotFoundError: ...
52
- >>> did_key_to_jwk_dict("did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54 ")
342
+ >>> did_key_to_jwk_dict("did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9 ")
53
343
"""
54
344
if decoders_by_prefix is None :
55
345
decoders_by_prefix = {
@@ -61,8 +351,18 @@ def did_key_to_jwk_dict(
61
351
)
62
352
}
63
353
354
+ try :
355
+ multibase_value , raw_public_key_bytes = did_key_decode_public_key (
356
+ did_key .replace (DID_KEY_METHOD , "" , 1 )
357
+ )
358
+ except Exception as e :
359
+ raise DIDKeyDecoderNotFoundError (did_key ) from e
360
+
64
361
for prefix , decoder in decoders_by_prefix .items ():
65
362
if did_key .startswith (DID_KEY_METHOD + prefix ):
66
- return decoder (did_key )
363
+ try :
364
+ return decoder (multibase_value , raw_public_key_bytes )
365
+ except Exception as e :
366
+ raise DIDKeyDecoderError (did_key ) from e
67
367
68
368
raise DIDKeyDecoderNotFoundError (did_key )
0 commit comments