Skip to content
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 _code-samples/did-resolver/README.md
Original file line number Diff line number Diff line change
@@ -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.
27 changes: 27 additions & 0 deletions _code-samples/did-resolver/js/Get_Signature.cjs
Original file line number Diff line number Diff line change
@@ -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 };
3 changes: 3 additions & 0 deletions _code-samples/did-resolver/js/README.md
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 10 additions & 0 deletions _code-samples/did-resolver/js/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
164 changes: 164 additions & 0 deletions _code-samples/did-resolver/js/resolver.js
Original file line number Diff line number Diff line change
@@ -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;
}
}

85 changes: 85 additions & 0 deletions docs/concepts/identity/index.md
Original file line number Diff line number Diff line change
@@ -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.


1 change: 1 addition & 0 deletions sidebars.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down