Skip to content

Commit

Permalink
Merge pull request #19 from aviarytech/version-0.4
Browse files Browse the repository at this point in the history
  • Loading branch information
brianorwhatever authored Oct 22, 2024
2 parents 33ce57b + a3e56ed commit 67b6b2c
Show file tree
Hide file tree
Showing 19 changed files with 431 additions and 378 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"
}
}
}
36 changes: 17 additions & 19 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,11 +24,10 @@ 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;

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

if (proof.verificationMethod.startsWith('did:key:')) {
Expand All @@ -47,33 +46,32 @@ export const documentStateIsValid = async (doc: any, proofs: any[], updateKeys:
throw new Error(`Unknown proof type ${proof.type}`);
}
if (proof.proofPurpose !== 'authentication') {
throw new Error(`Unknown proof purpose] ${proof.proofPurpose}`);
throw new Error(`Unknown proof purpose ${proof.proofPurpose}`);
}
if (proof.cryptosuite !== 'eddsa-jcs-2022') {
throw new Error(`Unknown cryptosuite ${proof.cryptosuite}`);
}

const vm = await resolveVM(proof.verificationMethod);
if (!vm) {
throw new Error(`Verification Method ${proof.verificationMethod} not found`);
}

const publicKey = base58btc.decode(vm.publicKeyMultibase!);
if (publicKey[0] !== 237 || publicKey[1] !== 1) {
throw new Error(`multiKey doesn't include ed25519 header (0xed01)`)
if (publicKey[0] !== 0xed || publicKey[1] !== 0x01) {
throw new Error(`multiKey doesn't include ed25519 header (0xed01)`);
}

const {proofValue, ...restProof} = proof;
const sig = base58btc.decode(proofValue);
const dataHash = createHash('sha256').update(canonicalize(doc)).digest();
const signature = base58btc.decode(proofValue);
const dataHash = createHash('sha256').update(canonicalize(rest)).digest();
const proofHash = createHash('sha256').update(canonicalize(restProof)).digest();
const input = Buffer.concat([dataHash, proofHash]);
const verified = await ed.verifyAsync(
bytesToHex(sig),
bytesToHex(input),
bytesToHex(publicKey.slice(2))
);
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) {
return false;
throw new Error(`Proof ${i} failed verification`);
}
i++;
}
return true;
}
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
32 changes: 7 additions & 25 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createDID, resolveDID, updateDID, deactivateDID } from './method';
import { createDID, resolveDID, updateDID, deactivateDID, resolveDIDFromLog } from './method';
import { createSigner, generateEd25519VerificationMethod } from './cryptography';
import { getFileUrl, readLogFromDisk, writeLogToDisk, writeVerificationMethodToEnv } from './utils';
import { fetchLogFromIdentifier, getFileUrl, readLogFromDisk, writeLogToDisk, writeVerificationMethodToEnv } from './utils';

