@atcute/did-plc 0.1.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/typedefs.ts CHANGED
@@ -42,10 +42,8 @@ const _unsignedLegacyCreateOperation = v.object({
42
42
  prev: v.null(),
43
43
  signingKey: didKeyString,
44
44
  recoveryKey: didKeyString,
45
- handle: v.string().assert((input) => input.length <= 256, `handle too long (max 256 characters)`),
46
- service: v
47
- .string()
48
- .assert((input) => input.length <= 512, `service endpoint too long (max 512 characters)`),
45
+ handle: v.string(),
46
+ service: v.string(),
49
47
  }) satisfies v.Type<t.UnsignedLegacyCreateOperation>;
50
48
 
51
49
  export const unsignedLegacyCreateOperation: v.Type<t.UnsignedLegacyCreateOperation> =
@@ -58,108 +56,17 @@ export const legacyCreateOperation: v.Type<t.LegacyCreateOperation> = _unsignedL
58
56
 
59
57
  // #region plc_operation
60
58
  export const service: v.Type<t.Service> = v.object({
61
- type: v.string().assert((input) => input.length <= 256, `service type too long (max 256 characters)`),
62
- endpoint: v
63
- .string()
64
- .assert((input) => input.length <= 512, `service endpoint too long (max 512 characters)`),
59
+ type: v.string(),
60
+ endpoint: v.string(),
65
61
  });
66
62
 
67
63
  const _unsignedOperation = v.object({
68
64
  type: v.literal('plc_operation'),
69
65
  prev: v.string().nullable(),
70
- rotationKeys: v.array(didKeyString).chain((input) => {
71
- const length = input.length;
72
-
73
- if (length === 0) {
74
- return v.err(`missing rotation keys`);
75
- } else if (length > 10) {
76
- return v.err(`too many rotation keys (max 10 keys)`);
77
- }
78
-
79
- for (let i = 0; i < length; i++) {
80
- const key = input[i];
81
-
82
- for (let j = 0; j < i; j++) {
83
- if (input[j] === key) {
84
- return v.err({
85
- message: `duplicate "${key}" rotation key`,
86
- path: [i],
87
- });
88
- }
89
- }
90
- }
91
-
92
- return v.ok(input);
93
- }),
94
- verificationMethods: v.record(permissiveDidKeyString).chain((input) => {
95
- const length = Object.keys(input).length;
96
-
97
- if (length > 10) {
98
- return v.err(`too many verification method entries (max 10)`);
99
- }
100
-
101
- for (const id in input) {
102
- const key = input[id];
103
-
104
- if (id.length > 32) {
105
- return v.err({
106
- message: `verification method id too long (max 32 characters)`,
107
- path: [id],
108
- });
109
- }
110
-
111
- if (key.length > 256) {
112
- return v.err({
113
- message: `verification method key too long (max 256 characters)`,
114
- path: [id],
115
- });
116
- }
117
- }
118
-
119
- return v.ok(input);
120
- }),
121
- alsoKnownAs: v
122
- .array(v.string().assert((input) => input.length <= 256, `aka entry too long (max 256 characters)`))
123
- .chain((input) => {
124
- const length = input.length;
125
-
126
- if (length > 10) {
127
- return v.err(`too many aka entries (max 10)`);
128
- }
129
-
130
- for (let i = 0; i < length; i++) {
131
- const aka = input[i];
132
-
133
- for (let j = 0; j < i; j++) {
134
- if (input[j] === aka) {
135
- return v.err({
136
- message: `duplicate "${aka}" aka entry`,
137
- path: [i],
138
- });
139
- }
140
- }
141
- }
142
-
143
- return v.ok(input);
144
- }),
145
- services: v.record(service).chain((input) => {
146
- const length = Object.keys(input).length;
147
-
148
- if (length > 10) {
149
- return v.err(`too many service entries (max 10)`);
150
- }
151
-
152
- for (const id in input) {
153
- if (id.length > 32) {
154
- return v.err({
155
- message: `service id too long (max 32 characters)`,
156
- path: [id],
157
- });
158
- }
159
- }
160
-
161
- return v.ok(input);
162
- }),
66
+ rotationKeys: v.array(didKeyString),
67
+ verificationMethods: v.record(permissiveDidKeyString),
68
+ alsoKnownAs: v.array(v.string()),
69
+ services: v.record(service),
163
70
  }) satisfies v.Type<t.UnsignedOperation>;
164
71
 
165
72
  export const unsignedOperation: v.Type<t.UnsignedOperation> = _unsignedOperation;
@@ -213,3 +120,18 @@ export const indexedEntryLog: v.Type<t.IndexedEntryLog> = v
213
120
  .tuple([_indexedEntry.extend({ operation: compatibleOperation })])
214
121
  .concat(v.array(_indexedEntry.extend({ operation: operationOrTombstone })));
215
122
  // #endregion
123
+
124
+ // #region Client response schemas
125
+ export const plcState: v.Type<t.PlcState> = v.object({
126
+ did: didPlcString,
127
+ rotationKeys: v.array(didKeyString),
128
+ verificationMethods: v.record(permissiveDidKeyString),
129
+ alsoKnownAs: v.array(v.string()),
130
+ services: v.record(service),
131
+ });
132
+
133
+ export const sequencedEntry: v.Type<t.SequencedEntry> = _indexedEntry.extend({
134
+ type: v.literal('sequenced_op'),
135
+ seq: v.number(),
136
+ });
137
+ // #endregion
package/lib/types.ts CHANGED
@@ -71,3 +71,26 @@ export type IndexedEntryLog = [
71
71
  genesis: IndexedEntry<CompatibleOperation>,
72
72
  ...IndexedEntry<OperationOrTombstone>[],
73
73
  ];
