Skip to content

Commit

Permalink
feat: Re-enable hash check on operations (#463)
Browse files Browse the repository at this point in the history
* Added opcid (operation CID) to mdip on resolution

* Fix for json-cache

* Renamed anchorSeed to generateDID

* Fixed opcid in v1

* Replaced prev (doc hash) with cid (op hash) in operations

* Improved coverage

* Refactored importEvent

* Improved opcid tests

* Update MDIP scheme doc

* Cleaned up test code

* Renamed opcid and cid to opid and previd respectively
  • Loading branch information
macterra authored Dec 9, 2024
1 parent 80a5cbe commit 9159d7a
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 54 deletions.
12 changes: 6 additions & 6 deletions doc/mdip/scheme.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ A DID Update is a change to any of the documents associated with the DID. To ini
1. `didDocumentMetadata` the document's metadata
1. `didDocumentData` the document's data
1. `mdip` the MDIP protocol spec
1. `prev` the sha256 hash of the canonicalized JSON of the previous version's doc
1. `previd` the CID of the previous operation
1. Sign the JSON with the private key of the controller of the DID
1. Submit the operation to the MDIP node. For example, with a REST API, post the operation to the MDIP node's endpoint to update DIDs (e.g. `/api/v1/did/`)

Expand Down Expand Up @@ -191,7 +191,7 @@ Example update to rotate keys for an agent DID:
"version": 1
}
},
"prev": "fb794984f44fe869a75fade8a7bf31ce0f3f46a3eaded4e286769c62f5d9a9ff",
"previd": "z3v8Auaa5U9xP6TRzobvzZE7j6N8nkatxW1UuWiay5xrbAR5D9e",
"signature": {
"signer": "did:mdip:test:z3v8AuadvRQErtPapNx3ncdUJpPc5dBDGTXXiRxsaH2N8Lj2KzL",
"signed": "2024-03-25T14:57:26.343Z",
Expand All @@ -203,7 +203,7 @@ Example update to rotate keys for an agent DID:

Upon receiving the operation the MDIP node must:
1. Verify the signature is valid for the controller of the DID.
1. Verify the previous hash.
1. Verify the previd is identical to the latest version's operation CID.
1. Record the operation on the DID specified registry (or forward the request to a trusted node that supports the specified registry).

For registries such as BTC with non-trivial transaction costs, it is expected that update operations will be placed in a queue, then registered periodically in a batch in order to balance costs and latency of updates. If the registry has trivial transaction costs, the update operation may be distributed individually and immediately. MDIP defers this tradeoff between cost, speed, and security to the node operators.
Expand All @@ -217,7 +217,7 @@ To revoke a DID, the MDIP client must sign and submit a `delete` operation to th
1. Create a operation object with these fields in any order:
1. `type` must be "delete"
1. `did` specifies the DID to be deleted
1. `prev` the sha256 hash of the canonicalized JSON of the previous version's doc
1. `previd` the CID of the previous operation
1. Sign the JSON with the private key of the controller of the DID
1. Submit the operation to the MDIP node. For example, with a REST API, post the operation using the `DELETE` method to the MDIP node's endpoint to update DIDs (e.g. `/api/v1/did/`)

Expand All @@ -227,7 +227,7 @@ Example deletion operation:
{
"type": "delete",
"did": "did:mdip:z3v8AuagQPwk6WhAjauVgkFCBJfHJBVBmNAYEhDNMBEXEmWQrHr",
"prev": "9f7f0a67b729248c966bb8945cb80320713aa1de42021c88ca849a4ca029f8d7",
"previd": "z3v8AuaWLbUPpU31mCazznLYy6JtTWmgx9QFsDVveDPDU8Na1sJ",
"signature": {
"signer": "did:mdip:z3v8Auad6fdVkSZE4khWmMwgTjpoMtv82fiT7c56ivNBdjzeMS2",
"created": "2024-02-05T20:00:54.171Z",
Expand All @@ -239,7 +239,7 @@ Example deletion operation:

Upon receiving the operation the MDIP node must:
1. Verify the signature is valid for the controller of the DID.
1. Verify the previous hash.
1. Verify the previd is identical to the latest version's operation CID.
1. Record the operation on the DID specified registry (or forward the request to a trusted node that supports the specified registry).

After revocation is confirmed on the DID's registry, resolving the DID will result in response like this:
Expand Down
10 changes: 5 additions & 5 deletions packages/gatekeeper/src/db-json-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,21 @@ export async function addEvent(did, event) {
}

export async function getEvents(did) {
let events = [];

try {
const db = loadDb();
const suffix = did.split(':').pop();
const updates = db.dids[suffix];

if (updates && updates.length > 0) {
return updates;
}
else {
return [];
events = updates;
}
}
catch {
return [];
}

return JSON.parse(JSON.stringify(events));
}

export async function setEvents(did, events) {
Expand Down
52 changes: 38 additions & 14 deletions packages/gatekeeper/src/gatekeeper-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,12 @@ export async function resetDb() {
verifiedDIDs = {};
}

export async function anchorSeed(seed) {
//console.time('>>ipfs.add');
const cid = await ipfs.add(JSON.parse(canonicalize(seed)));
//console.timeEnd('>>ipfs.add');
export async function generateCID(operation) {
return ipfs.add(JSON.parse(canonicalize(operation)));
}

export async function generateDID(operation) {
const cid = await generateCID(operation);
return `${config.didPrefix}:${cid}`;
}

Expand Down Expand Up @@ -338,7 +340,7 @@ export async function createDID(operation) {
const valid = await verifyCreateOperation(operation);

if (valid) {
const did = await anchorSeed(operation);
const did = await generateDID(operation);
const ops = await exportDID(did);

// Check to see if we already have this DID in the db
Expand Down Expand Up @@ -385,7 +387,7 @@ export async function generateDoc(anchor) {
return {};
}

const did = await anchorSeed(anchor);
const did = await generateDID(anchor);

if (anchor.mdip.type === 'agent') {
// TBD support different key types?
Expand Down Expand Up @@ -448,9 +450,8 @@ export async function resolveDID(did, options = {}) {

const anchor = events[0];
let doc = await generateDoc(anchor.operation);
let mdip = doc?.mdip;

if (atTime && new Date(mdip.created) > new Date(atTime)) {
if (atTime && new Date(doc.mdip.created) > new Date(atTime)) {
// TBD What to return if DID was created after specified time?
}

Expand All @@ -461,6 +462,8 @@ export async function resolveDID(did, options = {}) {
doc.didDocumentMetadata.confirmed = confirmed;

for (const { time, operation, registry, blockchain } of events) {
const opid = await generateCID(operation);

if (operation.type === 'create') {
if (verify) {
const valid = await verifyCreateOperation(operation);
Expand All @@ -469,6 +472,7 @@ export async function resolveDID(did, options = {}) {
throw new InvalidOperationError('signature');
}
}
doc.mdip.opid = opid;
continue;
}

Expand All @@ -480,7 +484,7 @@ export async function resolveDID(did, options = {}) {
break;
}

confirmed = confirmed && mdip.registry === registry;
confirmed = confirmed && doc.mdip.registry === registry;

if (confirm && !confirmed) {
break;
Expand All @@ -492,22 +496,24 @@ export async function resolveDID(did, options = {}) {
if (!valid) {
throw new InvalidOperationError('signature');
}

// TEMP during did:test, operation.previd is optional
if (operation.previd && operation.previd !== doc.mdip.opid) {
throw new InvalidOperationError('previd');
}
}

if (operation.type === 'update') {
// Increment version
version += 1;

// Maintain mdip metadata across versions
mdip = doc.mdip;

// TBD if registry change in operation.doc.didDocumentMetadata.mdip,
// fetch updates from new registry and search for same operation
doc = operation.doc;
doc.didDocumentMetadata.updated = time;
doc.didDocumentMetadata.version = version;
doc.didDocumentMetadata.confirmed = confirmed;
doc.mdip = mdip;
doc.mdip.opid = opid;

if (blockchain) {
doc.mdip.registration = blockchain;
Expand All @@ -522,6 +528,14 @@ export async function resolveDID(did, options = {}) {
doc.didDocumentMetadata.deactivated = true;
doc.didDocumentMetadata.deleted = time;
doc.didDocumentMetadata.confirmed = confirmed;
doc.mdip.opid = opid;

if (blockchain) {
doc.mdip.registration = blockchain;
}
else {
delete doc.mdip.registration;
}
}
else {
if (verify) {
Expand Down Expand Up @@ -643,7 +657,7 @@ async function importEvent(event) {
event.did = event.operation.did;
}
else {
event.did = await anchorSeed(event.operation);
event.did = await generateDID(event.operation);
}
}

Expand Down Expand Up @@ -675,6 +689,16 @@ async function importEvent(event) {
const ok = await verifyOperation(event.operation);

if (ok) {
// TEMP during did:test, operation.previd is optional
if (currentEvents.length > 0 && event.operation.previd) {
const lastEvent = currentEvents[currentEvents.length - 1];
const opid = await generateCID(lastEvent.operation);

if (opid !== event.operation.previd) {
throw new InvalidOperationError('previd');
}
}

await db.addEvent(did, event);
return true;
}
Expand Down
24 changes: 12 additions & 12 deletions packages/keymaster/src/keymaster-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,13 +317,13 @@ async function updateSeedBank(doc) {
const keypair = await hdKeyPair();
const did = doc.didDocument.id;
const current = await gatekeeper.resolveDID(did);
const prev = cipher.hashJSON(current);
const previd = current.mdip.opid;

const operation = {
type: "update",
did: did,
doc: doc,
prev: prev,
did,
previd,
doc,
};

const msgHash = cipher.hashJSON(operation);
Expand Down Expand Up @@ -655,13 +655,13 @@ export async function verifySignature(obj) {
export async function updateDID(doc) {
const did = doc.didDocument.id;
const current = await resolveDID(did);
const prev = cipher.hashJSON(current);
const previd = current.mdip.opid;

const operation = {
type: "update",
did: did,
doc: doc,
prev: prev,
did,
previd,
doc,
};

const controller = current.didDocument.controller || current.didDocument.id;
Expand All @@ -671,12 +671,12 @@ export async function updateDID(doc) {

export async function revokeDID(did) {
const current = await resolveDID(did);
const prev = cipher.hashJSON(current);
const previd = current.mdip.opid;

const operation = {
type: "delete",
did: did,
prev: prev,
did,
previd,
};

const controller = current.didDocument.controller || current.didDocument.id;
Expand Down Expand Up @@ -1237,7 +1237,7 @@ async function findMatchingCredential(credential) {
}

export async function createResponse(challengeDID, options = {}) {
let { retries = 0, delay = 1000} = options;
let { retries = 0, delay = 1000 } = options;

if (!options.registry) {
options.registry = ephemeralRegistry;
Expand Down
Loading

0 comments on commit 9159d7a

Please sign in to comment.