From 5b47973f77baec0b110701909987a7c81a529476 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Laptop)" Date: Thu, 17 Oct 2024 09:23:41 -0600 Subject: [PATCH 1/7] feat: Generate did:peer:4 dids Signed-off-by: Colton Wolkins (Laptop) --- src/lib/didcomm.ts | 113 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 6 deletions(-) diff --git a/src/lib/didcomm.ts b/src/lib/didcomm.ts index df84ed6..978acec 100644 --- a/src/lib/didcomm.ts +++ b/src/lib/didcomm.ts @@ -16,8 +16,11 @@ import { Service, } from "didcomm" import DIDPeer from "./peer2" +import * as DIDPeer4 from "./peer4" import { v4 as uuidv4 } from "uuid" import logger from "./logger" +import * as multibase from "multibase" +import * as multicodec from "multicodec" export type DID = string @@ -51,39 +54,99 @@ function ed25519ToSecret( return secretVer } -export function generateDidForMediator() { +export async function generateDidForMediator() { const key = ed25519.utils.randomPrivateKey() const enckeyPriv = edwardsToMontgomeryPriv(key) const verkey = ed25519.getPublicKey(key) const enckey = edwardsToMontgomeryPub(verkey) const service = { type: "DIDCommMessaging", + id: "#service", serviceEndpoint: { uri: "didcomm:transport/queue", accept: ["didcomm/v2"], routingKeys: [] as string[], }, } - const did = DIDPeer.generate([verkey], [enckey], service) + //const did = DIDPeer.generate([verkey], [enckey], service) // did:peer:2 + const doc = { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/multikey/v1" + ], + "verificationMethod": [ + { + "id": "#key-1", + "type": "Multikey", + "publicKeyMultibase": DIDPeer.keyToMultibase(verkey, "ed25519-pub") + }, + { + "id": "#key-2", + "type": "Multikey", + "publicKeyMultibase": DIDPeer.keyToMultibase(enckey, "x25519-pub") + } + ], + "authentication": [ + "#key-1" + ], + "capabilityDelegation": [ + "#key-1" + ], + "service": [service], + "keyAgreement": [ + "#key-2" + ] + } + const did = await DIDPeer4.encode(doc) const secretVer = ed25519ToSecret(did, key, verkey) const secretEnc = x25519ToSecret(did, enckeyPriv, enckey) return { did, secrets: [secretVer, secretEnc] } } -export function generateDid(routingDid: DID) { +export async function generateDid(routingDid: DID) { const key = ed25519.utils.randomPrivateKey() const enckeyPriv = edwardsToMontgomeryPriv(key) const verkey = ed25519.getPublicKey(key) const enckey = edwardsToMontgomeryPub(verkey) const service = { type: "DIDCommMessaging", + id: "#service", serviceEndpoint: { uri: routingDid, accept: ["didcomm/v2"], }, } - const did = DIDPeer.generate([verkey], [enckey], service) + //const did = DIDPeer.generate([verkey], [enckey], service) // did:peer:2 + const doc = { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/multikey/v1" + ], + "verificationMethod": [ + { + "id": "#key-1", + "type": "Multikey", + "publicKeyMultibase": DIDPeer.keyToMultibase(verkey, "ed25519-pub") + }, + { + "id": "#key-2", + "type": "Multikey", + "publicKeyMultibase": DIDPeer.keyToMultibase(enckey, "x25519-pub") + } + ], + "authentication": [ + "#key-1" + ], + "capabilityDelegation": [ + "#key-1" + ], + "service": [service], + "keyAgreement": [ + "#key-2" + ] + } + const did = await DIDPeer4.encode(doc) const secretVer = ed25519ToSecret(did, key, verkey) const secretEnc = x25519ToSecret(did, enckeyPriv, enckey) @@ -103,6 +166,43 @@ export class DIDPeerResolver implements DIDResolver { } } +export class DIDPeer4Resolver implements DIDResolver { + async resolve(did: DID): Promise { + const raw_doc = await DIDPeer4.resolve(did) + const fix_vms = async (vms: Array>) => { + let methods = vms.map((k: Record) => { + let new_method = { + id: `${did}${k.id}`, + type: k.type, + controller: k.controller, + publicKeyMultibase: k.publicKeyMultibase + } + if(new_method.type == "Multikey") { + const key = multibase.decode(k.publicKeyMultibase) + const codec = multicodec.getNameFromData(key) + switch(codec) { + case "x25519-pub": + new_method.type = "X25519KeyAgreementKey2020" + break + case "ed25519-pub": + new_method.type = "Ed25519VerificationKey2020" + break + } + } + return new_method + }) + return methods + }; + return { + id: raw_doc.id, + verificationMethod: await fix_vms(raw_doc.verificationMethod), + authentication: raw_doc.authentication.map((kid: string) => `${raw_doc.id}${kid}`), + keyAgreement: raw_doc.keyAgreement.map((kid: string) => `${raw_doc.id}${kid}`), + service: raw_doc.service, + } + } +} + var did_web_cache: Record = {}; export class DIDWebResolver implements DIDResolver { @@ -171,6 +271,7 @@ export class PrefixResolver implements DIDResolver { constructor() { this.resolver_map = { "did:peer:2": new DIDPeerResolver() as DIDResolver, + "did:peer:4": new DIDPeer4Resolver() as DIDResolver, "did:web:": new DIDWebResolver() as DIDResolver, } } @@ -304,13 +405,13 @@ export class DIDComm { } async generateDidForMediator(): Promise { - const { did, secrets } = generateDidForMediator() + const { did, secrets } = await generateDidForMediator() secrets.forEach(secret => this.secretsResolver.store_secret(secret)) return did } async generateDid(routingDid: DID): Promise { - const { did, secrets } = generateDid(routingDid) + const { did, secrets } = await generateDid(routingDid) secrets.forEach(secret => this.secretsResolver.store_secret(secret)) return did } From 2a8999fd23a10e3191c5b4feb9820cefdeadc607 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Laptop)" Date: Thu, 17 Oct 2024 09:24:25 -0600 Subject: [PATCH 2/7] feat: support empty messages Signed-off-by: Colton Wolkins (Laptop) --- src/pages/profile/messaging.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pages/profile/messaging.ts b/src/pages/profile/messaging.ts index b65acb7..299bf33 100644 --- a/src/pages/profile/messaging.ts +++ b/src/pages/profile/messaging.ts @@ -423,6 +423,13 @@ class MessageHistoryComponent inspectable: false, hideBody: true, }) + case "https://didcomm.org/empty/1.0/empty": + return m(MessageCard, { + header: "Empty", + message, + inspectable: false, + hideBody: true, + }) default: return m( MessageCard, From e101094d4438891563a650a778b2ce82dbd95a5e Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Laptop)" Date: Thu, 17 Oct 2024 09:28:07 -0600 Subject: [PATCH 3/7] fix: Add missing peer4 built library This was built from @dbluhm's did:peer:4 ts library https://github.com/dbluhm/did-peer-4-ts/ Signed-off-by: Colton Wolkins (Laptop) --- src/lib/peer4/index.js | 378 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 src/lib/peer4/index.js diff --git a/src/lib/peer4/index.js b/src/lib/peer4/index.js new file mode 100644 index 0000000..d02f40b --- /dev/null +++ b/src/lib/peer4/index.js @@ -0,0 +1,378 @@ +var __create = Object.create; +var __getProtoOf = Object.getPrototypeOf; +var __defProp = Object.defineProperty; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __toESM = (mod, isNodeMode, target) => { + target = mod != null ? __create(__getProtoOf(mod)) : {}; + const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target; + for (let key of __getOwnPropNames(mod)) + if (!__hasOwnProp.call(to, key)) + __defProp(to, key, { + get: () => mod[key], + enumerable: true + }); + return to; +}; +var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); + +// node_modules/base-x/src/index.js +var require_src = __commonJS((exports, module) => { + function base(ALPHABET) { + if (ALPHABET.length >= 255) { + throw new TypeError("Alphabet too long"); + } + var BASE_MAP = new Uint8Array(256); + for (var j = 0;j < BASE_MAP.length; j++) { + BASE_MAP[j] = 255; + } + for (var i = 0;i < ALPHABET.length; i++) { + var x = ALPHABET.charAt(i); + var xc = x.charCodeAt(0); + if (BASE_MAP[xc] !== 255) { + throw new TypeError(x + " is ambiguous"); + } + BASE_MAP[xc] = i; + } + var BASE = ALPHABET.length; + var LEADER = ALPHABET.charAt(0); + var FACTOR = Math.log(BASE) / Math.log(256); + var iFACTOR = Math.log(256) / Math.log(BASE); + function encode(source) { + if (source instanceof Uint8Array) { + } else if (ArrayBuffer.isView(source)) { + source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength); + } else if (Array.isArray(source)) { + source = Uint8Array.from(source); + } + if (!(source instanceof Uint8Array)) { + throw new TypeError("Expected Uint8Array"); + } + if (source.length === 0) { + return ""; + } + var zeroes = 0; + var length = 0; + var pbegin = 0; + var pend = source.length; + while (pbegin !== pend && source[pbegin] === 0) { + pbegin++; + zeroes++; + } + var size = (pend - pbegin) * iFACTOR + 1 >>> 0; + var b58 = new Uint8Array(size); + while (pbegin !== pend) { + var carry = source[pbegin]; + var i2 = 0; + for (var it1 = size - 1;(carry !== 0 || i2 < length) && it1 !== -1; it1--, i2++) { + carry += 256 * b58[it1] >>> 0; + b58[it1] = carry % BASE >>> 0; + carry = carry / BASE >>> 0; + } + if (carry !== 0) { + throw new Error("Non-zero carry"); + } + length = i2; + pbegin++; + } + var it2 = size - length; + while (it2 !== size && b58[it2] === 0) { + it2++; + } + var str = LEADER.repeat(zeroes); + for (;it2 < size; ++it2) { + str += ALPHABET.charAt(b58[it2]); + } + return str; + } + function decodeUnsafe(source) { + if (typeof source !== "string") { + throw new TypeError("Expected String"); + } + if (source.length === 0) { + return new Uint8Array; + } + var psz = 0; + var zeroes = 0; + var length = 0; + while (source[psz] === LEADER) { + zeroes++; + psz++; + } + var size = (source.length - psz) * FACTOR + 1 >>> 0; + var b256 = new Uint8Array(size); + while (source[psz]) { + var carry = BASE_MAP[source.charCodeAt(psz)]; + if (carry === 255) { + return; + } + var i2 = 0; + for (var it3 = size - 1;(carry !== 0 || i2 < length) && it3 !== -1; it3--, i2++) { + carry += BASE * b256[it3] >>> 0; + b256[it3] = carry % 256 >>> 0; + carry = carry / 256 >>> 0; + } + if (carry !== 0) { + throw new Error("Non-zero carry"); + } + length = i2; + psz++; + } + var it4 = size - length; + while (it4 !== size && b256[it4] === 0) { + it4++; + } + var vch = new Uint8Array(zeroes + (size - it4)); + var j2 = zeroes; + while (it4 !== size) { + vch[j2++] = b256[it4++]; + } + return vch; + } + function decode(string) { + var buffer = decodeUnsafe(string); + if (buffer) { + return buffer; + } + throw new Error("Non-base" + BASE + " character"); + } + return { + encode, + decodeUnsafe, + decode + }; + } + module.exports = base; +}); + +// node_modules/bs58/index.js +var require_bs58 = __commonJS((exports, module) => { + var basex = require_src(); + var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + module.exports = basex(ALPHABET); +}); + +// node_modules/varint/encode.js +var require_encode = __commonJS((exports, module) => { + function encode(num, out, offset) { + if (Number.MAX_SAFE_INTEGER && num > Number.MAX_SAFE_INTEGER) { + encode.bytes = 0; + throw new RangeError("Could not encode varint"); + } + out = out || []; + offset = offset || 0; + var oldOffset = offset; + while (num >= INT) { + out[offset++] = num & 255 | MSB; + num /= 128; + } + while (num & MSBALL) { + out[offset++] = num & 255 | MSB; + num >>>= 7; + } + out[offset] = num | 0; + encode.bytes = offset - oldOffset + 1; + return out; + } + module.exports = encode; + var MSB = 128; + var REST = 127; + var MSBALL = ~REST; + var INT = Math.pow(2, 31); +}); + +// node_modules/varint/decode.js +var require_decode = __commonJS((exports, module) => { + function read(buf, offset) { + var res = 0, offset = offset || 0, shift = 0, counter = offset, b, l = buf.length; + do { + if (counter >= l || shift > 49) { + read.bytes = 0; + throw new RangeError("Could not decode varint"); + } + b = buf[counter++]; + res += shift < 28 ? (b & REST) << shift : (b & REST) * Math.pow(2, shift); + shift += 7; + } while (b >= MSB); + read.bytes = counter - offset; + return res; + } + module.exports = read; + var MSB = 128; + var REST = 127; +}); + +// node_modules/varint/length.js +var require_length = __commonJS((exports, module) => { + var N1 = Math.pow(2, 7); + var N2 = Math.pow(2, 14); + var N3 = Math.pow(2, 21); + var N4 = Math.pow(2, 28); + var N5 = Math.pow(2, 35); + var N6 = Math.pow(2, 42); + var N7 = Math.pow(2, 49); + var N8 = Math.pow(2, 56); + var N9 = Math.pow(2, 63); + module.exports = function(value) { + return value < N1 ? 1 : value < N2 ? 2 : value < N3 ? 3 : value < N4 ? 4 : value < N5 ? 5 : value < N6 ? 6 : value < N7 ? 7 : value < N8 ? 8 : value < N9 ? 9 : 10; + }; +}); + +// node_modules/varint/index.js +var require_varint = __commonJS((exports, module) => { + module.exports = { + encode: require_encode(), + decode: require_decode(), + encodingLength: require_length() + }; +}); + +// index.ts +var bs58 = __toESM(require_bs58(), 1); +var varint = __toESM(require_varint(), 1); +function toMultibaseB58(input) { + const encoded = bs58.encode(input); + return `${base58btc}${encoded}`; +} +function fromMultibaseB58(input) { + const decoded = bs58.decode(input.slice(1)); + return decoded; +} +async function multihashSha256(input) { + const mh = new Uint8Array(2); + const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", input)); + varint.encode(sha2_256, mh, 0); + varint.encode(sha2_bytes_256, mh, 1); + const output = new Uint8Array(mh.length + digest.length); + output.set(mh); + output.set(digest, mh.length); + return output; +} +function toMulticodecJson(input) { + const encoded = new TextEncoder().encode(JSON.stringify(input)); + const bytes = new Uint8Array(2 + encoded.length); + varint.encode(json, bytes, 0); + bytes.set(encoded, 2); + return bytes; +} +function fromMulticodecJson(input) { + const decoded = new TextDecoder().decode(input.slice(2)); + return JSON.parse(decoded); +} +async function encode3(inputDocument) { + const encodedDocument = encodeDocument(inputDocument); + const hash = await hashDocument(encodedDocument); + const longForm = `did:peer:4${hash}:${encodedDocument}`; + return longForm; +} +async function encodeShort(inputDocument) { + const encodedDocument = encodeDocument(inputDocument); + const hash = await hashDocument(encodedDocument); + const shortForm = `did:peer:4${hash}`; + return shortForm; +} +function longToShort(did) { + if (!LONG_RE.test(did)) { + throw new Error("DID is not a long form did:peer:4"); + } + return did.slice(0, did.lastIndexOf(":")); +} +function encodeDocument(document) { + const encoded = toMultibaseB58(toMulticodecJson(document)); + return encoded; +} +async function hashDocument(encodedDocument) { + const bytes = new TextEncoder().encode(encodedDocument); + const multihashed = await multihashSha256(bytes); + return toMultibaseB58(multihashed); +} +async function resolve(did) { + const decodedDocument = await decode2(did); + const document = contextualizeDocument(did, decodedDocument); + document.alsoKnownAs = document.alsoKnownAs || []; + document.alsoKnownAs.push(longToShort(did)); + return document; +} +async function resolveShort(did) { + const decodedDocument = await decode2(did); + const shortForm = longToShort(did); + const document = contextualizeDocument(shortForm, decodedDocument); + document.alsoKnownAs = document.alsoKnownAs || []; + document.alsoKnownAs.push(did); + return document; +} +async function resolveShortFromDoc(document, did) { + const longForm = await encode3(document); + if (did !== null) { + const shortForm = longToShort(longForm); + if (did !== shortForm) { + throw new Error(`DID mismatch: ${did} !== ${shortForm}`); + } + } + return resolveShort(longForm); +} +async function decode2(did) { + if (!did.startsWith("did:peer:4")) { + throw new Error("Invalid did:peer:4"); + } + if (SHORT_RE.test(did)) { + throw new Error("Cannot decode document form short form did:peer:4"); + } + if (!LONG_RE.test(did)) { + throw new Error("Invalid did:peer:4"); + } + const [hash, doc] = did.slice(10).split(":"); + if (hash !== await hashDocument(doc)) { + throw new Error(`Hash is invalid for did: ${did}`); + } + const decoded = fromMulticodecJson(fromMultibaseB58(doc)); + return decoded; +} +function operateOnEmbedded(callback) { + function _curried(document) { + if (typeof document === "string") { + return document; + } else { + return callback(document); + } + } + return _curried; +} +function visitVerificationMethods(document, callback) { + document.verificationMethod = document.verificationMethod?.map(callback); + document.authentication = document.authentication?.map(operateOnEmbedded(callback)); + document.assertionMethod = document.assertionMethod?.map(operateOnEmbedded(callback)); + document.keyAgreement = document.keyAgreement?.map(operateOnEmbedded(callback)); + document.capabilityDelegation = document.capabilityDelegation?.map(operateOnEmbedded(callback)); + document.capabilityInvocation = document.capabilityInvocation?.map(operateOnEmbedded(callback)); + return document; +} +function contextualizeDocument(did, document) { + const contextualized = { ...document }; + contextualized.id = did; + visitVerificationMethods(contextualized, (vm) => { + if (vm.controller === undefined) { + vm.controller = did; + } + return vm; + }); + return contextualized; +} +var json = 512; +var sha2_256 = 18; +var sha2_bytes_256 = 32; +var base58btc = "z"; +var B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; +var LONG_RE = new RegExp(`^did:peer:4zQm[${B58}]{44}:z[${B58}]{6,}\$`); +var SHORT_RE = new RegExp(`^did:peer:4zQm[${B58}]{44}\$`); +export { + resolveShortFromDoc, + resolveShort, + resolve, + longToShort, + encodeShort, + encode3 as encode, + decode2 as decode, + SHORT_RE, + LONG_RE +}; From c90430f2a4f49e40cc8fdd3188f655ffb94a2bcf Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Laptop)" Date: Thu, 17 Oct 2024 15:18:47 -0600 Subject: [PATCH 4/7] feat: Add did rotation Signed-off-by: Colton Wolkins (Laptop) --- src/lib/agent.ts | 40 +++++++++++++++++++++++++- src/lib/contacts.ts | 9 ++++++ src/lib/didcomm.ts | 33 ++++++++++++++++++---- src/lib/worker.ts | 56 +++++++++++++++++++++++++++++++++++++ src/lib/workerTypes.ts | 2 ++ src/pages/profile/navbar.ts | 16 +++++++++++ 6 files changed, 149 insertions(+), 7 deletions(-) diff --git a/src/lib/agent.ts b/src/lib/agent.ts index 53bd3dd..455f523 100644 --- a/src/lib/agent.ts +++ b/src/lib/agent.ts @@ -62,6 +62,32 @@ export class Agent { case "didGenerated": this.onDidGenerated(e.data.payload) break + case "didRotated": + //this.onDidGenerated(e.data.payload) + let contacts = ContactService.getContacts() + const {oldDid, newDid, jwt} = e.data.payload + if(this.profile.did == oldDid) + this.profile.did = newDid + const header = { + "alg": "HS256", + "typ": "JWT", + } + //const encodedHeaders = btoa(JSON.stringify(header)) + //const claims = { + // sub: newDid, + // iss: oldDid, + //} + //const encodedPayload = btoa(JSON.stringify(claims)) + for (let contact of contacts) { + const message = { + type: "https://didcomm.org/empty/1.0/empty", + body: {}, + from_prior: jwt, + } + console.log("Sending new did to contact:", contact, message) + this.sendMessage(contact, message as IMessage) + } + break case "messageReceived": this.onMessageReceived(e.data.payload) break @@ -143,6 +169,11 @@ export class Agent { } private onMessageReceived(message: IMessage) { + console.log("Recieved message: ", message) + if (message?.prior) { + const oldContact = ContactService.getContact(message.prior.iss) + oldContact && ContactService.rotateContact(oldContact, message.prior.sub) + } const from = message.from == this.profile.did ? (this.profile as Contact) @@ -159,7 +190,7 @@ export class Agent { } ContactService.addMessage(message.from, { sender: fromName, - receiver: to.label || to.did, + receiver: to?.label || to.did, timestamp: new Date(), content: message.body.content, type: message.type, @@ -202,6 +233,13 @@ export class Agent { ContactService.addMessage(contact.did, internalMessage) } + public async rotateDid() { + this.worker.postMessage({ + type: "rotateDid", + payload: {}, + }) + } + public async refreshMessages() { this.postMessage({ type: "pickupStatus", diff --git a/src/lib/contacts.ts b/src/lib/contacts.ts index 7e6e66d..2132276 100644 --- a/src/lib/contacts.ts +++ b/src/lib/contacts.ts @@ -154,6 +154,15 @@ export class EphemeralContactService extends ContactService { this.contacts[contact.did] = contact } + rotateContact(contact: Contact, newDid: string): void { + const old_did = contact.did + delete this.contacts[contact.did] + contact.did = newDid + this.contacts[contact.did] = contact + this.messages[newDid] = this.messages[old_did] + delete this.messages[old_did] + } + saveMessageHistory(did: string, messages: Message[]): void { this.messages[did] = messages } diff --git a/src/lib/didcomm.ts b/src/lib/didcomm.ts index 978acec..fe7944b 100644 --- a/src/lib/didcomm.ts +++ b/src/lib/didcomm.ts @@ -8,6 +8,8 @@ import { DIDDoc, SecretsResolver, Secret, + IFromPrior, + FromPrior, Message, UnpackMetadata, PackEncryptedMetadata, @@ -39,17 +41,20 @@ function x25519ToSecret( return secretEnc } -function ed25519ToSecret( +async function ed25519ToSecret( did: DID, ed25519KeyPriv: Uint8Array, ed25519Key: Uint8Array -): Secret { +): Promise { //const verIdent = DIDPeer.keyToIdent(ed25519Key, "ed25519-pub") const verIdent = "key-1" + const ed25519KeyPriv2 = new Uint8Array(ed25519Key.length + ed25519KeyPriv.length) + ed25519KeyPriv2.set(ed25519KeyPriv) + ed25519KeyPriv2.set(ed25519Key, ed25519KeyPriv.length) const secretVer: Secret = { id: `${did}#${verIdent}`, type: "Ed25519VerificationKey2020", - privateKeyMultibase: DIDPeer.keyToMultibase(ed25519KeyPriv, "ed25519-priv"), + privateKeyMultibase: DIDPeer.keyToMultibase(ed25519KeyPriv2, "ed25519-priv"), } return secretVer } @@ -99,7 +104,7 @@ export async function generateDidForMediator() { } const did = await DIDPeer4.encode(doc) - const secretVer = ed25519ToSecret(did, key, verkey) + const secretVer = await ed25519ToSecret(did, key, verkey) const secretEnc = x25519ToSecret(did, enckeyPriv, enckey) return { did, secrets: [secretVer, secretEnc] } } @@ -148,7 +153,7 @@ export async function generateDid(routingDid: DID) { } const did = await DIDPeer4.encode(doc) - const secretVer = ed25519ToSecret(did, key, verkey) + const secretVer = await ed25519ToSecret(did, key, verkey) const secretEnc = x25519ToSecret(did, enckeyPriv, enckey) return { did, secrets: [secretVer, secretEnc] } } @@ -193,13 +198,15 @@ export class DIDPeer4Resolver implements DIDResolver { }) return methods }; - return { + const doc = { id: raw_doc.id, verificationMethod: await fix_vms(raw_doc.verificationMethod), authentication: raw_doc.authentication.map((kid: string) => `${raw_doc.id}${kid}`), keyAgreement: raw_doc.keyAgreement.map((kid: string) => `${raw_doc.id}${kid}`), service: raw_doc.service, } + console.log(doc) + return doc } } @@ -468,6 +475,20 @@ export class DIDComm { } } + async rotateDID( + olddid: DID, + newdid: DID, + ): Promise<[string, string]> { + return await (new FromPrior({iss: olddid, sub: newdid})).pack(null, this.resolver, this.secretsResolver) + return await (new FromPrior({iss: olddid, sub: newdid})).pack(`${olddid}#key-1`, this.resolver, this.secretsResolver) + } + + async getPrior(prior: string): Promise { + const from_prior = await FromPrior.unpack(prior, this.resolver) + console.log(from_prior) + return from_prior[0].as_value() + } + async prepareMessage( to: DID, from: DID, diff --git a/src/lib/worker.ts b/src/lib/worker.ts index d8408fe..d440536 100644 --- a/src/lib/worker.ts +++ b/src/lib/worker.ts @@ -8,6 +8,9 @@ const ctx: Worker = self as any class DIDCommWorker { private didcomm: DIDComm private didForMediator: string + private routingDid: string + private mediatorDid: string + private oldDid: string private did: string private ws: WebSocket @@ -25,6 +28,7 @@ class DIDCommWorker { async establishMediation({ mediatorDid }: { mediatorDid: string }) { logger.log("Establishing mediation with mediator: ", mediatorDid) this.didForMediator = await this.didcomm.generateDidForMediator() + logger.log("Generated did: ", this.didForMediator); { const [msg, meta] = await this.didcomm.sendMessageAndExpectReply( mediatorDid, @@ -43,6 +47,8 @@ class DIDCommWorker { } const routingDid = reply.body.routing_did[0] this.did = await this.didcomm.generateDid(routingDid) + this.routingDid = routingDid + this.mediatorDid = mediatorDid this.postMessage({ type: "didGenerated", payload: this.did }) } @@ -84,6 +90,52 @@ class DIDCommWorker { } } + async rotateDid() { + const oldDid = this.did + const did = await this.didcomm.generateDid(this.routingDid) + const rotatedDid = await this.didcomm.rotateDID(oldDid, did) + + const [msg, meta] = await this.didcomm.sendMessageAndExpectReply( + this.mediatorDid, + this.didForMediator, + { + type: "https://didcomm.org/coordinate-mediation/3.0/recipient-update", + body: { + updates: [ + { + recipient_did: did, + action: "add", + }, + ], + }, + } + ) + + const reply = msg.as_value() + if ( + reply.type !== + "https://didcomm.org/coordinate-mediation/3.0/recipient-update-response" + ) { + console.error("Unexpected reply: ", reply) + throw new Error("Unexpected reply") + } + + if (reply.body.updated[0]?.recipient_did !== did) { + throw new Error("Unexpected did in recipient update response") + } + + if (reply.body.updated[0]?.action !== "add") { + throw new Error("Unexpected action in recipient update response") + } + + if (reply.body.updated[0]?.result !== "success") { + throw new Error("Unexpected status in recipient update response") + } + this.did = did + this.oldDid = oldDid + this.postMessage({ type: "didRotated", payload: {newDid: this.did, oldDid: oldDid, jwt: rotatedDid[0]}}) + } + async pickupStatus({ mediatorDid }: { mediatorDid: string }) { const [msg, meta] = await this.didcomm.sendMessageAndExpectReply( mediatorDid, @@ -204,6 +256,10 @@ class DIDCommWorker { console.log("Unhandled message: ", message) break } + if("from_prior" in message) { + const prior = await this.didcomm.getPrior(message.from_prior) + message.prior = prior + } this.postMessage({ type: "messageReceived", payload: message }) } diff --git a/src/lib/workerTypes.ts b/src/lib/workerTypes.ts index 6c47216..d75a2f9 100644 --- a/src/lib/workerTypes.ts +++ b/src/lib/workerTypes.ts @@ -1,6 +1,7 @@ export type WorkerCommandType = | "init" | "establishMediation" + | "rotateDid" | "connect" | "disconnect" | "sendMessage" @@ -15,6 +16,7 @@ export type WorkerMessageType = | "init" | "log" | "didGenerated" + | "didRotated" | "messageReceived" | "connected" | "disconnected" diff --git a/src/pages/profile/navbar.ts b/src/pages/profile/navbar.ts index 3f0e11b..555eda1 100644 --- a/src/pages/profile/navbar.ts +++ b/src/pages/profile/navbar.ts @@ -98,6 +98,11 @@ export default class Navbar implements m.ClassComponent { agent.refreshMessages() } + rotateDid() { + agent.rotateDid() + } + + view(vnode: m.Vnode) { const { profileName, @@ -170,6 +175,17 @@ export default class Navbar implements m.ClassComponent { { style: { marginRight: ".5em" } }, `(${truncatedDid})` ), + did && m( + "button.button.is-white.navbar-item", + { + onclick: () => { + this.rotateDid() + }, + style: { marginRight: ".5em" }, + title: "Rotate DID", + }, + [m("span.icon", [m("i.fas.fa-refresh")])] + ), did && m( "button.button.is-small.is-white", From 209435b1fcfeed6a0f1b9b5984ccd088ccd22b72 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Laptop)" Date: Mon, 21 Oct 2024 09:13:24 -0600 Subject: [PATCH 5/7] chore: replace peer4 library with source Signed-off-by: Colton Wolkins (Laptop) --- package-lock.json | 32 +++- package.json | 5 +- src/lib/peer4/index.js | 378 ----------------------------------------- src/lib/peer4/index.ts | 170 ++++++++++++++++++ 4 files changed, 204 insertions(+), 381 deletions(-) delete mode 100644 src/lib/peer4/index.js create mode 100644 src/lib/peer4/index.ts diff --git a/package-lock.json b/package-lock.json index 084122b..73333e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,15 +7,17 @@ "": { "name": "didcomm-demo", "version": "1.0.0", - "license": "ISC", + "license": "MIT", "dependencies": { "@noble/curves": "^1.2.0", + "bs58": "^5.0.0", "didcomm": "^0.4.1", "lit-code": "^0.1.12", "mithril": "^2.2.2", "multibase": "^4.0.6", "multicodec": "^3.2.1", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "varint": "^6.0.0" }, "devDependencies": { "@fortawesome/fontawesome-free": "^6.4.2", @@ -25,6 +27,7 @@ "@types/mithril": "^2.0.14", "@types/prismjs": "^1.26.0", "@types/uuid": "^9.0.3", + "@types/varint": "^6.0.3", "bulma": "^0.9.4", "css-loader": "^6.8.1", "html-loader": "^4.2.0", @@ -573,6 +576,16 @@ "integrity": "sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==", "dev": true }, + "node_modules/@types/varint": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/varint/-/varint-6.0.3.tgz", + "integrity": "sha512-DHukoGWdJ2aYkveZJTB2rN2lp6m7APzVsoJQ7j/qy1fQxyamJTPD5xQzCMoJ2Qtgn0mE3wWeNOpbTyBFvF+dyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/ws": { "version": "8.5.5", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", @@ -939,6 +952,12 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==", + "license": "MIT" + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -1094,6 +1113,15 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "license": "MIT", + "dependencies": { + "base-x": "^4.0.0" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", diff --git a/package.json b/package.json index 5f17b16..70b2184 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,14 @@ }, "dependencies": { "@noble/curves": "^1.2.0", + "bs58": "^5.0.0", "didcomm": "^0.4.1", "lit-code": "^0.1.12", "mithril": "^2.2.2", "multibase": "^4.0.6", "multicodec": "^3.2.1", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "varint": "^6.0.0" }, "devDependencies": { "@fortawesome/fontawesome-free": "^6.4.2", @@ -44,6 +46,7 @@ "@types/mithril": "^2.0.14", "@types/prismjs": "^1.26.0", "@types/uuid": "^9.0.3", + "@types/varint": "^6.0.3", "bulma": "^0.9.4", "css-loader": "^6.8.1", "html-loader": "^4.2.0", diff --git a/src/lib/peer4/index.js b/src/lib/peer4/index.js deleted file mode 100644 index d02f40b..0000000 --- a/src/lib/peer4/index.js +++ /dev/null @@ -1,378 +0,0 @@ -var __create = Object.create; -var __getProtoOf = Object.getPrototypeOf; -var __defProp = Object.defineProperty; -var __getOwnPropNames = Object.getOwnPropertyNames; -var __hasOwnProp = Object.prototype.hasOwnProperty; -var __toESM = (mod, isNodeMode, target) => { - target = mod != null ? __create(__getProtoOf(mod)) : {}; - const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target; - for (let key of __getOwnPropNames(mod)) - if (!__hasOwnProp.call(to, key)) - __defProp(to, key, { - get: () => mod[key], - enumerable: true - }); - return to; -}; -var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); - -// node_modules/base-x/src/index.js -var require_src = __commonJS((exports, module) => { - function base(ALPHABET) { - if (ALPHABET.length >= 255) { - throw new TypeError("Alphabet too long"); - } - var BASE_MAP = new Uint8Array(256); - for (var j = 0;j < BASE_MAP.length; j++) { - BASE_MAP[j] = 255; - } - for (var i = 0;i < ALPHABET.length; i++) { - var x = ALPHABET.charAt(i); - var xc = x.charCodeAt(0); - if (BASE_MAP[xc] !== 255) { - throw new TypeError(x + " is ambiguous"); - } - BASE_MAP[xc] = i; - } - var BASE = ALPHABET.length; - var LEADER = ALPHABET.charAt(0); - var FACTOR = Math.log(BASE) / Math.log(256); - var iFACTOR = Math.log(256) / Math.log(BASE); - function encode(source) { - if (source instanceof Uint8Array) { - } else if (ArrayBuffer.isView(source)) { - source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength); - } else if (Array.isArray(source)) { - source = Uint8Array.from(source); - } - if (!(source instanceof Uint8Array)) { - throw new TypeError("Expected Uint8Array"); - } - if (source.length === 0) { - return ""; - } - var zeroes = 0; - var length = 0; - var pbegin = 0; - var pend = source.length; - while (pbegin !== pend && source[pbegin] === 0) { - pbegin++; - zeroes++; - } - var size = (pend - pbegin) * iFACTOR + 1 >>> 0; - var b58 = new Uint8Array(size); - while (pbegin !== pend) { - var carry = source[pbegin]; - var i2 = 0; - for (var it1 = size - 1;(carry !== 0 || i2 < length) && it1 !== -1; it1--, i2++) { - carry += 256 * b58[it1] >>> 0; - b58[it1] = carry % BASE >>> 0; - carry = carry / BASE >>> 0; - } - if (carry !== 0) { - throw new Error("Non-zero carry"); - } - length = i2; - pbegin++; - } - var it2 = size - length; - while (it2 !== size && b58[it2] === 0) { - it2++; - } - var str = LEADER.repeat(zeroes); - for (;it2 < size; ++it2) { - str += ALPHABET.charAt(b58[it2]); - } - return str; - } - function decodeUnsafe(source) { - if (typeof source !== "string") { - throw new TypeError("Expected String"); - } - if (source.length === 0) { - return new Uint8Array; - } - var psz = 0; - var zeroes = 0; - var length = 0; - while (source[psz] === LEADER) { - zeroes++; - psz++; - } - var size = (source.length - psz) * FACTOR + 1 >>> 0; - var b256 = new Uint8Array(size); - while (source[psz]) { - var carry = BASE_MAP[source.charCodeAt(psz)]; - if (carry === 255) { - return; - } - var i2 = 0; - for (var it3 = size - 1;(carry !== 0 || i2 < length) && it3 !== -1; it3--, i2++) { - carry += BASE * b256[it3] >>> 0; - b256[it3] = carry % 256 >>> 0; - carry = carry / 256 >>> 0; - } - if (carry !== 0) { - throw new Error("Non-zero carry"); - } - length = i2; - psz++; - } - var it4 = size - length; - while (it4 !== size && b256[it4] === 0) { - it4++; - } - var vch = new Uint8Array(zeroes + (size - it4)); - var j2 = zeroes; - while (it4 !== size) { - vch[j2++] = b256[it4++]; - } - return vch; - } - function decode(string) { - var buffer = decodeUnsafe(string); - if (buffer) { - return buffer; - } - throw new Error("Non-base" + BASE + " character"); - } - return { - encode, - decodeUnsafe, - decode - }; - } - module.exports = base; -}); - -// node_modules/bs58/index.js -var require_bs58 = __commonJS((exports, module) => { - var basex = require_src(); - var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; - module.exports = basex(ALPHABET); -}); - -// node_modules/varint/encode.js -var require_encode = __commonJS((exports, module) => { - function encode(num, out, offset) { - if (Number.MAX_SAFE_INTEGER && num > Number.MAX_SAFE_INTEGER) { - encode.bytes = 0; - throw new RangeError("Could not encode varint"); - } - out = out || []; - offset = offset || 0; - var oldOffset = offset; - while (num >= INT) { - out[offset++] = num & 255 | MSB; - num /= 128; - } - while (num & MSBALL) { - out[offset++] = num & 255 | MSB; - num >>>= 7; - } - out[offset] = num | 0; - encode.bytes = offset - oldOffset + 1; - return out; - } - module.exports = encode; - var MSB = 128; - var REST = 127; - var MSBALL = ~REST; - var INT = Math.pow(2, 31); -}); - -// node_modules/varint/decode.js -var require_decode = __commonJS((exports, module) => { - function read(buf, offset) { - var res = 0, offset = offset || 0, shift = 0, counter = offset, b, l = buf.length; - do { - if (counter >= l || shift > 49) { - read.bytes = 0; - throw new RangeError("Could not decode varint"); - } - b = buf[counter++]; - res += shift < 28 ? (b & REST) << shift : (b & REST) * Math.pow(2, shift); - shift += 7; - } while (b >= MSB); - read.bytes = counter - offset; - return res; - } - module.exports = read; - var MSB = 128; - var REST = 127; -}); - -// node_modules/varint/length.js -var require_length = __commonJS((exports, module) => { - var N1 = Math.pow(2, 7); - var N2 = Math.pow(2, 14); - var N3 = Math.pow(2, 21); - var N4 = Math.pow(2, 28); - var N5 = Math.pow(2, 35); - var N6 = Math.pow(2, 42); - var N7 = Math.pow(2, 49); - var N8 = Math.pow(2, 56); - var N9 = Math.pow(2, 63); - module.exports = function(value) { - return value < N1 ? 1 : value < N2 ? 2 : value < N3 ? 3 : value < N4 ? 4 : value < N5 ? 5 : value < N6 ? 6 : value < N7 ? 7 : value < N8 ? 8 : value < N9 ? 9 : 10; - }; -}); - -// node_modules/varint/index.js -var require_varint = __commonJS((exports, module) => { - module.exports = { - encode: require_encode(), - decode: require_decode(), - encodingLength: require_length() - }; -}); - -// index.ts -var bs58 = __toESM(require_bs58(), 1); -var varint = __toESM(require_varint(), 1); -function toMultibaseB58(input) { - const encoded = bs58.encode(input); - return `${base58btc}${encoded}`; -} -function fromMultibaseB58(input) { - const decoded = bs58.decode(input.slice(1)); - return decoded; -} -async function multihashSha256(input) { - const mh = new Uint8Array(2); - const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", input)); - varint.encode(sha2_256, mh, 0); - varint.encode(sha2_bytes_256, mh, 1); - const output = new Uint8Array(mh.length + digest.length); - output.set(mh); - output.set(digest, mh.length); - return output; -} -function toMulticodecJson(input) { - const encoded = new TextEncoder().encode(JSON.stringify(input)); - const bytes = new Uint8Array(2 + encoded.length); - varint.encode(json, bytes, 0); - bytes.set(encoded, 2); - return bytes; -} -function fromMulticodecJson(input) { - const decoded = new TextDecoder().decode(input.slice(2)); - return JSON.parse(decoded); -} -async function encode3(inputDocument) { - const encodedDocument = encodeDocument(inputDocument); - const hash = await hashDocument(encodedDocument); - const longForm = `did:peer:4${hash}:${encodedDocument}`; - return longForm; -} -async function encodeShort(inputDocument) { - const encodedDocument = encodeDocument(inputDocument); - const hash = await hashDocument(encodedDocument); - const shortForm = `did:peer:4${hash}`; - return shortForm; -} -function longToShort(did) { - if (!LONG_RE.test(did)) { - throw new Error("DID is not a long form did:peer:4"); - } - return did.slice(0, did.lastIndexOf(":")); -} -function encodeDocument(document) { - const encoded = toMultibaseB58(toMulticodecJson(document)); - return encoded; -} -async function hashDocument(encodedDocument) { - const bytes = new TextEncoder().encode(encodedDocument); - const multihashed = await multihashSha256(bytes); - return toMultibaseB58(multihashed); -} -async function resolve(did) { - const decodedDocument = await decode2(did); - const document = contextualizeDocument(did, decodedDocument); - document.alsoKnownAs = document.alsoKnownAs || []; - document.alsoKnownAs.push(longToShort(did)); - return document; -} -async function resolveShort(did) { - const decodedDocument = await decode2(did); - const shortForm = longToShort(did); - const document = contextualizeDocument(shortForm, decodedDocument); - document.alsoKnownAs = document.alsoKnownAs || []; - document.alsoKnownAs.push(did); - return document; -} -async function resolveShortFromDoc(document, did) { - const longForm = await encode3(document); - if (did !== null) { - const shortForm = longToShort(longForm); - if (did !== shortForm) { - throw new Error(`DID mismatch: ${did} !== ${shortForm}`); - } - } - return resolveShort(longForm); -} -async function decode2(did) { - if (!did.startsWith("did:peer:4")) { - throw new Error("Invalid did:peer:4"); - } - if (SHORT_RE.test(did)) { - throw new Error("Cannot decode document form short form did:peer:4"); - } - if (!LONG_RE.test(did)) { - throw new Error("Invalid did:peer:4"); - } - const [hash, doc] = did.slice(10).split(":"); - if (hash !== await hashDocument(doc)) { - throw new Error(`Hash is invalid for did: ${did}`); - } - const decoded = fromMulticodecJson(fromMultibaseB58(doc)); - return decoded; -} -function operateOnEmbedded(callback) { - function _curried(document) { - if (typeof document === "string") { - return document; - } else { - return callback(document); - } - } - return _curried; -} -function visitVerificationMethods(document, callback) { - document.verificationMethod = document.verificationMethod?.map(callback); - document.authentication = document.authentication?.map(operateOnEmbedded(callback)); - document.assertionMethod = document.assertionMethod?.map(operateOnEmbedded(callback)); - document.keyAgreement = document.keyAgreement?.map(operateOnEmbedded(callback)); - document.capabilityDelegation = document.capabilityDelegation?.map(operateOnEmbedded(callback)); - document.capabilityInvocation = document.capabilityInvocation?.map(operateOnEmbedded(callback)); - return document; -} -function contextualizeDocument(did, document) { - const contextualized = { ...document }; - contextualized.id = did; - visitVerificationMethods(contextualized, (vm) => { - if (vm.controller === undefined) { - vm.controller = did; - } - return vm; - }); - return contextualized; -} -var json = 512; -var sha2_256 = 18; -var sha2_bytes_256 = 32; -var base58btc = "z"; -var B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; -var LONG_RE = new RegExp(`^did:peer:4zQm[${B58}]{44}:z[${B58}]{6,}\$`); -var SHORT_RE = new RegExp(`^did:peer:4zQm[${B58}]{44}\$`); -export { - resolveShortFromDoc, - resolveShort, - resolve, - longToShort, - encodeShort, - encode3 as encode, - decode2 as decode, - SHORT_RE, - LONG_RE -}; diff --git a/src/lib/peer4/index.ts b/src/lib/peer4/index.ts new file mode 100644 index 0000000..0ff7208 --- /dev/null +++ b/src/lib/peer4/index.ts @@ -0,0 +1,170 @@ +import * as bs58 from 'bs58'; +import * as varint from 'varint'; + +type Document = Record; + +const json = 0x0200; +const sha2_256 = 0x12; +const sha2_bytes_256 = 0x20; +const base58btc = "z"; +const B58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; +export const LONG_RE = new RegExp(`^did:peer:4zQm[${B58}]{44}:z[${B58}]{6,}$`) +export const SHORT_RE = new RegExp(`^did:peer:4zQm[${B58}]{44}$`) + +function toMultibaseB58(input: Uint8Array): string { + const encoded = bs58.encode(input); + return `${base58btc}${encoded}`; +} + +function fromMultibaseB58(input: string): Uint8Array { + const decoded = bs58.decode(input.slice(1)); + return decoded +} + +async function multihashSha256(input: Uint8Array): Promise { + const mh = new Uint8Array(2); + const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', input)); + varint.encode(sha2_256, mh, 0); + varint.encode(sha2_bytes_256, mh, 1); + const output = new Uint8Array(mh.length + digest.length); + output.set(mh); + output.set(digest, mh.length); + return output +} + +function toMulticodecJson(input: Document): Uint8Array { + const encoded = new TextEncoder().encode(JSON.stringify(input)); + const bytes = new Uint8Array(2 + encoded.length); + varint.encode(json, bytes, 0); + bytes.set(encoded, 2); + return bytes +} + +function fromMulticodecJson(input: Uint8Array): Document { + const decoded = new TextDecoder().decode(input.slice(2)); + return JSON.parse(decoded) +} + +export async function encode(inputDocument: Document): Promise { + const encodedDocument = encodeDocument(inputDocument); + const hash = await hashDocument(encodedDocument); + + const longForm = `did:peer:4${hash}:${encodedDocument}`; + + return longForm; +} + +export async function encodeShort(inputDocument: Document): Promise { + const encodedDocument = encodeDocument(inputDocument); + const hash = await hashDocument(encodedDocument); + + const shortForm = `did:peer:4${hash}`; + + return shortForm; +} + +export function longToShort(did: string): string { + if (!LONG_RE.test(did)) { + throw new Error('DID is not a long form did:peer:4'); + } + + return did.slice(0, did.lastIndexOf(':')) +} + +function encodeDocument(document: Document): string { + const encoded = toMultibaseB58( + toMulticodecJson(document) + ) + return encoded +} + +async function hashDocument(encodedDocument: string): Promise { + const bytes = new TextEncoder().encode(encodedDocument); + const multihashed = await multihashSha256(bytes); + return toMultibaseB58(multihashed); +} + +export async function resolve(did: string): Promise { + const decodedDocument = await decode(did); + const document = contextualizeDocument(did, decodedDocument); + document.alsoKnownAs = document.alsoKnownAs || []; + document.alsoKnownAs.push(longToShort(did)); + return document; +} + +export async function resolveShort(did: string): Promise { + const decodedDocument = await decode(did); + const shortForm = longToShort(did); + const document = contextualizeDocument(shortForm, decodedDocument); + document.alsoKnownAs = document.alsoKnownAs || []; + document.alsoKnownAs.push(did); + return document; +} + +export async function resolveShortFromDoc(document: Document, did: string | null): Promise { + const longForm = await encode(document); + if (did !== null) { + const shortForm = longToShort(longForm); + if (did !== shortForm) { + throw new Error(`DID mismatch: ${did} !== ${shortForm}`); + } + } + return resolveShort(longForm); +} + +export async function decode(did: string): Promise { + if (!did.startsWith("did:peer:4")) { + throw new Error('Invalid did:peer:4'); + } + + if (SHORT_RE.test(did)) { + throw new Error('Cannot decode document form short form did:peer:4'); + } + + if (!LONG_RE.test(did)) { + throw new Error('Invalid did:peer:4'); + } + + const [hash, doc] = did.slice(10).split(':') + if (hash !== await hashDocument(doc)) { + throw new Error(`Hash is invalid for did: ${did}`); + } + + const decoded = fromMulticodecJson(fromMultibaseB58(doc)) + return decoded; +} + +function operateOnEmbedded(callback: (document: Document) => Document): Document | string { + function _curried(document: Document | string): Document | string { + if (typeof document === "string") { + return document; + } else { + return callback(document); + } + } + return _curried; +} + +function visitVerificationMethods(document: Document, callback: (document: Document) => Document) { + document.verificationMethod = document.verificationMethod?.map(callback); + document.authentication = document.authentication?.map(operateOnEmbedded(callback)); + document.assertionMethod = document.assertionMethod?.map(operateOnEmbedded(callback)); + document.keyAgreement = document.keyAgreement?.map(operateOnEmbedded(callback)); + document.capabilityDelegation = document.capabilityDelegation?.map(operateOnEmbedded(callback)); + document.capabilityInvocation = document.capabilityInvocation?.map(operateOnEmbedded(callback)); + return document +} + +function contextualizeDocument(did: string, document: Document): Document { + const contextualized = { ...document }; + contextualized.id = did; + + visitVerificationMethods(contextualized, (vm) => { + if (vm.controller === undefined) { + vm.controller = did + } + return vm + }) + + return contextualized; +} From dbe17b339b37606e55f09caa79baaee405399c37 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Laptop)" Date: Mon, 28 Oct 2024 13:56:24 -0600 Subject: [PATCH 6/7] chore: cleanup logs and code Signed-off-by: Colton Wolkins (Laptop) --- src/lib/agent.ts | 13 ++----------- src/lib/didcomm.ts | 1 - 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/lib/agent.ts b/src/lib/agent.ts index 455f523..9f4d754 100644 --- a/src/lib/agent.ts +++ b/src/lib/agent.ts @@ -63,27 +63,18 @@ export class Agent { this.onDidGenerated(e.data.payload) break case "didRotated": - //this.onDidGenerated(e.data.payload) let contacts = ContactService.getContacts() const {oldDid, newDid, jwt} = e.data.payload if(this.profile.did == oldDid) this.profile.did = newDid - const header = { - "alg": "HS256", - "typ": "JWT", - } - //const encodedHeaders = btoa(JSON.stringify(header)) - //const claims = { - // sub: newDid, - // iss: oldDid, - //} - //const encodedPayload = btoa(JSON.stringify(claims)) + for (let contact of contacts) { const message = { type: "https://didcomm.org/empty/1.0/empty", body: {}, from_prior: jwt, } + logger.log("Sending new did to contact:", contact?.label || contact.did) console.log("Sending new did to contact:", contact, message) this.sendMessage(contact, message as IMessage) } diff --git a/src/lib/didcomm.ts b/src/lib/didcomm.ts index fe7944b..ef6fbfb 100644 --- a/src/lib/didcomm.ts +++ b/src/lib/didcomm.ts @@ -205,7 +205,6 @@ export class DIDPeer4Resolver implements DIDResolver { keyAgreement: raw_doc.keyAgreement.map((kid: string) => `${raw_doc.id}${kid}`), service: raw_doc.service, } - console.log(doc) return doc } } From 5abc403dbe0aa949022cb1c848e964e084508ce4 Mon Sep 17 00:00:00 2001 From: "Colton Wolkins (Laptop)" Date: Mon, 28 Oct 2024 14:08:04 -0600 Subject: [PATCH 7/7] chore: clean up more log entries and clarify logs Signed-off-by: Colton Wolkins (Laptop) --- src/lib/agent.ts | 1 - src/lib/didcomm.ts | 2 +- src/lib/worker.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/agent.ts b/src/lib/agent.ts index 9f4d754..9891149 100644 --- a/src/lib/agent.ts +++ b/src/lib/agent.ts @@ -160,7 +160,6 @@ export class Agent { } private onMessageReceived(message: IMessage) { - console.log("Recieved message: ", message) if (message?.prior) { const oldContact = ContactService.getContact(message.prior.iss) oldContact && ContactService.rotateContact(oldContact, message.prior.sub) diff --git a/src/lib/didcomm.ts b/src/lib/didcomm.ts index ef6fbfb..f82a9da 100644 --- a/src/lib/didcomm.ts +++ b/src/lib/didcomm.ts @@ -484,7 +484,7 @@ export class DIDComm { async getPrior(prior: string): Promise { const from_prior = await FromPrior.unpack(prior, this.resolver) - console.log(from_prior) + console.log("received from_prior:", from_prior) return from_prior[0].as_value() } diff --git a/src/lib/worker.ts b/src/lib/worker.ts index d440536..27b0e13 100644 --- a/src/lib/worker.ts +++ b/src/lib/worker.ts @@ -198,7 +198,7 @@ class DIDCommWorker { } async handleMessage(message: IMessage) { - console.log("handleMessage: ", message) + console.log("handleMessage: ", "(before 'Received:' stringify)", message) switch (message.type) { case "https://didcomm.org/messagepickup/3.0/status": if (message.body.message_count > 0) {