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 5 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
62 changes: 60 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,36 @@ 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 = await DidDhtUtils.parseBep44GetMessage({ bep44Message });

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

// Only do a second retrieval if the authoritative resolution gateway URIs are different from the given gateway URI.
if(!resolutionGatewayUris.includes(gatewayUri)) {
Copy link
Member

Choose a reason for hiding this comment

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

does this work with the . / FQDN syntax?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, it takes out the trailing . and the documentation is updated .

The resolutionGatewayUris will be the value in the ns minus the last .

thehenrytsai marked this conversation as resolved.
Show resolved Hide resolved
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(nsRecordGatewayUri == resolutionGatewayUris[resolutionGatewayUris.length - 1]) {
throw new Error(`DID document not found for: ${didUri}. Errors: ${accumulatedErrors.join('; ')}`);
}

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

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

// Convert the DNS packet to a DID document and metadata.
const resolutionResult = await DidDhtDocument.fromDnsPacket({ didUri, dnsPacket });
Expand Down Expand Up @@ -965,6 +991,38 @@ 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 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);
break;
}
}

return authoritativeGatewayUris;
}

/**
* Converts a DNS packet to a DID document according to the DID DHT specification.
*
Expand Down
76 changes: 76 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,82 @@
}
}
});

it('handles custom authoritative gateways', async () => {
const dnsPacket = await DidDhtDocument.toDnsPacket({
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',
],
},
didMetadata: {
published: true,
},
authoritativeGatewayUris: ['gateway1.example-did-dht-gateway.com', 'gateway2.example-did-dht-gateway.com']
});

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
}
});
});

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