74
+
75
+ // #region client response types
76
+
77
+ /**
78
+ * current identity state derived from the did:plc operation log
79
+ */
80
+ export interface PlcState {
81
+ did: DidPlcString;
82
+ rotationKeys: DidKeyString[];
83
+ verificationMethods: Record<string, DidKeyString>;
84
+ alsoKnownAs: string[];
85
+ services: Record<string, Service>;
86
+ }
87
+
88
+ /**
89
+ * operation entry with sequence number from /export endpoint
90
+ */
91
+ export interface SequencedEntry extends IndexedEntry {
92
+ type: 'sequenced_op';
93
+ seq: number;
94
+ }
95
+
96
+ // #endregion
package/lib/utils.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import * as CBOR from '@atcute/cbor';
2
+ import type { PrivateKey } from '@atcute/crypto';
2
3
  import { verifySigWithDidKey } from '@atcute/crypto';
3
- import { fromBase64Url } from '@atcute/multibase';
4
+ import { fromBase64Url, toBase32, toBase64Url } from '@atcute/multibase';
5
+ import { toSha256 } from '@atcute/uint8array';
4
6
 
5
7
  import * as t from './types.js';
6
8
 
@@ -22,6 +24,17 @@ export const wrapAtprotoPrefix = (str: string): string => {
22
24
  return `at://${stripped}`;
23
25
  };
24
26
 
27
+ /**
28
+ * derives the did:plc identifier from a genesis operation
29
+ * @param op signed genesis operation
30
+ * @returns the did:plc string
31
+ */
32
+ export const deriveDidFromGenesisOp = async (op: t.CompatibleOperation): Promise<t.DidPlcString> => {
33
+ const opBytes = CBOR.encode(op);
34
+ const hash = await toSha256(opBytes);
35
+ return `did:plc:${toBase32(hash).slice(0, 24)}`;
36
+ };
37
+
25
38
  export const normalizeOp = (op: t.CompatibleOperation): t.Operation => {
26
39
  if (op.type === 'create') {
27
40
  return {
@@ -64,3 +77,33 @@ export const isSignedOperationValid = async (
64
77
 
65
78
  return null;
66
79
  };
80
+
81
+ // #region signing operations
82
+
83
+ /**
84
+ * signs an unsigned plc operation
85
+ * @param op unsigned operation to sign
86
+ * @param key private key to sign with (must be one of the rotation keys)
87
+ * @returns signed operation
88
+ */
89
+ export const signOperation = async (op: t.UnsignedOperation, key: PrivateKey): Promise<t.Operation> => {
90
+ const data = CBOR.encode(op);
91
+ const sig = await key.sign(data);
92
+
93
+ return { ...op, sig: toBase64Url(sig) };
94
+ };
95
+
96
+ /**
97
+ * signs an unsigned plc tombstone
98
+ * @param op unsigned tombstone to sign
99
+ * @param key private key to sign with (must be one of the rotation keys)
100
+ * @returns signed tombstone
101
+ */
102
+ export const signTombstone = async (op: t.UnsignedTombstone, key: PrivateKey): Promise<t.Tombstone> => {
103
+ const data = CBOR.encode(op);
104
+ const sig = await key.sign(data);
105
+
106
+ return { ...op, sig: toBase64Url(sig) };
107
+ };
108
+
109
+ // #endregion
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@atcute/did-plc",
4
- "version": "0.1.7",
4
+ "version": "0.3.0",
5
5
  "description": "validations, type definitions and schemas for did:plc operations",
6
6
  "keywords": [
7
7
  "atproto",
@@ -13,6 +13,9 @@
13
13
  "url": "https://github.com/mary-ext/atcute",
14
14
  "directory": "packages/identity/did-plc"
15
15
  },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
16
19
  "files": [
17
20
  "dist/",
18
21
  "lib/",
@@ -24,21 +27,23 @@
24
27
  },
25
28
  "sideEffects": false,
26
29
  "devDependencies": {
27
- "@types/bun": "^1.2.21"
30
+ "@vitest/coverage-v8": "^4.0.16",
31
+ "vitest": "^4.0.16"
28
32
  },
29
33
  "dependencies": {
30
34
  "@badrap/valita": "^0.4.6",
31
- "@atcute/cbor": "^2.2.6",
32
- "@atcute/crypto": "^2.2.5",
33
- "@atcute/cid": "^2.2.4",
34
- "@atcute/identity": "^1.1.1",
35
- "@atcute/lexicons": "^1.2.2",
35
+ "@atcute/cbor": "^2.2.8",
36
+ "@atcute/cid": "^2.2.6",
37
+ "@atcute/crypto": "^2.3.0",
38
+ "@atcute/identity": "^1.1.3",
39
+ "@atcute/lexicons": "^1.2.6",
36
40
  "@atcute/multibase": "^1.1.6",
37
- "@atcute/uint8array": "^1.0.5"
41
+ "@atcute/uint8array": "^1.0.6",
42
+ "@atcute/util-fetch": "^1.0.4"
38
43
  },
39
44
  "scripts": {
40
- "build": "tsc --project tsconfig.build.json",
41
- "test": "bun test --coverage",
45
+ "build": "tsgo --project tsconfig.build.json",
46
+ "test": "vitest",
42
47
  "prepublish": "rm -rf dist; pnpm run build"
43
48
  }
44
49
  }