defmodule DidKey do @moduledoc """ This module contains functions to resolve a DID key. """ @x25519_multicodec_value 0xEC @ed25519_multicodec_value 0xED @doc """ Resolves the DID document from a `did:key` value or an error. Currently, only Ed25519 `did:key` DIDs are supported. ## Examples # Default HTTPS request iex> DidKey.resolve("did:key:z6MkvfMy7RapuFpSEHgCULoKyWagWVZLGM32hWKpHL7kQAWk") {:ok, did_document} iex> DidKey.resolve("did:key:example") {:error, "Invalid DID key. Needs to contain multibase base58-btc encoded key material"} iex> DidKey.resolve("example") {:error, "DID does not start with 'did:key:'"} """ @doc since: "0.1.0" @spec resolve(did :: String.t()) :: {:ok, map()} | {:error, String.t()} def resolve(did = "did:key:z" <> base58_string) do with {:ok, signing_key_bytes} <- decode_signing_key(base58_string), {:ok, encryption_key_bytes} <- derive_encryption_key(signing_key_bytes) do {:ok, create_document(did, base58_string, encryption_key_bytes)} else {:error, message} -> {:error, message} end end def resolve("did:key:" <> _) do {:error, "Invalid DID key. Needs to contain multibase base58-btc encoded key material"} end def resolve(_) do {:error, "DID does not start with 'did:key:'"} end @spec decode_signing_key(base58_string :: String.t()) :: {:ok, binary()} | {:error, String.t()} defp decode_signing_key(base58_string) do {multicodec_value, bytes} = Base58.decode(base58_string) |> Varint.LEB128.decode() case {multicodec_value, byte_size(bytes)} do {@ed25519_multicodec_value, 32} -> {:ok, bytes} {@ed25519_multicodec_value, _} -> {:error, "Invalid key length"} _ -> {:error, "Unknown key type"} end end @spec derive_encryption_key(bytes :: binary()) :: {:ok, binary()} defp derive_encryption_key(bytes) do {:ok, %Eddy.PubKey{point: point}} = Eddy.PubKey.from_bin(bytes) multibase_public_key = Varint.LEB128.encode(@x25519_multicodec_value) <> Eddy.X25519.from_point(point) {:ok, Base58.encode(multibase_public_key)} end @spec create_document( did :: String.t(), signing_public_key :: binary(), encryption_public_key :: binary() ) :: map() defp create_document(did, signing_public_key, encryption_public_key) do signing_public_key_multibase = "z" <> signing_public_key verification_method = %{ id: did <> "#" <> signing_public_key_multibase, type: "Ed25519VerificationKey2020", controller: did, publicKeyMultibase: signing_public_key_multibase } encryption_public_key_multibase = "z" <> encryption_public_key key_agreement = %{ id: did <> "#" <> encryption_public_key_multibase, type: "X25519KeyAgreementKey2020", controller: did, pubicKeyMultibase: encryption_public_key_multibase } %{ "@context" => [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1", "https://w3id.org/security/suites/x25519-2020/v1" ], id: did, verificationMethod: [verification_method], authentication: [verification_method.id], assertionMethod: [verification_method.id], capabilityDelegation: [verification_method.id], capabilityInvocation: [verification_method.id], keyAgreement: [key_agreement] } end end