Skip to content

Commit

Permalink
Fix interop issues
Browse files Browse the repository at this point in the history
  • Loading branch information
brianorwhatever committed Oct 16, 2024
1 parent 072443f commit a3e56ed
Show file tree
Hide file tree
Showing 12 changed files with 56 additions and 66 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@
"@noble/curves": "^1.4.2",
"@noble/ed25519": "^2.1.0",
"elysia": "^0.8.17",
"fast-json-patch": "^3.1.1",
"json-canonicalize": "^1.0.6",
"multiformats": "^13.1.0",
"nanoid": "^5.0.6"
}
}
}
12 changes: 5 additions & 7 deletions src/assertions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as ed from '@noble/ed25519';
import { base58btc } from "multiformats/bases/base58";
import { bytesToHex, createSCID, deriveHash, resolveVM } from "./utils";
import { bytesToHex, createSCID, deriveHash, deriveNextKeyHash, resolveVM } from "./utils";
import { canonicalize } from 'json-canonicalize';
import { createHash } from 'node:crypto';

Expand All @@ -24,9 +24,9 @@ const isWitnessAuthorized = (verificationMethod: string, witnesses: string[]): b
return false;
};

export const documentStateIsValid = async (doc: any, proofs: any[], updateKeys: string[], witnesses: string[] = []) => {
export const documentStateIsValid = async (doc: any, updateKeys: string[], witnesses: string[] = []) => {
if (process.env.IGNORE_ASSERTION_DOCUMENT_STATE_IS_VALID) return true;

const {proof: proofs, ...rest} = doc;
for (let i = 0; i < proofs.length; i++) {
const proof = proofs[i];

Expand Down Expand Up @@ -64,13 +64,11 @@ export const documentStateIsValid = async (doc: any, proofs: any[], updateKeys:

const {proofValue, ...restProof} = proof;
const signature = base58btc.decode(proofValue);

const dataHash = createHash('sha256').update(canonicalize(doc)).digest();
const dataHash = createHash('sha256').update(canonicalize(rest)).digest();
const proofHash = createHash('sha256').update(canonicalize(restProof)).digest();
const input = Buffer.concat([proofHash, dataHash]);

const verified = await ed.verifyAsync(Buffer.from(signature).toString('hex'), Buffer.from(input).toString('hex'), Buffer.from(publicKey.slice(2)).toString('hex'));

if (!verified) {
throw new Error(`Proof ${i} failed verification`);
}
Expand All @@ -90,7 +88,7 @@ export const newKeysAreValid = (updateKeys: string[], previousNextKeyHashes: str
}
if(previousPrerotation) {
const inNextKeyHashes = updateKeys.reduce((result, key) => {
const hashedKey = deriveHash(key);
const hashedKey = deriveNextKeyHash(key);
return result && previousNextKeyHashes.includes(hashedKey);
}, true);
if (!inNextKeyHashes) {
Expand Down
10 changes: 4 additions & 6 deletions src/cryptography.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
import * as ed from '@noble/ed25519';
import { edwardsToMontgomeryPub, edwardsToMontgomeryPriv } from '@noble/curves/ed25519';

import { bytesToHex, createDate } from "./utils";
import { createDate } from "./utils";
import { base58btc } from "multiformats/bases/base58"
import { canonicalize } from 'json-canonicalize';
import { createHash } from 'node:crypto';

export const createSigner = (vm: VerificationMethod, useStatic: boolean = true) => {
return async (doc: any, challenge: string) => {
return async (doc: any) => {
try {
const proof: any = {
type: 'DataIntegrityProof',
cryptosuite: 'eddsa-jcs-2022',
verificationMethod: useStatic ? `did:key:${vm.publicKeyMultibase}` : vm.id,
verificationMethod: useStatic ? `did:key:${vm.publicKeyMultibase}#${vm.publicKeyMultibase}` : vm.id,
created: createDate(),
proofPurpose: 'authentication',
challenge
proofPurpose: 'authentication'
}
const dataHash = createHash('sha256').update(canonicalize(doc)).digest();
const proofHash = createHash('sha256').update(canonicalize(proof)).digest();
const input = Buffer.concat([proofHash, dataHash]);
const secretKey = base58btc.decode(vm.secretKeyMultibase!).slice(2);

const signature = await ed.signAsync(Buffer.from(input).toString('hex'), Buffer.from(secretKey).toString('hex'));

proof.proofValue = base58btc.encode(signature);
Expand Down
7 changes: 3 additions & 4 deletions src/interfaces.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ interface DataIntegrityProof {
created: string;
proofValue: string;
proofPurpose: string;
challenge?: string;
}

interface DIDLogEntry {
Expand Down Expand Up @@ -85,7 +84,7 @@ interface VerificationMethod {
interface CreateDIDInterface {
domain: string;
updateKeys: string[];
signer: (doc: any, challenge: string) => Promise<{proof: any}>;
signer: (doc: any) => Promise<{proof: any}>;
controller?: string;
context?: string | string[];
verificationMethods?: VerificationMethod[];
Expand All @@ -105,7 +104,7 @@ interface SignDIDDocInterface {

interface UpdateDIDInterface {
log: DIDLog;
signer: (doc: any, challenge: string) => Promise<{proof: any}>;
signer: (doc: any) => Promise<{proof: any}>;
updateKeys?: string[];
context?: string[];
controller?: string[];
Expand All @@ -123,5 +122,5 @@ interface UpdateDIDInterface {

interface DeactivateDIDInterface {
log: DIDLog;
signer: (doc: any, challenge: string) => Promise<{proof: any}>;
signer: (doc: any) => Promise<{proof: any}>;
}
61 changes: 29 additions & 32 deletions src/method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,34 +37,32 @@ export const createDID = async (options: CreateDIDInterface): Promise<{did: stri
};
const initialLogEntryHash = deriveHash(initialLogEntry);
params.scid = await createSCID(initialLogEntryHash);
doc = JSON.parse(JSON.stringify(doc).replaceAll(PLACEHOLDER, params.scid));

initialLogEntry.versionId = `1-${initialLogEntryHash}`;
initialLogEntry.parameters = JSON.parse(JSON.stringify(initialLogEntry.parameters).replaceAll(PLACEHOLDER, params.scid));
initialLogEntry.state = doc;

const signedDoc = await options.signer(doc, initialLogEntry.versionId);
const prelimEntry = JSON.parse(JSON.stringify(initialLogEntry).replaceAll(PLACEHOLDER, params.scid));
const logEntryHash2 = deriveHash(prelimEntry);
prelimEntry.versionId = `1-${logEntryHash2}`;
const signedDoc = await options.signer(prelimEntry);
let allProofs = [signedDoc.proof];
initialLogEntry.proof = allProofs;
prelimEntry.proof = allProofs;

if (options.witnesses && options.witnesses.length > 0) {
const witnessProofs = await collectWitnessProofs(options.witnesses, [initialLogEntry]);
const witnessProofs = await collectWitnessProofs(options.witnesses, [prelimEntry]);
if (witnessProofs.length > 0) {
allProofs = [...allProofs, ...witnessProofs];
initialLogEntry.proof = allProofs;
prelimEntry.proof = allProofs;
}
}
return {
did: doc.id!,
doc,
did: prelimEntry.state.id!,
doc: prelimEntry.state,
meta: {
versionId: initialLogEntry.versionId,
created: initialLogEntry.versionTime,
updated: initialLogEntry.versionTime,
versionId: prelimEntry.versionId,
created: prelimEntry.versionTime,
updated: prelimEntry.versionTime,
...params
},
log: [
initialLogEntry
prelimEntry
]
}
}
Expand Down Expand Up @@ -138,20 +136,21 @@ export const resolveDIDFromLog = async (log: DIDLog, options: {
meta.witnesses = parameters.witnesses || meta.witnesses;
meta.witnessThreshold = parameters.witnessThreshold || meta.witnessThreshold || meta.witnesses.length;
nextKeyHashes = parameters.nextKeyHashes ?? [];
newKeysAreValid(meta.updateKeys, [], nextKeyHashes, false, meta.prerotation === true);
const logEntryHash = deriveHash(
{
versionId: PLACEHOLDER,
versionTime: meta.created,
parameters: JSON.parse(JSON.stringify(parameters).replaceAll(meta.scid, PLACEHOLDER)),
state: JSON.parse(JSON.stringify(newDoc).replaceAll(meta.scid, PLACEHOLDER))
}
);
newKeysAreValid(meta.updateKeys, [], nextKeyHashes, false, meta.prerotation === true);
const logEntry = {
versionId: PLACEHOLDER,
versionTime: meta.created,
parameters: JSON.parse(JSON.stringify(parameters).replaceAll(meta.scid, PLACEHOLDER)),
state: JSON.parse(JSON.stringify(newDoc).replaceAll(meta.scid, PLACEHOLDER))
};
const logEntryHash = deriveHash(logEntry);
meta.previousLogEntryHash = logEntryHash;
if (!await scidIsFromHash(meta.scid, logEntryHash)) {
throw new Error(`SCID '${meta.scid}' not derived from logEntryHash '${logEntryHash}'`);
}
const verified = await documentStateIsValid(newDoc, proof, meta.updateKeys, meta.witnesses);
const prelimEntry = JSON.parse(JSON.stringify(logEntry).replaceAll(PLACEHOLDER, meta.scid));
const logEntryHash2 = deriveHash(prelimEntry);
const verified = await documentStateIsValid({...prelimEntry, versionId: `1-${logEntryHash2}`, proof}, meta.updateKeys, meta.witnesses);
if (!verified) {
throw new Error(`version ${meta.versionId} failed verification of the proof.`)
}
Expand All @@ -170,7 +169,7 @@ export const resolveDIDFromLog = async (log: DIDLog, options: {
if (!hashChainValid(`${i+1}-${entryHash}`, entry.versionId)) {
throw new Error(`Hash chain broken at '${meta.versionId}'`);
}
const verified = await documentStateIsValid(newDoc, proof, meta.updateKeys, meta.witnesses);
const verified = await documentStateIsValid(entry, meta.updateKeys, meta.witnesses);
if (!verified) {
throw new Error(`version ${meta.versionId} failed verification of the proof.`)
}
Expand Down Expand Up @@ -255,12 +254,11 @@ export const updateDID = async (options: UpdateDIDInterface): Promise<{did: stri
versionId: meta.versionId,
versionTime: meta.updated,
parameters: params,
state: clone(newDoc),
proof: []
state: clone(newDoc)
};
const logEntryHash = deriveHash(logEntry);
logEntry.versionId = `${nextVersion}-${logEntryHash}`;
const signedDoc = await options.signer(newDoc, logEntry.versionId);
const signedDoc = await options.signer(logEntry);
logEntry.proof = [signedDoc.proof];
const newMeta = {
...meta,
Expand Down Expand Up @@ -306,12 +304,11 @@ export const deactivateDID = async (options: DeactivateDIDInterface): Promise<{d
versionId: meta.versionId,
versionTime: meta.updated,
parameters: {deactivated: true},
state: clone(newDoc),
proof: []
state: clone(newDoc)
};
const logEntryHash = deriveHash(logEntry);
logEntry.versionId = `${nextVersion}-${logEntryHash}`;
const signedDoc = await options.signer(newDoc, logEntry.versionId);
const signedDoc = await options.signer(logEntry);
logEntry.proof = [signedDoc.proof];
return {
did,
Expand Down
1 change: 0 additions & 1 deletion src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const app = new Elysia()
.get('/health', 'ok')
.get('/.well-known/did.jsonl', () => getLogFileForBase())
.post('/witness', async ({body}) => {
console.log('log', (body as any).log);
const result = await createWitnessProof((body as any).log);
console.log(`Signed with VM`, (result as any).proof.verificationMethod)
if ('error' in result) {
Expand Down
2 changes: 1 addition & 1 deletion src/routes/.well-known/did.jsonl
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"versionId":"1-QmeqjbFXeSXyhEAN8yFsKfySzTWUbHbiAGj8E7yFPJYkiY","versionTime":"2024-10-16T03:37:34Z","parameters":{"method":"did:tdw:0.4","scid":"QmeqjbFXeSXyhEAN8yFsKfySzTWUbHbiAGj8E7yFPJYkiY","updateKeys":["z6MkseDudoStndLcxE8nBfBQ1MqDKTk34hYyzkGJ96pFpv34"],"portable":true,"prerotation":false,"nextKeyHashes":[],"witnesses":[],"witnessThreshold":0,"deactivated":false},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:tdw:QmeqjbFXeSXyhEAN8yFsKfySzTWUbHbiAGj8E7yFPJYkiY:localhost%3A8000","controller":"did:tdw:QmeqjbFXeSXyhEAN8yFsKfySzTWUbHbiAGj8E7yFPJYkiY:localhost%3A8000","authentication":["did:tdw:QmeqjbFXeSXyhEAN8yFsKfySzTWUbHbiAGj8E7yFPJYkiY:localhost%3A8000#96pFpv34"],"verificationMethod":[{"id":"did:tdw:QmeqjbFXeSXyhEAN8yFsKfySzTWUbHbiAGj8E7yFPJYkiY:localhost%3A8000#96pFpv34","controller":"did:tdw:QmeqjbFXeSXyhEAN8yFsKfySzTWUbHbiAGj8E7yFPJYkiY:localhost%3A8000","type":"Multikey","publicKeyMultibase":"z6MkseDudoStndLcxE8nBfBQ1MqDKTk34hYyzkGJ96pFpv34"}]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6MkseDudoStndLcxE8nBfBQ1MqDKTk34hYyzkGJ96pFpv34","created":"2024-10-16T03:37:34Z","proofPurpose":"authentication","challenge":"1-QmeqjbFXeSXyhEAN8yFsKfySzTWUbHbiAGj8E7yFPJYkiY","proofValue":"z5pVnvpAuUSJ63tec1AJWuGAMJgBUPV5xv7a5FTj4tx9AxP7ecncgLG1LSPCEesoXjwnwTxv8eTke1BhTtvepTAkh"}]}
{"versionId":"1-QmVo8HTg988dwKhyBooHcCGHUpk23wrND9n8KAY97KQA3B","versionTime":"2024-10-16T19:49:10Z","parameters":{"method":"did:tdw:0.4","scid":"QmXToKAqZp3M6WP7HPDmEsjweTjReuAC1C8mMJAG8QNFSj","updateKeys":["z6Mkfgfqi4EWFyryzTemtL3u9v3Ueqoo2oLJbt227vs27kdp"],"portable":false,"prerotation":false,"nextKeyHashes":[],"witnesses":[],"witnessThreshold":0,"deactivated":false},"state":{"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/multikey/v1"],"id":"did:tdw:QmXToKAqZp3M6WP7HPDmEsjweTjReuAC1C8mMJAG8QNFSj:localhost%3A8000","controller":"did:tdw:QmXToKAqZp3M6WP7HPDmEsjweTjReuAC1C8mMJAG8QNFSj:localhost%3A8000","authentication":["did:tdw:QmXToKAqZp3M6WP7HPDmEsjweTjReuAC1C8mMJAG8QNFSj:localhost%3A8000#7vs27kdp"],"verificationMethod":[{"id":"did:tdw:QmXToKAqZp3M6WP7HPDmEsjweTjReuAC1C8mMJAG8QNFSj:localhost%3A8000#7vs27kdp","controller":"did:tdw:QmXToKAqZp3M6WP7HPDmEsjweTjReuAC1C8mMJAG8QNFSj:localhost%3A8000","type":"Multikey","publicKeyMultibase":"z6Mkfgfqi4EWFyryzTemtL3u9v3Ueqoo2oLJbt227vs27kdp"}]},"proof":[{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","verificationMethod":"did:key:z6Mkfgfqi4EWFyryzTemtL3u9v3Ueqoo2oLJbt227vs27kdp#z6Mkfgfqi4EWFyryzTemtL3u9v3Ueqoo2oLJbt227vs27kdp","created":"2024-10-16T19:49:10Z","proofPurpose":"authentication","proofValue":"z2dWto83Fc8iSsmpp8qKkbdJBBu82GmJGx4qrF3KjVRKtvQYEbrHKASo1JDbxXbTuoN9PnqZbY8uvDv758csXGiM6"}]}
5 changes: 5 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ export const deriveHash = (input: any): string => {
return base58btc.encode((sha256.digest(encoder.encode(data)) as any).bytes);
}

export const deriveNextKeyHash = (input: string): string => {
const encoder = new TextEncoder();
return base58btc.encode((sha256.digest(encoder.encode(input)) as any).bytes);
}

export const createDIDDoc = async (options: CreateDIDInterface): Promise<{doc: DIDDoc}> => {
const {controller} = options;
const {all} = normalizeVMs(options.verificationMethods, controller);
Expand Down
7 changes: 2 additions & 5 deletions src/witness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,8 @@ export async function createWitnessProof(log: DIDLog): Promise<{ proof: any } |
publicKeyMultibase: fullVM.publicKeyMultibase,
secretKeyMultibase: fullVM.secretKeyMultibase
}, false);
// Sign the log entry
const signedDoc = await signer(
state,
versionId
);
const {proof, ...entry} = logEntry;
const signedDoc = await signer(entry);

return {
proof: signedDoc.proof
Expand Down
8 changes: 3 additions & 5 deletions test/features.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as jsonpatch from 'fast-json-patch/index.mjs';
import { beforeAll, expect, test} from "bun:test";
import { createDID, resolveDID, resolveDIDFromLog, updateDID } from "../src/method";
import { createDID, resolveDIDFromLog, updateDID } from "../src/method";
import { mock } from "bun-bagel";
import { createSigner, generateEd25519VerificationMethod } from "../src/cryptography";
import { deriveHash, createDate, clone } from "../src/utils";
Expand Down Expand Up @@ -78,6 +77,7 @@ beforeAll(async () => {
});

test("Resolve DID at time (first)", async () => {
console.error('log', log)
const resolved = await resolveDIDFromLog(log, {versionTime: new Date('2021-01-15T08:32:55Z')});
expect(resolved.meta.versionId.split('-')[0]).toBe('1');
});
Expand Down Expand Up @@ -157,7 +157,6 @@ test("Require `nextKeyHashes` if prerotation enabled in Read (when enabled in Cr
verificationMethod: "did:key:z6Mkr2D4ixckmQx8tAVvXEhMuaMhzahxe61qJt7G9vYyiXiJ",
created: "2024-06-06T08:23:06Z",
proofPurpose: "authentication",
challenge: "yfdr7xm1xf4e8eryw97r3e2yvey4gd13a93me7c6q3r7gfam3bh0",
proofValue: "z4wWcu5WXftuvLtZy2jLHiyB8WJoWh8naNu4VFeGdfoBUbFie6mkQYAT2fyLXdbXBpPr7DWdgGatT6NZj7GJGmoBR",
}
]
Expand Down Expand Up @@ -408,15 +407,14 @@ test("updateDID should not allow moving a non-portable DID", async () => {
updateKeys: [authKey1.publicKeyMultibase!],
domain: newDomain,
updated: newTimestamp,
signer: async (doc: any, challenge: string) => ({
signer: async (doc: any) => ({
...doc,
proof: {
type: "DataIntegrityProof",
cryptosuite: "eddsa-jcs-2022",
verificationMethod: `did:key:${authKey1.publicKeyMultibase}`,
created: newTimestamp,
proofPurpose: "authentication",
challenge,
proofValue: "z5KDJTw1C2fRwTsxVzP1GXUJgapWeWxvd5VrwLucY4Pr1fwaMHDQsQwH5cPDdwSNUxiR7LHMUMpvhchDABUW8b2wB"
}
})
Expand Down
Loading

0 comments on commit a3e56ed

Please sign in to comment.