Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always set alg and kid on expansion #502

Closed
wants to merge 18 commits into from
Closed
38 changes: 34 additions & 4 deletions packages/dids/src/methods/did-dht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { extractDidFragment } from '../utils.js';
import { DidError, DidErrorCode } from '../did-error.js';
import { DidVerificationRelationship } from '../types/did-core.js';
import { EMPTY_DID_RESOLUTION_RESULT } from '../types/did-resolution.js';
import { getJoseSignatureAlgorithmFromPublicKey } from '@web5/crypto/utils';

/**
* Represents a BEP44 message, which is used for storing and retrieving data in the Mainline DHT
Expand Down Expand Up @@ -347,6 +348,15 @@ export enum DidDhtRegisteredKeyType {
secp256r1 = 2
}

/**
* Private helper that maps did dht registered key types to their corresponding default algorithm identifiers.
*/
const KeyTypeToDefaultAlgorithmMap = {
[DidDhtRegisteredKeyType.Ed25519] : 'EdDSA',
thehenrytsai marked this conversation as resolved.
Show resolved Hide resolved
[DidDhtRegisteredKeyType.secp256k1] : 'ES256K',
[DidDhtRegisteredKeyType.secp256r1] : 'ES256',
} as const;
nitro-neal marked this conversation as resolved.
Show resolved Hide resolved

