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

Add ns record gateway handling #576

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/dids/src/did-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ export enum DidErrorCode {
/** The DID resolver was unable to find the DID document resulting from the resolution request. */
NotFound = 'notFound',

/** The DID resolver was unable to find the DID document in any authoritative gateway. */
NotFoundInAuthoritativeGateWay = 'notFoundInAuthoritativeGateWay',

/**
* The representation requested via the `accept` input metadata property is not supported by the
* DID method and/or DID resolver implementation.
Expand Down
65 changes: 63 additions & 2 deletions packages/dids/src/methods/did-dht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -805,10 +805,40 @@ export class DidDhtDocument {
const publicKeyBytes = DidDhtUtils.identifierToIdentityKeyBytes({ didUri });

// Retrieve the signed BEP44 message from a DID DHT Gateway or Pkarr relay.
const bep44Message = await DidDhtDocument.pkarrGet({ gatewayUri, publicKeyBytes });
let bep44Message = await DidDhtDocument.pkarrGet({ gatewayUri, publicKeyBytes });

// Verify the signature of the BEP44 message and parse the value to a DNS packet.
const dnsPacket = await DidDhtUtils.parseBep44GetMessage({ bep44Message });
let dnsPacket: Packet | undefined = await DidDhtUtils.parseBep44GetMessage({ bep44Message });

// Look at the NS records in the DNS packet to find the resolution gateway URIs.
const resolutionGatewayUris = await DidDhtDocument.getAuthoritativeGatewayUris({ didUri, dnsPacket });

// Only do a second retrieval if authoritative resolution gateway URIs are specified and are different from the given gateway URI.
if(resolutionGatewayUris.length > 0 && !resolutionGatewayUris.includes(gatewayUri)) {
dnsPacket = undefined; // reset to `undefined` to use as a condition check for throwing an error
const accumulatedErrors = [];
for(const nsRecordGatewayUri of resolutionGatewayUris) {
try {
bep44Message = await DidDhtDocument.pkarrGet({ gatewayUri: nsRecordGatewayUri, publicKeyBytes });
dnsPacket = await DidDhtUtils.parseBep44GetMessage({ bep44Message });
} catch (error: any) {
accumulatedErrors.push(`Failed retrieval from ${nsRecordGatewayUri}: ${error}`);

// If the retrieval failed, try the next resolution gateway.
continue;
}

// If the retrieval was successful, break the loop.
break;
}

if(dnsPacket === undefined) {
throw new DidError(
DidErrorCode.NotFoundInAuthoritativeGateWay,
`DID document not found for: ${didUri} after looping through all authoritative gateways. Errors: ${accumulatedErrors.join('; ')}`
);
}
}

// Convert the DNS packet to a DID document and metadata.
const resolutionResult = await DidDhtDocument.fromDnsPacket({ didUri, dnsPacket });
Expand Down Expand Up @@ -965,6 +995,37 @@ export class DidDhtDocument {
return response.ok;
}


/**
* Extracts authoritative gateway URIs from a DNS packet based on the DID DHT specifications.
* This method filters NS records related to the provided DID URI and extracts gateway URIs
* that are used to resolve the complete DID document.
*
* @see {@link https://did-dht.com/#designating-authoritative-gateways | DID DHT Specification, § Authoritative Gateways}
*
* @param {object} params - The parameters to use when extracting gateway URIs from the DNS packet.
* @param {string} params.didUri - The DID URI corresponding to the DNS packet.
* @param {Packet} params.dnsPacket - The DNS packet containing potential NS records for resolution gateways.
* @returns {Promise<string[]>} Resolves to an array of gateway URIs without trailing '.' if found, otherwise an empty array.
*/
public static async getAuthoritativeGatewayUris({ didUri, dnsPacket }: {
didUri: string;
dnsPacket: Packet;
}): Promise<string[]> {
const authoritativeGatewayUris: string[] = [];

for (const answer of dnsPacket?.answers ?? []) {
if (answer.type !== 'NS') continue;

if (answer.name.endsWith(`.${DidDhtDocument.getUniqueDidSuffix(didUri)}.`)) {
const gatewayUri = answer.data.slice(0, -1); // Remove trailing dot
authoritativeGatewayUris.push(gatewayUri);
}
}

return authoritativeGatewayUris;
}

/**
* Converts a DNS packet to a DID document according to the DID DHT specification.
*
Expand Down
86 changes: 86 additions & 0 deletions packages/dids/tests/methods/did-dht.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,92 @@ describe('DidDhtDocument', () => {
}
}
});

it('handles custom authoritative gateways', async () => {

const didDocument: DidDocument = {
id : 'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery',
verificationMethod : [
{
id : 'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery#0',
type : 'JsonWebKey',
controller : 'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery',
publicKeyJwk : {
crv : 'Ed25519',
kty : 'OKP',
x : '2zHGF5m_DhcPbBZB6ooIxIOR-Vw-yJVYSPo2NgCMkgg',
kid : 'KDT9PKj4_z7gPk2s279Y-OGlMtt_L93oJzIaiVrrySU',
alg : 'EdDSA',
},
},
{
id : 'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery#sig',
type : 'JsonWebKey',
controller : 'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery',
publicKeyJwk : {
crv : 'Ed25519',
kty : 'OKP',
x : 'FrrBhqvAWxE4lstj-IWgN8_5-O4L1KuZjdNjn5bX_dw',
kid : 'dRnxo2XQ7QT1is5WmpEefwEz3z4_4JdpGea6KWUn3ww',
alg : 'EdDSA',
},
},
{
id : 'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery#enc',
type : 'JsonWebKey',
controller : 'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery',
publicKeyJwk : {
kty : 'EC',
crv : 'secp256k1',
x : 'e1_pCWZwI9cxdrotVKIT8t75itk22XkpalDPx7pVpYQ',
y : '5cAlBmnzzuwRNuFtLhyFNdy9v1rVEqEgrFEiiwKMx5I',
kid : 'jGYs9XgQMDH_PCDFWocTN0F06mTUOA1J1McVvluq4lM',
alg : 'ES256K',
},
},
],
authentication: [
'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery#0',
'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery#sig',
],
assertionMethod: [
'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery#0',
'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery#sig',
],
capabilityDelegation: [
'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery#0',
],
capabilityInvocation: [
'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery#0',
],
keyAgreement: [
'did:dht:5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery#enc',
],
};

const authoritativeGatewayUris = ['gateway1.example-did-dht-gateway.com', 'gateway2.example-did-dht-gateway.com'];

const dnsPacket = await DidDhtDocument.toDnsPacket({
didDocument,
didMetadata: {
published: true,
},
authoritativeGatewayUris
});

for (const record of dnsPacket.answers ?? []) {
if (record.type !== 'NS')
continue;

expect(record.name).to.equal('_did.5cahcfh3zh8bqd5cn3y6inoea1b3d6kh85rjksne9e5dcyrc1ery.');
expect(record.data).to.match(/(gateway1.example-did-dht-gateway.com.|gateway2.example-did-dht-gateway.com.)/);
Dismissed Show dismissed Hide dismissed
Dismissed Show dismissed Hide dismissed
}

const resolutionGatewayUris = await DidDhtDocument.getAuthoritativeGatewayUris({ didUri: didDocument.id, dnsPacket });

expect(resolutionGatewayUris).to.have.length(2);
expect(resolutionGatewayUris).to.have.members(authoritativeGatewayUris);
});
});

describe('Web5TestVectorsDidDht', () => {
Expand Down
Loading