pyvckit/did.py

149 lines
4.2 KiB
Python

import json
import argparse
import requests
import multicodec
import multiformats
import nacl.signing
import nacl.encoding
from jwcrypto import jwk
from urllib.parse import urlparse
from nacl.signing import SigningKey
from nacl.encoding import RawEncoder
from templates import did_document_tmpl
def key_to_did(public_key_bytes, url):
"""did-key-format :=
did:key:MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))"""
mc = multicodec.add_prefix('ed25519-pub', public_key_bytes)
# Multibase encode the hashed bytes
did = multiformats.multibase.encode(mc, 'base58btc')
if url:
u = urlparse(url)
domain = u.netloc
path = u.path.strip("/").replace("/", ":")
return f"did:web:{domain}:{path}:{did}"
return f"did:key:{did}"
def key_read(path_keys):
# Save the private JWK to a file
with open(path_keys, 'r') as f:
private_jwk = f.read()
return private_jwk
def get_signing_key(jwk_pr):
key = json.loads(jwk_pr)
private_key_material_str = key['d']
missing_padding = len(private_key_material_str) % 4
if missing_padding:
private_key_material_str += '=' * (4 - missing_padding)
private_key_material = nacl.encoding.URLSafeBase64Encoder.decode(private_key_material_str)
signing_key = SigningKey(private_key_material, encoder=RawEncoder)
return signing_key
def generate_did(jwk_pr, url=None):
signing_key = get_signing_key(jwk_pr)
verify_key = signing_key.verify_key
public_key_bytes = verify_key.encode()
# Generate the DID
did = key_to_did(public_key_bytes, url)
return did
def generate_keys():
# Generate an Ed25519 key pair
key = jwk.JWK.generate(kty='OKP', crv='Ed25519')
key['kid'] = 'Generated'
key_json = key.export_private(True)
return json.dumps(key_json)
def gen_did_document(did, keys):
if did[:8] != "did:web:":
return "", ""
document = did_document_tmpl.copy()
webdid_owner = did+"#owner"
webdid_revocation = did+"#revocation"
document["id"] = did
document["verificationMethod"][0]["id"] = webdid_owner
document["verificationMethod"][0]["controller"] = did
document["verificationMethod"][0]["publicKeyJwk"]["x"] = keys["x"]
document["authentication"].append(webdid_owner)
document["assertionMethod"].append(webdid_owner)
document["service"][0]["id"] = webdid_revocation
document_fixed_serialized = json.dumps(document)
url = "https://" + "/".join(did.split(":")[2:]) + "/did.json"
return url, document_fixed_serialized
def resolve_did(did):
if did[:8] != "did:web:":
return
try:
url = "https://" + "/".join(did.split(":")[2:]) + "/did.json"
response = requests.get(url)
except Exception:
url = "http://" + "/".join(did.split(":")[2:]) + "/did.json"
response = requests.get(url)
if 200 <= response.status_code < 300:
return response.json()
def main():
parser=argparse.ArgumentParser(description='Generates a new did or key pair')
parser.add_argument("-k", "--key-path", required=False)
parser.add_argument("-n", "--new", choices=['keys', 'did'])
parser.add_argument("-u", "--url")
parser.add_argument("-g", "--gen-doc")
args=parser.parse_args()
if args.new == 'keys':
keyspair = generate_keys()
print(keyspair)
return
if not args.key_path and args.new == 'did':
print("error: argument --key-path: expected one argument")
return
if args.new == 'did' and args.url:
key = key_read(args.key_path)
did = generate_did(key, args.url)
print(did)
return
if args.new == 'did':
key = key_read(args.key_path)
did = generate_did(key)
print(did)
return
if args.gen_doc and not args.key_path:
print("error: argument --key-path: expected one argument")
return
if args.gen_doc:
keys = json.loads(key_read(args.key_path))
if not keys.get("x"):
print("error: argument --key-path: not is valid")
return
url, doc = gen_did_document(args.gen_doc, keys)
# print(url)
print(doc)
return
if __name__ == "__main__":
main()