Current section
Files
Jump to
Current section
Files
lib/did_key.ex
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