decentralised message store

refactor: did web factory

serenity 31a765e4 2c8bdef4

Changed files
+155 -10
src
lib
types
utils
routes
dot-well-known
did-dot-json
+10 -10
src/lib/types/atproto.ts
···
});
export type AtUri = z.infer<typeof atUriSchema>;
+
export const verificationMethodSchema = z.object({
+
id: z.string(),
+
type: z.string(),
+
controller: z.string(),
+
publicKeyMultibase: z.string(),
+
});
+
export type VerificationMethod = z.infer<typeof verificationMethodSchema>;
+
export const didDocumentSchema = z.object({
+
"@context": z.array(z.string()),
id: z.string(),
alsoKnownAs: z.optional(z.array(z.string())),
-
verificationMethod: z.optional(
-
z.array(
-
z.object({
-
id: z.string(),
-
type: z.string(),
-
controller: z.string(),
-
publicKeyMultibase: z.string(),
-
}),
-
),
-
),
+
verificationMethod: z.optional(z.array(verificationMethodSchema)),
service: z.optional(
z.array(
z.object({
+102
src/lib/utils/did.ts
···
+
import type {
+
DidDocument,
+
DidWeb,
+
VerificationMethod,
+
} from "@/lib/types/atproto";
+
import { Secp256k1PrivateKeyExportable } from "@atcute/crypto";
+
import { toString as uint8arraysToString } from "uint8arrays";
+
+
export interface ServiceKeys {
+
atproto: Secp256k1PrivateKeyExportable;
+
service: Secp256k1PrivateKeyExportable;
+
}
+
+
export interface CreateDidWebDocResult {
+
didDoc: DidDocument;
+
keys: ServiceKeys;
+
}
+
+
export const createDidWebDoc = async (
+
didWeb: DidWeb,
+
): Promise<CreateDidWebDocResult> => {
+
const atprotoKey = await Secp256k1PrivateKeyExportable.createKeypair();
+
const serviceKey = await Secp256k1PrivateKeyExportable.createKeypair();
+
+
const atprotoMultikey = encodeMultikey(
+
await atprotoKey.exportPublicKey("raw"),
+
);
+
const serviceMultikey = encodeMultikey(
+
await atprotoKey.exportPublicKey("raw"),
+
);
+
+
const { domain, serviceEndpoint } = extractInfoFromDidWeb(didWeb);
+
+
const verificationMethod: Array<VerificationMethod> = [
+
{
+
id: `${didWeb}#atproto`,
+
type: "Multikey",
+
controller: didWeb,
+
publicKeyMultibase: atprotoMultikey,
+
},
+
];
+
+
const didDoc: DidDocument = {
+
"@context": [
+
"https://www.w3.org/ns/did/v1",
+
"https://w3id.org/security/multikey/v1",
+
],
+
id: didWeb,
+
verificationMethod,
+
};
+
+
if (serviceEndpoint) {
+
const serviceEndpointType = "GemstoneShard";
+
+
const serviceEndpointUrl = `https://${domain}/`;
+
+
// @ts-expect-error we are already adding the verificationMethod array above when we create didDoc.
+
didDoc.verificationMethod.push({
+
id: `${didWeb}#${serviceEndpoint}`,
+
type: "Multikey",
+
controller: didWeb,
+
publicKeyMultibase: serviceMultikey,
+
});
+
+
didDoc.service = [
+
{
+
id: `${didWeb}#${serviceEndpoint}`,
+
type: serviceEndpointType,
+
serviceEndpoint: serviceEndpointUrl,
+
},
+
];
+
}
+
+
return {
+
didDoc,
+
keys: {
+
atproto: atprotoKey,
+
service: serviceKey,
+
},
+
};
+
};
+
+
const encodeMultikey = (publicKeyBytes: Uint8Array) => {
+
// For secp256k1 (K-256), prefix with 0xE701
+
const prefixed = new Uint8Array(publicKeyBytes.length + 2);
+
prefixed[0] = 0xe7;
+
prefixed[1] = 0x01;
+
prefixed.set(publicKeyBytes, 2);
+
+
// Base58-btc encode with 'z' prefix
+
const value = uint8arraysToString(prefixed, "base58btc");
+
+
return "z" + value;
+
};
+
+
const extractInfoFromDidWeb = (didWeb: DidWeb) => {
+
const fragments = didWeb.split("#");
+
return {
+
domain: fragments[0].replace("did:web:", ""),
+
serviceEndpoint: fragments[1] as string | undefined,
+
};
+
};
+43
src/routes/dot-well-known/did-dot-json/route.ts
···
+
import { SERVICE_DID } from "@/lib/env";
+
import type { Did } from "@/lib/types/atproto";
+
import { didDocumentSchema, didWebSchema } from "@/lib/types/atproto";
+
import type { Route, RouteHandler } from "@/lib/types/routes";
+
import { createDidWebDoc } from "@/lib/utils/did";
+
import { newErrorResponse } from "@/lib/utils/http/responses";
+
+
const routeHandlerFactory = (did: Did) => {
+
const serveDidPlc: RouteHandler = async () => {
+
const plcDirectoryReq = new Request(`https://plc.directory/${did}`);
+
const plcDirectoryRes = await fetch(plcDirectoryReq);
+
const {
+
success,
+
data: didDocument,
+
error,
+
} = didDocumentSchema.safeParse(await plcDirectoryRes.json());
+
+
if (!success)
+
return newErrorResponse(500, {
+
message:
+
"did:plc was not set properly. Either the Shard's did:plc is wrong, or the did:plc was not registered with a public ledger.",
+
details: error,
+
});
+
+
return Response.json(didDocument);
+
};
+
+
const { success: isDidWeb, data: didWeb } = didWebSchema.safeParse(did);
+
if (!isDidWeb) return serveDidPlc;
+
+
const serveDidDoc: RouteHandler = () => {
+
const didDoc = createDidWebDoc(didWeb);
+
+
return Response.json(didDoc);
+
};
+
+
return serveDidDoc;
+
};
+
+
export const didWebDocRoute: Route = {
+
method: "GET",
+
handler: routeHandlerFactory(SERVICE_DID),
+
};