const usage = `
Usage: bun run cli [command] [options]
Expand Down Expand Up @@ -78,7 +78,7 @@ async function handleCreate(args: string[]) {
process.exit(1);
}

const authKey = await generateEd25519VerificationMethod('authentication');
const authKey = await generateEd25519VerificationMethod();
const { did, doc, meta, log } = await createDID({
domain,
signer: createSigner(authKey),
Expand Down Expand Up @@ -114,7 +114,7 @@ async function handleResolve(args: string[]) {

try {
const log = await fetchLogFromIdentifier(didIdentifier);
const { did, doc, meta } = await resolveDID(log);
const { did, doc, meta } = await resolveDIDFromLog(log);

console.log('Resolved DID:', did);
console.log('DID Document:', JSON.stringify(doc, null, 2));
Expand All @@ -125,24 +125,6 @@ async function handleResolve(args: string[]) {
}
}

async function fetchLogFromIdentifier(identifier: string): Promise<DIDLog> {
try {
const url = getFileUrl(identifier);
console.log(url, identifier)
const response = await fetch(url);

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const text = await response.text();
return text.trim().split('\n').map(line => JSON.parse(line));
} catch (error) {
console.error('Error fetching DID log:', error);
throw error;
}
}

async function handleUpdate(args: string[]) {
const options = parseOptions(args);
const logFile = options['log'] as string;
Expand All @@ -160,12 +142,12 @@ async function handleUpdate(args: string[]) {
}

const log = readLogFromDisk(logFile);
const authKey = await generateEd25519VerificationMethod('authentication');
const authKey = await generateEd25519VerificationMethod();

const verificationMethods: VerificationMethod[] = [
authKey,
...(addVm?.map(type => ({
type,
type: "Multikey",
publicKeyMultibase: authKey.publicKeyMultibase,
} as VerificationMethod)) || [])
];
Expand Down Expand Up @@ -203,7 +185,7 @@ async function handleDeactivate(args: string[]) {
}

const log = readLogFromDisk(logFile);
const authKey = await generateEd25519VerificationMethod('authentication');
const authKey = await generateEd25519VerificationMethod();
const { did, doc, meta, log: deactivatedLog } = await deactivateDID({
log,
signer: createSigner(authKey),
Expand Down
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const PLACEHOLDER = "{SCID}";
export const METHOD = "tdw";
export const VERSION = "0.3";
export const VERSION = "0.4";
export const PROTOCOL = `did:${METHOD}:${VERSION}`;
export const BASE_CONTEXT = [
"https://www.w3.org/ns/did/v1",
Expand Down
34 changes: 17 additions & 17 deletions src/cryptography.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,51 @@
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([dataHash, proofHash]);
const secretKey = base58btc.decode(vm.secretKeyMultibase!);
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'));

const output = await ed.signAsync(bytesToHex(input), bytesToHex(secretKey.slice(2, 34)));

proof.proofValue = base58btc.encode(output);
proof.proofValue = base58btc.encode(signature);
return {...doc, proof};
} catch (e: any) {
console.error(e)
throw new Error(`Document signing failure: ${e.details}`)
throw new Error(`Document signing failure: ${e.message || e}`)
}
}
}

export const generateEd25519VerificationMethod = async (purpose: 'authentication' | 'assertionMethod' | 'capabilityInvocation' | 'capabilityDelegation'): Promise<VerificationMethod> => {
export const generateEd25519VerificationMethod = async (): Promise<VerificationMethod> => {
const privKey = ed.utils.randomPrivateKey();
const pubKey = await ed.getPublicKeyAsync(privKey);
const publicKeyMultibase = base58btc.encode(Buffer.concat([new Uint8Array([0xed, 0x01]), pubKey]));
const secretKeyMultibase = base58btc.encode(Buffer.concat([new Uint8Array([0x80, 0x26]), privKey]));

return {
type: purpose,
type: "Multikey",
publicKeyMultibase,
secretKeyMultibase
secretKeyMultibase,
purpose: 'authentication'
};
}

export const generateX25519VerificationMethod = async (purpose: 'keyAgreement'): Promise<VerificationMethod> => {
export const generateX25519VerificationMethod = async (): Promise<VerificationMethod> => {
const privKey = ed.utils.randomPrivateKey();
const pubKey = await ed.getPublicKeyAsync(privKey);
const x25519PubKey = edwardsToMontgomeryPub(pubKey);
Expand All @@ -55,8 +54,9 @@ export const generateX25519VerificationMethod = async (purpose: 'keyAgreement'):
const secretKeyMultibase = base58btc.encode(Buffer.concat([new Uint8Array([0x82, 0x26]), x25519PrivKey]));

return {
type: purpose,
type: "Multikey",
publicKeyMultibase,
secretKeyMultibase
secretKeyMultibase,
purpose: 'keyAgreement'
}
}
56 changes: 29 additions & 27 deletions src/interfaces.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface DIDResolutionMeta {
}

interface DIDDoc {
"@context"?: string | string[] | object | object[];
id?: string;
controller?: string | string[];
alsoKnownAs?: string[];
Expand All @@ -26,11 +27,12 @@ interface DIDDoc {
service?: ServiceEndpoint[];
}

interface DIDOperation {
op: string;
path: string;
value: any;
}
// Remove the DIDOperation interface as it's no longer needed
// interface DIDOperation {
// op: string;
// path: string;
// value: any;
// }

interface DataIntegrityProof {
id?: string;
Expand All @@ -40,26 +42,25 @@ interface DataIntegrityProof {
created: string;
proofValue: string;
proofPurpose: string;
challenge?: string;
}

type DIDLogEntry = [
versionId: string,
timestamp: string,
params: {
method?: string,
scid?: string,
updateKeys?: string[],
prerotation?: boolean,
nextKeyHashes?: string[],
portable?: boolean,
witnesses?: string[],
witnessThreshold?: number,
deactivated?: boolean
},
data: {value: any} | {patch: DIDOperation[]},
proof?: DataIntegrityProof[]
];
interface DIDLogEntry {
versionId: string;
versionTime: string;
parameters: {
method?: string;
scid?: string;
updateKeys?: string[];
prerotation?: boolean;
nextKeyHashes?: string[];
portable?: boolean;
witnesses?: string[];
witnessThreshold?: number;
deactivated?: boolean;
};
state: DIDDoc; // Change this to specifically hold the DID document
proof?: DataIntegrityProof[];
}

type DIDLog = DIDLogEntry[];

Expand All @@ -71,7 +72,8 @@ interface ServiceEndpoint {

interface VerificationMethod {
id?: string;
type: 'authentication' | 'assertionMethod' | 'keyAgreement' | 'capabilityInvocation' | 'capabilityDelegation';
type: 'Multikey';
purpose?: 'authentication' | 'assertionMethod' | 'keyAgreement' | 'capabilityInvocation' | 'capabilityDelegation';
controller?: string;
publicKeyJWK?: any;
publicKeyMultibase?: string;
Expand All @@ -82,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 @@ -102,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 @@ -120,5 +122,5 @@ interface UpdateDIDInterface {

interface DeactivateDIDInterface {
log: DIDLog;
signer: (doc: any, challenge: string) => Promise<{proof: any}>;
signer: (doc: any) => Promise<{proof: any}>;
}
Loading

0 comments on commit 67b6b2c

Please sign in to comment.