decentralised message store
1import { __DEV__, SERVER_PORT, SERVICE_DID } from "@/lib/env";
2import {
3 didWebSchema,
4 type DidDocument,
5 type DidWeb,
6 type VerificationMethod,
7} from "@/lib/types/atproto";
8import { Secp256k1PrivateKeyExportable } from "@atcute/crypto";
9import { toString as uint8arraysToString } from "uint8arrays";
10
11export interface ServiceKeys {
12 atproto: Secp256k1PrivateKeyExportable;
13 service: Secp256k1PrivateKeyExportable;
14}
15
16export interface CreateDidWebDocResult {
17 didDoc: DidDocument;
18 keys: ServiceKeys;
19}
20
21const buildDidWebDoc = async (
22 didWeb: DidWeb,
23): Promise<CreateDidWebDocResult> => {
24 const atprotoKey = await Secp256k1PrivateKeyExportable.createKeypair();
25 const serviceKey = await Secp256k1PrivateKeyExportable.createKeypair();
26
27 const atprotoMultikey = encodeMultikey(
28 await atprotoKey.exportPublicKey("raw"),
29 );
30 const serviceMultikey = encodeMultikey(
31 await atprotoKey.exportPublicKey("raw"),
32 );
33
34 const { domain, serviceEndpoint } = extractInfoFromDidWeb(didWeb);
35
36 const verificationMethod: Array<VerificationMethod> = [
37 {
38 id: `${didWeb}#atproto`,
39 type: "Multikey",
40 controller: didWeb,
41 publicKeyMultibase: atprotoMultikey,
42 },
43 ];
44
45 const didDoc: DidDocument = {
46 "@context": [
47 "https://www.w3.org/ns/did/v1",
48 "https://w3id.org/security/multikey/v1",
49 "https://w3id.org/security/suites/secp256k1-2019/v1",
50 ],
51 id: didWeb,
52 verificationMethod,
53 };
54
55 if (serviceEndpoint) {
56 const serviceEndpointType = "GemstoneShard";
57
58 const serviceEndpointUrl = `https://${domain}/`;
59
60 // @ts-expect-error we are already adding the verificationMethod array above when we create didDoc.
61 didDoc.verificationMethod.push({
62 id: `${didWeb}#${serviceEndpoint}`,
63 type: "Multikey",
64 controller: didWeb,
65 publicKeyMultibase: serviceMultikey,
66 });
67
68 didDoc.service = [
69 {
70 id: `${didWeb}#${serviceEndpoint}`,
71 type: serviceEndpointType,
72 serviceEndpoint: serviceEndpointUrl,
73 },
74 ];
75 }
76
77 return {
78 didDoc,
79 keys: {
80 atproto: atprotoKey,
81 service: serviceKey,
82 },
83 };
84};
85
86const encodeMultikey = (publicKeyBytes: Uint8Array) => {
87 // For secp256k1 (K-256), prefix with 0xE701
88 const prefixed = new Uint8Array(publicKeyBytes.length + 2);
89 prefixed[0] = 0xe7;
90 prefixed[1] = 0x01;
91 prefixed.set(publicKeyBytes, 2);
92
93 // Base58-btc encode with 'z' prefix
94 const value = uint8arraysToString(prefixed, "base58btc");
95
96 return "z" + value;
97};
98
99const extractInfoFromDidWeb = (didWeb: DidWeb) => {
100 const fragments = didWeb.split("#");
101 return {
102 domain: fragments[0].replace("did:web:", ""),
103 serviceEndpoint: fragments[1] as string | undefined,
104 };
105};
106
107const createDidWebDoc = async () => {
108 let did = SERVICE_DID;
109 if (__DEV__) {
110 did = `${did}%3A${SERVER_PORT.toString()}`;
111 }
112 const { success: isDidWeb, data: didWeb } = didWebSchema.safeParse(did);
113 if (!isDidWeb) return;
114 const { didDoc } = await buildDidWebDoc(didWeb);
115 return didDoc;
116};
117
118export const didDoc = await createDidWebDoc();