From 61651e3e940f1ff487c963f46cf2a42fc0ecd6fc Mon Sep 17 00:00:00 2001 From: zshahzadpumacy Date: Tue, 4 Feb 2025 15:09:40 +0100 Subject: [PATCH] Add documentation and code samples for DID Resolver on XRPL --- _code-samples/did-resolver/README.md | 3 + .../did-resolver/js/Get_Signature.cjs | 27 +++ _code-samples/did-resolver/js/README.md | 3 + _code-samples/did-resolver/js/package.json | 10 ++ _code-samples/did-resolver/js/resolver.js | 164 ++++++++++++++++++ docs/concepts/identity/index.md | 85 +++++++++ sidebars.yaml | 1 + 7 files changed, 293 insertions(+) create mode 100644 _code-samples/did-resolver/README.md create mode 100644 _code-samples/did-resolver/js/Get_Signature.cjs create mode 100644 _code-samples/did-resolver/js/README.md create mode 100644 _code-samples/did-resolver/js/package.json create mode 100644 _code-samples/did-resolver/js/resolver.js create mode 100644 docs/concepts/identity/index.md diff --git a/_code-samples/did-resolver/README.md b/_code-samples/did-resolver/README.md new file mode 100644 index 00000000000..58f8be66bd6 --- /dev/null +++ b/_code-samples/did-resolver/README.md @@ -0,0 +1,3 @@ +# Decentralized Identifiers (DIDs) Verifications + +Decentralized Identifiers (DIDs) verification ensures secure, trustless authentication by using cryptographic proofs without relying on centralized authorities. diff --git a/_code-samples/did-resolver/js/Get_Signature.cjs b/_code-samples/did-resolver/js/Get_Signature.cjs new file mode 100644 index 00000000000..5ed58f4ba19 --- /dev/null +++ b/_code-samples/did-resolver/js/Get_Signature.cjs @@ -0,0 +1,27 @@ +const EC = require('elliptic').ec; +const crypto = require('crypto'); + +const ec = new EC('secp256k1'); + +/** + * Generates a signature for predefined data using the provided private key. + * @param {string} privateKeyHex - The private key in hex format. + * @returns {Object} - The signature object. + */ +function getSignature(privateKeyHex) { + const keyPair = ec.keyFromPrivate(privateKeyHex); + + // Declare the data to sign + const dataToSign = 'Helloworld'; + + // Generate the hash for the data + const hash = crypto.createHash('sha256').update(dataToSign).digest(); + console.log('Node.js Hash:', hash); + + const signature = keyPair.sign(hash); + console.log('r:', signature.r.toString(16)); + console.log('s:', signature.s.toString(16)); + return signature; +} + +module.exports = { getSignature }; diff --git a/_code-samples/did-resolver/js/README.md b/_code-samples/did-resolver/js/README.md new file mode 100644 index 00000000000..12c5b7fe3b3 --- /dev/null +++ b/_code-samples/did-resolver/js/README.md @@ -0,0 +1,3 @@ +# Decentralized Identifiers Verification + +This XRPL DID Resolver enables developers to retrieve, validate, and verify DIDs on the XRP Ledger. It provides functionality for resolving DIDs and verifying digital signatures using elliptic curve cryptography. diff --git a/_code-samples/did-resolver/js/package.json b/_code-samples/did-resolver/js/package.json new file mode 100644 index 00000000000..c971747edb4 --- /dev/null +++ b/_code-samples/did-resolver/js/package.json @@ -0,0 +1,10 @@ +{ + "type": "module", + "dependencies": { + "did-resolver": "^4.1.0", + "elliptic": "^6.6.1", + "express": "^4.21.1", + "node-fetch": "^3.3.2", + "xrpl": "^4.0.0" + } +} diff --git a/_code-samples/did-resolver/js/resolver.js b/_code-samples/did-resolver/js/resolver.js new file mode 100644 index 00000000000..a9ef5df3152 --- /dev/null +++ b/_code-samples/did-resolver/js/resolver.js @@ -0,0 +1,164 @@ +import { Resolver } from 'did-resolver'; +import { Client } from 'xrpl'; +import fetch from 'node-fetch'; +import crypto from 'crypto'; +import elliptic from 'elliptic'; + +// Initialize the elliptic curve for signature verification +const ec = new elliptic.ec('secp256k1'); + +const XRPL_NODE = 'wss://s.devnet.rippletest.net:51233'; + +/** + * Fetch DID object from the XRPL Ledger + */ +async function getDIDObject(address) { + const client = new Client(XRPL_NODE); + await client.connect(); + let result; + try { + result = await client.request({ + command: 'ledger_entry', + did: address, + }); + } catch (e) { + if (e.message === 'entryNotFound') { + throw new Error('DID Not Found'); + } + console.log(e.message); + throw e; + } finally { + await client.disconnect(); + } + return result.result.node; +} + +/** + * Retrieve and parse the DID document + */ +async function getDID(address) { + const object = await getDIDObject(address); + + if (object.LedgerEntryType !== 'DID') { + throw new Error('Unexpected LedgerEntryType'); + } + + let decodedData = Buffer.from(object.URI, 'hex').toString('utf8'); + + try { + const response = await fetch(decodedData); + if (!response.ok) { + throw new Error("Network response was not ok " + response.statusText); + } + const jsonData = await response.json(); + return jsonData; + } catch (error) { + console.error("Error fetching JSON document:", error); + return null; + } +} + +/** + * Process DID and validate the document + */ +async function processDID(did, address) { + let result; + try { + result = await getDID(address); + if (typeof result !== 'object') { + return { + error: { + error: 'unsupportedFormat', + message: 'DID does not resolve to a valid document containing a JSON document', + }, + }; + } + if (result?.id !== did) { + return { + error: { + error: 'notFound', + message: 'DID document ID does not match requested DID', + }, + }; + } + } catch (error) { + return { + error: { + error: 'resolver_error', + message: `DID must resolve to a valid document containing a JSON document: ${error}`, + }, + }; + } + return { result }; +} + +/** + * DID Resolver Function + */ +async function resolveDID(did, parsed) { + const address = parsed.id; + const didDocumentMetadata = {}; + const result = await processDID(did, address); + + if (result.error) { + return { + didDocument: null, + didDocumentMetadata, + didResolutionMetadata: result.error, + }; + } + + const didDocument = result.result || null; + const contentType = + typeof didDocument?.['@context'] !== 'undefined' + ? 'application/did+ld+json' + : 'application/did+json'; + + return { + didDocument, + didDocumentMetadata, + didResolutionMetadata: { contentType }, + }; +} + +/** + * DID Resolver Object + */ +const xrplResolver = { + xrpl: resolveDID +}; + +/** + * Verify Signature using DID and Public Key + */ +export async function verifySignature(did, signature) { + try { + const didResolver = new Resolver({ ...xrplResolver }); + + const doc = await didResolver.resolve(did); + + // Extract Public Key from the DID document + let publicKeyHex = doc.didDocument?.publicKey?.map(key => key.publicKeyHex) ?? []; + if (Array.isArray(publicKeyHex) && publicKeyHex.length > 0) { + publicKeyHex = publicKeyHex[0]; + } else { + throw new Error('No public key found in DID document'); + } + + // Hash the data + const data = 'Helloworld'; + const hash = crypto.createHash('sha256').update(data).digest(); + + // Convert public key to elliptic curve format + const publicKey = ec.keyFromPublic(publicKeyHex, 'hex'); + + // Verify the signature + const isValid = publicKey.verify(hash, signature); + + return isValid; + } catch (error) { + console.error('Error during signature verification:', error); + return false; + } +} + diff --git a/docs/concepts/identity/index.md b/docs/concepts/identity/index.md new file mode 100644 index 00000000000..1256be0aac4 --- /dev/null +++ b/docs/concepts/identity/index.md @@ -0,0 +1,85 @@ +--- +title: "Decentralized Identity" +sidebar_label: "DID Resolver" +--- + +# Decentralized Identity + +This document explains how to use the XRPL DID Resolver for verifying Decentralized Identifiers (DIDs) on the XRP Ledger (XRPL). The resolver interacts with XRPL's ledger to fetch, validate, and process DIDs using the `xrpl-did-resolver` package. + +## Inputs Required + +Following inputs are required for verification: + +- **DID Identifier**: A valid DID stored on the XRPL Ledger. +- **Signature**: A signature to be verified against the DID's public key. + +## How It Works +Assuming that a Decentralized Identity (DID) already exists on the XRPL ledger and a valid signature is available for verification. If a signature is not available, it can be generated using the following code snippet: + +```javascript +function getSignature(privateKey) { + const keyPair = ec.keyFromPrivate(privateKey); + const hash = crypto.createHash('sha256').update('Helloworld').digest(); + return keyPair.sign(hash); +} +``` +### Fetching a DID Object from XRPL + +To retrieve the DID entry from the XRPL ledger, the function `getDIDObject` is used. It establishes a connection with the XRPL node, requests the ledger entry for the provided DID, and returns the stored object. +```javascript +async function getDIDObject(address) { + const client = new Client(XRPL_NODE); + await client.connect(); + const result = await client.request({ command: 'ledger_entry', did: address }); + await client.disconnect(); + return result.result.node; +} +``` +### Retrieving and Parsing a DID Document + +After fetching the DID object, the function `getDID` decodes the URI field and fetches the DID document from an external source if necessary. +```javascript +async function getDID(address) { + const object = await getDIDObject(address); + const decodedData = Buffer.from(object.URI, 'hex').toString('utf8'); + const response = await fetch(decodedData); + return await response.json(); +} +``` +### Processing a DID Document + +The function `processDID` ensures the DID document matches the requested DID and returns the validated data. +```javascript +async function processDID(did, address) { + const result = await getDID(address); + if (result?.id !== did) throw new Error('DID document ID mismatch'); + return { result }; +} +``` +### Resolving a DID + +To fully resolve a DID, the function `resolveDID` processes the DID and returns the structured DID document along with metadata. +```javascript +async function resolveDID(did, parsed) { + const address = parsed.id; + const result = await processDID(did, address); + return { didDocument: result.result, didResolutionMetadata: { contentType: 'application/did+json' } }; +} +``` +### Verifying a Signature + +To verify a digital signature, the function `verifySignature` extracts the public key from the DID document and verifies the given signature using elliptic curve cryptography. +```javascript +export async function verifySignature(did, signature) { + const didResolver = new Resolver({ ...xrplResolver }); + const doc = await didResolver.resolve(did); + const publicKeyHex = doc.didDocument?.publicKey?.[0]?.publicKeyHex; + const hash = crypto.createHash('sha256').update('Helloworld').digest(); + const publicKey = ec.keyFromPublic(publicKeyHex, 'hex'); + return publicKey.verify(hash, signature); +} +``` +For further clarification, [resolver.js](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/did-resolver) file is available in code samples. + + diff --git a/sidebars.yaml b/sidebars.yaml index 9760440c304..94575db74ac 100644 --- a/sidebars.yaml +++ b/sidebars.yaml @@ -163,6 +163,7 @@ items: - page: docs/concepts/decentralized-storage/decentralized-identifiers.md - page: docs/concepts/decentralized-storage/price-oracles.md + - page: docs/concepts/identity/index.md - page: docs/tutorials/index.md label: Tutorials labelTranslationKey: sidebar.docs.tutorials