/**
* Maps {@link https://www.w3.org/TR/did-core/#verification-relationships | DID Core Verification Relationship}
* values to the corresponding record name in the DNS packet representation of a DHT DID document.
Expand Down Expand Up @@ -1013,8 +1023,8 @@ export class DidDhtDocument {
// Process verification methods.
case dnsRecordId.startsWith('k'): {
// Get the method ID fragment (id), key type (t), Base64URL-encoded public key (k), and
// optionally, controller (c) from the decoded TXT record data.
const { id, t, k, c } = DidDhtUtils.parseTxtDataToObject(answer.data);
// optionally, controller (c) and alg (a) from the decoded TXT record data.
const { id, t, k, c, a: parsedAlg } = DidDhtUtils.parseTxtDataToObject(answer.data);

// Convert the public key from Base64URL format to a byte array.
const publicKeyBytes = Convert.base64Url(k).toUint8Array();
Expand All @@ -1025,6 +1035,16 @@ export class DidDhtDocument {
// Convert the public key from a byte array to JWK format.
let publicKey = await DidDhtUtils.keyConverter(namedCurve).bytesToPublicKey({ publicKeyBytes });

// Always set the algorithm on did:dht expansion.
nitro-neal marked this conversation as resolved.
Show resolved Hide resolved
publicKey.alg = parsedAlg || getJoseSignatureAlgorithmFromPublicKey(publicKey);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sanity: getJoseSignatureAlgorithmFromPublicKey() does not seem to return Ed25519 that will be expected of vector 3.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

am I using this correctly?

export function getJoseSignatureAlgorithmFromPublicKey(publicKey: Jwk): string {
  const curveToJoseAlgorithm: Record<string, string> = {
    'Ed25519'   : 'EdDSA',
    'P-256'     : 'ES256',
    'P-384'     : 'ES384',
    'P-521'     : 'ES512',
    'secp256k1' : 'ES256K',
  };

  // If the key contains an `alg` property that matches a JOSE registered algorithm identifier,
  // return its value.
  if (publicKey.alg && Object.values(curveToJoseAlgorithm).includes(publicKey.alg)) {
    return publicKey.alg;
  }

  // If the key contains a `crv` property, return the corresponding algorithm.
  if (publicKey.crv && Object.keys(curveToJoseAlgorithm).includes(publicKey.crv)) {
    return curveToJoseAlgorithm[publicKey.crv];
  }

  throw new Error(
    `Unable to determine algorithm based on provided input: alg=${publicKey.alg}, crv=${publicKey.crv}. ` +
    `Supported 'alg' values: ${Object.values(curveToJoseAlgorithm).join(', ')}. ` +
    `Supported 'crv' values: ${Object.keys(curveToJoseAlgorithm).join(', ')}.`
  );
}

Copy link
Contributor Author

@nitro-neal nitro-neal May 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the alg is always there for our jwks, but is that not necessarily the case for outside jwks inside the did dht??

if the alg is always there we can just do

if(parsedAlg) {
   publicKey.alg = parsedAlg
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get if(parsedAlg) { publicKey.alg = parsedAlg }, if parsedAlg is always defined, then there is nothing do right? since:

const { id, t, k, c, a: parsedAlg } = DidDhtUtils.parseTxtDataToObject(answer.data);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parsed alg will not always be defined, but publicKey.alg is always defined


// Determine the Key ID (kid): '0' for the identity key or JWK thumbprint for others. Always set alg on expansion.
nitro-neal marked this conversation as resolved.
Show resolved Hide resolved
if (id !== '0' && publicKey.kid === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my understanding: why would kid be defined when id is not 0? May have opportunity to simplify the condition here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

simplifying to:

          if (id === '0') {
            publicKey.kid = '0';
          } else if (publicKey.kid === undefined) {
            publicKey.kid = await computeJwkThumbprint({ jwk: publicKey });
          }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the publicKey.kid should be defined, but if it is not, we can recompute it deterministically with computeJwkThumbprint

publicKey.kid = await computeJwkThumbprint({ jwk: publicKey });
thehenrytsai marked this conversation as resolved.
Show resolved Hide resolved
} else if (id === '0') {
publicKey.kid = '0';
}

// Initialize the `verificationMethod` array if it does not already exist.
didDocument.verificationMethod ??= [];

Expand Down Expand Up @@ -1159,7 +1179,11 @@ export class DidDhtDocument {
let methodId = vm.id.split('#').pop()!; // Remove fragment prefix, if any.
idLookup.set(methodId, dnsRecordId);

const publicKey = vm.publicKeyJwk;
const publicKey = vm.publicKeyJwk!;

if(methodId === '0') {
publicKey.kid = '0';
}
nitro-neal marked this conversation as resolved.
Show resolved Hide resolved

if (!(publicKey?.crv && publicKey.crv in AlgorithmToKeyTypeMap)) {
throw new DidError(DidErrorCode.InvalidPublicKeyType, `Verification method '${vm.id}' contains an unsupported key type: ${publicKey?.crv ?? 'undefined'}`);
Expand All @@ -1168,14 +1192,20 @@ export class DidDhtDocument {
// Use the public key's `crv` property to get the DID DHT key type.
const keyType = DidDhtRegisteredKeyType[publicKey.crv as keyof typeof DidDhtRegisteredKeyType];

let alg;
if(KeyTypeToDefaultAlgorithmMap[keyType] !== getJoseSignatureAlgorithmFromPublicKey(publicKey))
{
alg = getJoseSignatureAlgorithmFromPublicKey(publicKey);
}

// Convert the public key from JWK format to a byte array.
const publicKeyBytes = await DidDhtUtils.keyConverter(publicKey.crv).publicKeyToBytes({ publicKey });

// Convert the public key from a byte array to Base64URL format.
const publicKeyBase64Url = Convert.uint8Array(publicKeyBytes).toBase64Url();

// Define the data for the DNS TXT record.
const txtData = [`id=${methodId}`, `t=${keyType}`, `k=${publicKeyBase64Url}`];
const txtData = [`id=${methodId}`, `t=${keyType}`, `k=${publicKeyBase64Url}`, ...(alg ? [`a=${alg}`] : [])];
nitro-neal marked this conversation as resolved.
Show resolved Hide resolved

// Add the controller property, if set to a value other than the Identity Key (DID Subject).
if (vm.controller !== didDocument.id) txtData.push(`c=${vm.controller}`);
Expand Down
Loading