@graffiti-garden/implementation-decentralized 0.0.1
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/LICENSE +674 -0
- package/dist/1-services/1-authorization.d.ts +37 -0
- package/dist/1-services/1-authorization.d.ts.map +1 -0
- package/dist/1-services/2-dids-tests.d.ts +2 -0
- package/dist/1-services/2-dids-tests.d.ts.map +1 -0
- package/dist/1-services/2-dids.d.ts +9 -0
- package/dist/1-services/2-dids.d.ts.map +1 -0
- package/dist/1-services/3-storage-buckets-tests.d.ts +2 -0
- package/dist/1-services/3-storage-buckets-tests.d.ts.map +1 -0
- package/dist/1-services/3-storage-buckets.d.ts +11 -0
- package/dist/1-services/3-storage-buckets.d.ts.map +1 -0
- package/dist/1-services/4-inboxes-tests.d.ts +2 -0
- package/dist/1-services/4-inboxes-tests.d.ts.map +1 -0
- package/dist/1-services/4-inboxes.d.ts +87 -0
- package/dist/1-services/4-inboxes.d.ts.map +1 -0
- package/dist/1-services/utilities.d.ts +7 -0
- package/dist/1-services/utilities.d.ts.map +1 -0
- package/dist/2-primitives/1-string-encoding-tests.d.ts +2 -0
- package/dist/2-primitives/1-string-encoding-tests.d.ts.map +1 -0
- package/dist/2-primitives/1-string-encoding.d.ts +6 -0
- package/dist/2-primitives/1-string-encoding.d.ts.map +1 -0
- package/dist/2-primitives/2-content-addresses-tests.d.ts +2 -0
- package/dist/2-primitives/2-content-addresses-tests.d.ts.map +1 -0
- package/dist/2-primitives/2-content-addresses.d.ts +8 -0
- package/dist/2-primitives/2-content-addresses.d.ts.map +1 -0
- package/dist/2-primitives/3-channel-attestations-tests.d.ts +2 -0
- package/dist/2-primitives/3-channel-attestations-tests.d.ts.map +1 -0
- package/dist/2-primitives/3-channel-attestations.d.ts +13 -0
- package/dist/2-primitives/3-channel-attestations.d.ts.map +1 -0
- package/dist/2-primitives/4-allowed-attestations-tests.d.ts +2 -0
- package/dist/2-primitives/4-allowed-attestations-tests.d.ts.map +1 -0
- package/dist/2-primitives/4-allowed-attestations.d.ts +9 -0
- package/dist/2-primitives/4-allowed-attestations.d.ts.map +1 -0
- package/dist/3-protocol/1-sessions.d.ts +81 -0
- package/dist/3-protocol/1-sessions.d.ts.map +1 -0
- package/dist/3-protocol/2-handles-tests.d.ts +2 -0
- package/dist/3-protocol/2-handles-tests.d.ts.map +1 -0
- package/dist/3-protocol/2-handles.d.ts +13 -0
- package/dist/3-protocol/2-handles.d.ts.map +1 -0
- package/dist/3-protocol/3-object-encoding-tests.d.ts +2 -0
- package/dist/3-protocol/3-object-encoding-tests.d.ts.map +1 -0
- package/dist/3-protocol/3-object-encoding.d.ts +43 -0
- package/dist/3-protocol/3-object-encoding.d.ts.map +1 -0
- package/dist/3-protocol/4-graffiti.d.ts +79 -0
- package/dist/3-protocol/4-graffiti.d.ts.map +1 -0
- package/dist/3-protocol/login-dialog.html.d.ts +2 -0
- package/dist/3-protocol/login-dialog.html.d.ts.map +1 -0
- package/dist/browser/ajv-QBSREQSI.js +9 -0
- package/dist/browser/ajv-QBSREQSI.js.map +7 -0
- package/dist/browser/build-BXWPS7VK.js +2 -0
- package/dist/browser/build-BXWPS7VK.js.map +7 -0
- package/dist/browser/chunk-RFBBAUMM.js +2 -0
- package/dist/browser/chunk-RFBBAUMM.js.map +7 -0
- package/dist/browser/graffiti-KV3G3O72-URO7SJIJ.js +2 -0
- package/dist/browser/graffiti-KV3G3O72-URO7SJIJ.js.map +7 -0
- package/dist/browser/index.js +16 -0
- package/dist/browser/index.js.map +7 -0
- package/dist/browser/login-dialog.html-XUWYDNNI.js +44 -0
- package/dist/browser/login-dialog.html-XUWYDNNI.js.map +7 -0
- package/dist/browser/rock-salt-LI7DAH66-KPFEBIBO.js +2 -0
- package/dist/browser/rock-salt-LI7DAH66-KPFEBIBO.js.map +7 -0
- package/dist/browser/style-YUTCEBZV-RWYJV575.js +287 -0
- package/dist/browser/style-YUTCEBZV-RWYJV575.js.map +7 -0
- package/dist/cjs/1-services/1-authorization.js +317 -0
- package/dist/cjs/1-services/1-authorization.js.map +7 -0
- package/dist/cjs/1-services/2-dids-tests.js +44 -0
- package/dist/cjs/1-services/2-dids-tests.js.map +7 -0
- package/dist/cjs/1-services/2-dids.js +47 -0
- package/dist/cjs/1-services/2-dids.js.map +7 -0
- package/dist/cjs/1-services/3-storage-buckets-tests.js +123 -0
- package/dist/cjs/1-services/3-storage-buckets-tests.js.map +7 -0
- package/dist/cjs/1-services/3-storage-buckets.js +148 -0
- package/dist/cjs/1-services/3-storage-buckets.js.map +7 -0
- package/dist/cjs/1-services/4-inboxes-tests.js +145 -0
- package/dist/cjs/1-services/4-inboxes-tests.js.map +7 -0
- package/dist/cjs/1-services/4-inboxes.js +539 -0
- package/dist/cjs/1-services/4-inboxes.js.map +7 -0
- package/dist/cjs/1-services/utilities.js +75 -0
- package/dist/cjs/1-services/utilities.js.map +7 -0
- package/dist/cjs/2-primitives/1-string-encoding-tests.js +50 -0
- package/dist/cjs/2-primitives/1-string-encoding-tests.js.map +7 -0
- package/dist/cjs/2-primitives/1-string-encoding.js +46 -0
- package/dist/cjs/2-primitives/1-string-encoding.js.map +7 -0
- package/dist/cjs/2-primitives/2-content-addresses-tests.js +62 -0
- package/dist/cjs/2-primitives/2-content-addresses-tests.js.map +7 -0
- package/dist/cjs/2-primitives/2-content-addresses.js +53 -0
- package/dist/cjs/2-primitives/2-content-addresses.js.map +7 -0
- package/dist/cjs/2-primitives/3-channel-attestations-tests.js +130 -0
- package/dist/cjs/2-primitives/3-channel-attestations-tests.js.map +7 -0
- package/dist/cjs/2-primitives/3-channel-attestations.js +84 -0
- package/dist/cjs/2-primitives/3-channel-attestations.js.map +7 -0
- package/dist/cjs/2-primitives/4-allowed-attestations-tests.js +96 -0
- package/dist/cjs/2-primitives/4-allowed-attestations-tests.js.map +7 -0
- package/dist/cjs/2-primitives/4-allowed-attestations.js +68 -0
- package/dist/cjs/2-primitives/4-allowed-attestations.js.map +7 -0
- package/dist/cjs/3-protocol/1-sessions.js +473 -0
- package/dist/cjs/3-protocol/1-sessions.js.map +7 -0
- package/dist/cjs/3-protocol/2-handles-tests.js +39 -0
- package/dist/cjs/3-protocol/2-handles-tests.js.map +7 -0
- package/dist/cjs/3-protocol/2-handles.js +65 -0
- package/dist/cjs/3-protocol/2-handles.js.map +7 -0
- package/dist/cjs/3-protocol/3-object-encoding-tests.js +253 -0
- package/dist/cjs/3-protocol/3-object-encoding-tests.js.map +7 -0
- package/dist/cjs/3-protocol/3-object-encoding.js +287 -0
- package/dist/cjs/3-protocol/3-object-encoding.js.map +7 -0
- package/dist/cjs/3-protocol/4-graffiti.js +937 -0
- package/dist/cjs/3-protocol/4-graffiti.js.map +7 -0
- package/dist/cjs/3-protocol/login-dialog.html.js +67 -0
- package/dist/cjs/3-protocol/login-dialog.html.js.map +7 -0
- package/dist/cjs/index.js +32 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/index.spec.js +130 -0
- package/dist/cjs/index.spec.js.map +7 -0
- package/dist/esm/1-services/1-authorization.js +304 -0
- package/dist/esm/1-services/1-authorization.js.map +7 -0
- package/dist/esm/1-services/2-dids-tests.js +24 -0
- package/dist/esm/1-services/2-dids-tests.js.map +7 -0
- package/dist/esm/1-services/2-dids.js +27 -0
- package/dist/esm/1-services/2-dids.js.map +7 -0
- package/dist/esm/1-services/3-storage-buckets-tests.js +103 -0
- package/dist/esm/1-services/3-storage-buckets-tests.js.map +7 -0
- package/dist/esm/1-services/3-storage-buckets.js +132 -0
- package/dist/esm/1-services/3-storage-buckets.js.map +7 -0
- package/dist/esm/1-services/4-inboxes-tests.js +125 -0
- package/dist/esm/1-services/4-inboxes-tests.js.map +7 -0
- package/dist/esm/1-services/4-inboxes.js +533 -0
- package/dist/esm/1-services/4-inboxes.js.map +7 -0
- package/dist/esm/1-services/utilities.js +60 -0
- package/dist/esm/1-services/utilities.js.map +7 -0
- package/dist/esm/2-primitives/1-string-encoding-tests.js +33 -0
- package/dist/esm/2-primitives/1-string-encoding-tests.js.map +7 -0
- package/dist/esm/2-primitives/1-string-encoding.js +26 -0
- package/dist/esm/2-primitives/1-string-encoding.js.map +7 -0
- package/dist/esm/2-primitives/2-content-addresses-tests.js +45 -0
- package/dist/esm/2-primitives/2-content-addresses-tests.js.map +7 -0
- package/dist/esm/2-primitives/2-content-addresses.js +33 -0
- package/dist/esm/2-primitives/2-content-addresses.js.map +7 -0
- package/dist/esm/2-primitives/3-channel-attestations-tests.js +116 -0
- package/dist/esm/2-primitives/3-channel-attestations-tests.js.map +7 -0
- package/dist/esm/2-primitives/3-channel-attestations.js +69 -0
- package/dist/esm/2-primitives/3-channel-attestations.js.map +7 -0
- package/dist/esm/2-primitives/4-allowed-attestations-tests.js +82 -0
- package/dist/esm/2-primitives/4-allowed-attestations-tests.js.map +7 -0
- package/dist/esm/2-primitives/4-allowed-attestations.js +51 -0
- package/dist/esm/2-primitives/4-allowed-attestations.js.map +7 -0
- package/dist/esm/3-protocol/1-sessions.js +465 -0
- package/dist/esm/3-protocol/1-sessions.js.map +7 -0
- package/dist/esm/3-protocol/2-handles-tests.js +19 -0
- package/dist/esm/3-protocol/2-handles-tests.js.map +7 -0
- package/dist/esm/3-protocol/2-handles.js +45 -0
- package/dist/esm/3-protocol/2-handles.js.map +7 -0
- package/dist/esm/3-protocol/3-object-encoding-tests.js +248 -0
- package/dist/esm/3-protocol/3-object-encoding-tests.js.map +7 -0
- package/dist/esm/3-protocol/3-object-encoding.js +280 -0
- package/dist/esm/3-protocol/3-object-encoding.js.map +7 -0
- package/dist/esm/3-protocol/4-graffiti.js +957 -0
- package/dist/esm/3-protocol/4-graffiti.js.map +7 -0
- package/dist/esm/3-protocol/login-dialog.html.js +47 -0
- package/dist/esm/3-protocol/login-dialog.html.js.map +7 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/index.spec.js +133 -0
- package/dist/esm/index.spec.js.map +7 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.spec.d.ts +2 -0
- package/dist/index.spec.d.ts.map +1 -0
- package/package.json +62 -0
- package/src/1-services/1-authorization.ts +399 -0
- package/src/1-services/2-dids-tests.ts +24 -0
- package/src/1-services/2-dids.ts +30 -0
- package/src/1-services/3-storage-buckets-tests.ts +121 -0
- package/src/1-services/3-storage-buckets.ts +183 -0
- package/src/1-services/4-inboxes-tests.ts +154 -0
- package/src/1-services/4-inboxes.ts +722 -0
- package/src/1-services/utilities.ts +65 -0
- package/src/2-primitives/1-string-encoding-tests.ts +33 -0
- package/src/2-primitives/1-string-encoding.ts +33 -0
- package/src/2-primitives/2-content-addresses-tests.ts +46 -0
- package/src/2-primitives/2-content-addresses.ts +42 -0
- package/src/2-primitives/3-channel-attestations-tests.ts +125 -0
- package/src/2-primitives/3-channel-attestations.ts +95 -0
- package/src/2-primitives/4-allowed-attestations-tests.ts +86 -0
- package/src/2-primitives/4-allowed-attestations.ts +69 -0
- package/src/3-protocol/1-sessions.ts +601 -0
- package/src/3-protocol/2-handles-tests.ts +17 -0
- package/src/3-protocol/2-handles.ts +60 -0
- package/src/3-protocol/3-object-encoding-tests.ts +269 -0
- package/src/3-protocol/3-object-encoding.ts +396 -0
- package/src/3-protocol/4-graffiti.ts +1265 -0
- package/src/3-protocol/login-dialog.html.ts +43 -0
- package/src/index.spec.ts +158 -0
- package/src/index.ts +16 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { assert, describe, expect, test } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
encodeObjectUrl,
|
|
4
|
+
decodeObjectUrl,
|
|
5
|
+
ObjectEncoding
|
|
6
|
+
} from "./3-object-encoding.js";
|
|
7
|
+
import { randomBytes } from "@noble/hashes/utils.js";
|
|
8
|
+
import {
|
|
9
|
+
STRING_ENCODER_METHOD_BASE64URL,
|
|
10
|
+
StringEncoder
|
|
11
|
+
} from "../2-primitives/1-string-encoding.js";
|
|
12
|
+
import { ContentAddresses } from "../2-primitives/2-content-addresses.js";
|
|
13
|
+
import { ChannelAttestations } from "../2-primitives/3-channel-attestations.js";
|
|
14
|
+
import {
|
|
15
|
+
ALLOWED_ATTESTATION_METHOD_HMAC_SHA256,
|
|
16
|
+
AllowedAttestations
|
|
17
|
+
} from "../2-primitives/4-allowed-attestations.js";
|
|
18
|
+
import {
|
|
19
|
+
encode as dagCborEncode,
|
|
20
|
+
decode as dagCborDecode
|
|
21
|
+
} from "@ipld/dag-cbor";
|
|
22
|
+
import {
|
|
23
|
+
maskGraffitiObject
|
|
24
|
+
} from "@graffiti-garden/api";
|
|
25
|
+
function objectEncodingTests() {
|
|
26
|
+
describe("object Urls", () => {
|
|
27
|
+
for (const actor of [
|
|
28
|
+
"did:plc:alsdkjfkdjf",
|
|
29
|
+
"did:web:example.com/someone",
|
|
30
|
+
"did:example:123456789abcdefghi\u{1F47B}"
|
|
31
|
+
]) {
|
|
32
|
+
test(`encodeObjectUrl encodes and decodes correctly with actor: ${actor}`, async () => {
|
|
33
|
+
const contentAddressBytes = randomBytes();
|
|
34
|
+
const contentAddress = await new StringEncoder().encode(
|
|
35
|
+
STRING_ENCODER_METHOD_BASE64URL,
|
|
36
|
+
contentAddressBytes
|
|
37
|
+
);
|
|
38
|
+
const url = encodeObjectUrl(actor, contentAddress);
|
|
39
|
+
const decoded = decodeObjectUrl(url);
|
|
40
|
+
expect(decoded.actor).toBe(actor);
|
|
41
|
+
expect(decoded.contentAddress).toBe(contentAddress);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
for (const invalidUrl of [
|
|
45
|
+
"http://example.com/not-an-object-url",
|
|
46
|
+
"graffiti:",
|
|
47
|
+
"graffiti:",
|
|
48
|
+
"graffiti:no-content-address",
|
|
49
|
+
"graffiti:too:many:parts"
|
|
50
|
+
]) {
|
|
51
|
+
test(`Invalid Graffiti URL: ${invalidUrl}`, () => {
|
|
52
|
+
expect(() => decodeObjectUrl(invalidUrl)).toThrow();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
describe("object encoding and validation", async () => {
|
|
57
|
+
const objectEncoding = new ObjectEncoding({
|
|
58
|
+
stringEncoder: new StringEncoder(),
|
|
59
|
+
allowedAttestations: new AllowedAttestations(),
|
|
60
|
+
channelAttestations: new ChannelAttestations(),
|
|
61
|
+
contentAddresses: new ContentAddresses()
|
|
62
|
+
});
|
|
63
|
+
const value = {
|
|
64
|
+
message: "Hello world!",
|
|
65
|
+
nested: {
|
|
66
|
+
foo: {
|
|
67
|
+
bar: 42
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
array: [1, "something \u{1F47B}", { key: "value" }]
|
|
71
|
+
};
|
|
72
|
+
const channels = ["channel1\u{1F47B}", "channel2"];
|
|
73
|
+
const allowed = ["did:web:noone.example.com", "did:web:someone.else.com"];
|
|
74
|
+
const actor = "did:web:someone.example.com";
|
|
75
|
+
const { allowedTickets, tags, objectBytes, object } = await objectEncoding.encode(
|
|
76
|
+
{
|
|
77
|
+
value,
|
|
78
|
+
channels,
|
|
79
|
+
allowed
|
|
80
|
+
},
|
|
81
|
+
actor
|
|
82
|
+
);
|
|
83
|
+
assert(Array.isArray(allowedTickets));
|
|
84
|
+
test("validate private", async () => {
|
|
85
|
+
await objectEncoding.validate(object, tags, objectBytes, {
|
|
86
|
+
allowedTickets
|
|
87
|
+
});
|
|
88
|
+
for (const [index, recipient] of allowed.entries()) {
|
|
89
|
+
const copy = JSON.parse(JSON.stringify(object));
|
|
90
|
+
const masked = maskGraffitiObject(copy, [], recipient);
|
|
91
|
+
await objectEncoding.validate(masked, tags, objectBytes, {
|
|
92
|
+
recipient,
|
|
93
|
+
allowedTicket: allowedTickets[index],
|
|
94
|
+
allowedIndex: index
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
test("incorrect value", async () => {
|
|
99
|
+
await expect(
|
|
100
|
+
objectEncoding.validate(
|
|
101
|
+
{
|
|
102
|
+
...object,
|
|
103
|
+
value: {
|
|
104
|
+
...object.value,
|
|
105
|
+
extra: "field"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
tags,
|
|
109
|
+
objectBytes,
|
|
110
|
+
{
|
|
111
|
+
allowedTickets
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
).rejects.toThrow();
|
|
115
|
+
});
|
|
116
|
+
test("incorrect content address", async () => {
|
|
117
|
+
const url = encodeObjectUrl(
|
|
118
|
+
actor,
|
|
119
|
+
await new StringEncoder().encode(
|
|
120
|
+
STRING_ENCODER_METHOD_BASE64URL,
|
|
121
|
+
randomBytes()
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
await expect(
|
|
125
|
+
objectEncoding.validate(
|
|
126
|
+
{
|
|
127
|
+
...object,
|
|
128
|
+
url
|
|
129
|
+
},
|
|
130
|
+
[new TextEncoder().encode(url), ...tags.slice(1)],
|
|
131
|
+
objectBytes,
|
|
132
|
+
{
|
|
133
|
+
allowedTickets
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
).rejects.toThrow();
|
|
137
|
+
});
|
|
138
|
+
test("incorrect bytes", async () => {
|
|
139
|
+
const wrongObjectBytes = randomBytes();
|
|
140
|
+
const correctContentAddress = decodeObjectUrl(object.url).contentAddress;
|
|
141
|
+
const correctContentAddressBytes = await new StringEncoder().decode(
|
|
142
|
+
correctContentAddress
|
|
143
|
+
);
|
|
144
|
+
const contentAddresses = new ContentAddresses();
|
|
145
|
+
const contentAddressMethod = await contentAddresses.getMethod(
|
|
146
|
+
correctContentAddressBytes
|
|
147
|
+
);
|
|
148
|
+
const wrongContentAddressBytes = await contentAddresses.register(
|
|
149
|
+
contentAddressMethod,
|
|
150
|
+
wrongObjectBytes
|
|
151
|
+
);
|
|
152
|
+
const wrongContentAddress = await new StringEncoder().encode(
|
|
153
|
+
STRING_ENCODER_METHOD_BASE64URL,
|
|
154
|
+
wrongContentAddressBytes
|
|
155
|
+
);
|
|
156
|
+
const wrongObjectUrl = encodeObjectUrl(actor, wrongContentAddress);
|
|
157
|
+
await expect(
|
|
158
|
+
objectEncoding.validate(
|
|
159
|
+
{
|
|
160
|
+
...object,
|
|
161
|
+
url: wrongObjectUrl
|
|
162
|
+
},
|
|
163
|
+
[new TextEncoder().encode(wrongObjectUrl), ...tags.slice(1)],
|
|
164
|
+
wrongObjectBytes,
|
|
165
|
+
{
|
|
166
|
+
allowedTickets
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
).rejects.toThrow();
|
|
170
|
+
});
|
|
171
|
+
test("incorrect format", async () => {
|
|
172
|
+
const wrongData = {
|
|
173
|
+
not: "the expected format"
|
|
174
|
+
};
|
|
175
|
+
const wrongObjectBytes = dagCborEncode(wrongData);
|
|
176
|
+
const correctContentAddress = decodeObjectUrl(object.url).contentAddress;
|
|
177
|
+
const correctContentAddressBytes = await new StringEncoder().decode(
|
|
178
|
+
correctContentAddress
|
|
179
|
+
);
|
|
180
|
+
const contentAddresses = new ContentAddresses();
|
|
181
|
+
const contentAddressMethod = await contentAddresses.getMethod(
|
|
182
|
+
correctContentAddressBytes
|
|
183
|
+
);
|
|
184
|
+
const wrongContentAddressBytes = await contentAddresses.register(
|
|
185
|
+
contentAddressMethod,
|
|
186
|
+
wrongObjectBytes
|
|
187
|
+
);
|
|
188
|
+
const wrongContentAddress = await new StringEncoder().encode(
|
|
189
|
+
STRING_ENCODER_METHOD_BASE64URL,
|
|
190
|
+
wrongContentAddressBytes
|
|
191
|
+
);
|
|
192
|
+
const wrongObjectUrl = encodeObjectUrl(actor, wrongContentAddress);
|
|
193
|
+
await expect(
|
|
194
|
+
objectEncoding.validate(
|
|
195
|
+
{
|
|
196
|
+
...object,
|
|
197
|
+
url: wrongObjectUrl
|
|
198
|
+
},
|
|
199
|
+
[new TextEncoder().encode(wrongObjectUrl), ...tags.slice(1)],
|
|
200
|
+
wrongObjectBytes,
|
|
201
|
+
{
|
|
202
|
+
allowedTickets
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
).rejects.toThrow();
|
|
206
|
+
});
|
|
207
|
+
test("missing allowed tickets", async () => {
|
|
208
|
+
await expect(
|
|
209
|
+
objectEncoding.validate(object, tags, objectBytes)
|
|
210
|
+
).rejects.toThrow();
|
|
211
|
+
});
|
|
212
|
+
test("wrong allowed tickets", async () => {
|
|
213
|
+
const wrongAllowedTickets = await Promise.all(
|
|
214
|
+
allowedTickets.map(
|
|
215
|
+
async (ticket) => (await new AllowedAttestations().attest(
|
|
216
|
+
ALLOWED_ATTESTATION_METHOD_HMAC_SHA256,
|
|
217
|
+
"did:web:not-the-right.actor"
|
|
218
|
+
)).ticket
|
|
219
|
+
)
|
|
220
|
+
);
|
|
221
|
+
await expect(
|
|
222
|
+
objectEncoding.validate(object, tags, objectBytes, {
|
|
223
|
+
allowedTickets: wrongAllowedTickets
|
|
224
|
+
})
|
|
225
|
+
).rejects.toThrow();
|
|
226
|
+
});
|
|
227
|
+
test("wrong recipients", async () => {
|
|
228
|
+
const wrongRecipients = allowed.map((recipient) => recipient + "-wrong");
|
|
229
|
+
await expect(
|
|
230
|
+
objectEncoding.validate(
|
|
231
|
+
{
|
|
232
|
+
...object,
|
|
233
|
+
allowed: wrongRecipients
|
|
234
|
+
},
|
|
235
|
+
tags,
|
|
236
|
+
objectBytes,
|
|
237
|
+
{
|
|
238
|
+
allowedTickets
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
).rejects.toThrow();
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
export {
|
|
246
|
+
objectEncodingTests
|
|
247
|
+
};
|
|
248
|
+
//# sourceMappingURL=3-object-encoding-tests.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/3-protocol/3-object-encoding-tests.ts"],
|
|
4
|
+
"sourcesContent": ["import { assert, describe, expect, test } from \"vitest\";\n\nimport {\n encodeObjectUrl,\n decodeObjectUrl,\n ObjectEncoding,\n} from \"./3-object-encoding.js\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\nimport {\n STRING_ENCODER_METHOD_BASE64URL,\n StringEncoder,\n} from \"../2-primitives/1-string-encoding.js\";\nimport { ContentAddresses } from \"../2-primitives/2-content-addresses.js\";\nimport { ChannelAttestations } from \"../2-primitives/3-channel-attestations.js\";\nimport {\n ALLOWED_ATTESTATION_METHOD_HMAC_SHA256,\n AllowedAttestations,\n} from \"../2-primitives/4-allowed-attestations.js\";\nimport {\n encode as dagCborEncode,\n decode as dagCborDecode,\n} from \"@ipld/dag-cbor\";\nimport {\n maskGraffitiObject,\n type GraffitiObjectBase,\n} from \"@graffiti-garden/api\";\n\nexport function objectEncodingTests() {\n describe(\"object Urls\", () => {\n for (const actor of [\n \"did:plc:alsdkjfkdjf\",\n \"did:web:example.com/someone\",\n \"did:example:123456789abcdefghi\uD83D\uDC7B\",\n ]) {\n test(`encodeObjectUrl encodes and decodes correctly with actor: ${actor}`, async () => {\n const contentAddressBytes = randomBytes();\n const contentAddress = await new StringEncoder().encode(\n STRING_ENCODER_METHOD_BASE64URL,\n contentAddressBytes,\n );\n\n const url = encodeObjectUrl(actor, contentAddress);\n const decoded = decodeObjectUrl(url);\n expect(decoded.actor).toBe(actor);\n expect(decoded.contentAddress).toBe(contentAddress);\n });\n }\n\n for (const invalidUrl of [\n \"http://example.com/not-an-object-url\",\n \"graffiti:\",\n \"graffiti:\",\n \"graffiti:no-content-address\",\n \"graffiti:too:many:parts\",\n ]) {\n test(`Invalid Graffiti URL: ${invalidUrl}`, () => {\n expect(() => decodeObjectUrl(invalidUrl)).toThrow();\n });\n }\n });\n\n describe(\"object encoding and validation\", async () => {\n const objectEncoding = new ObjectEncoding({\n stringEncoder: new StringEncoder(),\n allowedAttestations: new AllowedAttestations(),\n channelAttestations: new ChannelAttestations(),\n contentAddresses: new ContentAddresses(),\n });\n\n const value = {\n message: \"Hello world!\",\n nested: {\n foo: {\n bar: 42,\n },\n },\n array: [1, \"something \uD83D\uDC7B\", { key: \"value\" }],\n };\n const channels = [\"channel1\uD83D\uDC7B\", \"channel2\"];\n const allowed = [\"did:web:noone.example.com\", \"did:web:someone.else.com\"];\n const actor = \"did:web:someone.example.com\";\n\n const { allowedTickets, tags, objectBytes, object } =\n await objectEncoding.encode<{}>(\n {\n value,\n channels,\n allowed,\n },\n actor,\n );\n assert(Array.isArray(allowedTickets));\n\n test(\"validate private\", async () => {\n await objectEncoding.validate(object, tags, objectBytes, {\n allowedTickets,\n });\n\n for (const [index, recipient] of allowed.entries()) {\n const copy = JSON.parse(JSON.stringify(object)) as GraffitiObjectBase;\n const masked = maskGraffitiObject(copy, [], recipient);\n await objectEncoding.validate(masked, tags, objectBytes, {\n recipient: recipient,\n allowedTicket: allowedTickets[index],\n allowedIndex: index,\n });\n }\n });\n\n test(\"incorrect value\", async () => {\n await expect(\n objectEncoding.validate(\n {\n ...object,\n value: {\n ...object.value,\n extra: \"field\",\n },\n },\n tags,\n objectBytes,\n {\n allowedTickets,\n },\n ),\n ).rejects.toThrow();\n });\n\n test(\"incorrect content address\", async () => {\n const url = encodeObjectUrl(\n actor,\n await new StringEncoder().encode(\n STRING_ENCODER_METHOD_BASE64URL,\n randomBytes(),\n ),\n );\n\n await expect(\n objectEncoding.validate(\n {\n ...object,\n url,\n },\n [new TextEncoder().encode(url), ...tags.slice(1)],\n objectBytes,\n {\n allowedTickets,\n },\n ),\n ).rejects.toThrow();\n });\n\n test(\"incorrect bytes\", async () => {\n const wrongObjectBytes = randomBytes();\n const correctContentAddress = decodeObjectUrl(object.url).contentAddress;\n const correctContentAddressBytes = await new StringEncoder().decode(\n correctContentAddress,\n );\n const contentAddresses = new ContentAddresses();\n const contentAddressMethod = await contentAddresses.getMethod(\n correctContentAddressBytes,\n );\n const wrongContentAddressBytes = await contentAddresses.register(\n contentAddressMethod,\n wrongObjectBytes,\n );\n const wrongContentAddress = await new StringEncoder().encode(\n STRING_ENCODER_METHOD_BASE64URL,\n wrongContentAddressBytes,\n );\n const wrongObjectUrl = encodeObjectUrl(actor, wrongContentAddress);\n\n await expect(\n objectEncoding.validate(\n {\n ...object,\n url: wrongObjectUrl,\n },\n [new TextEncoder().encode(wrongObjectUrl), ...tags.slice(1)],\n wrongObjectBytes,\n {\n allowedTickets,\n },\n ),\n ).rejects.toThrow();\n });\n\n test(\"incorrect format\", async () => {\n const wrongData = {\n not: \"the expected format\",\n };\n const wrongObjectBytes = dagCborEncode(wrongData);\n const correctContentAddress = decodeObjectUrl(object.url).contentAddress;\n const correctContentAddressBytes = await new StringEncoder().decode(\n correctContentAddress,\n );\n const contentAddresses = new ContentAddresses();\n const contentAddressMethod = await contentAddresses.getMethod(\n correctContentAddressBytes,\n );\n const wrongContentAddressBytes = await contentAddresses.register(\n contentAddressMethod,\n wrongObjectBytes,\n );\n const wrongContentAddress = await new StringEncoder().encode(\n STRING_ENCODER_METHOD_BASE64URL,\n wrongContentAddressBytes,\n );\n const wrongObjectUrl = encodeObjectUrl(actor, wrongContentAddress);\n await expect(\n objectEncoding.validate(\n {\n ...object,\n url: wrongObjectUrl,\n },\n [new TextEncoder().encode(wrongObjectUrl), ...tags.slice(1)],\n wrongObjectBytes,\n {\n allowedTickets,\n },\n ),\n ).rejects.toThrow();\n });\n\n test(\"missing allowed tickets\", async () => {\n await expect(\n objectEncoding.validate(object, tags, objectBytes),\n ).rejects.toThrow();\n });\n\n test(\"wrong allowed tickets\", async () => {\n const wrongAllowedTickets = await Promise.all(\n allowedTickets.map(\n async (ticket) =>\n (\n await new AllowedAttestations().attest(\n ALLOWED_ATTESTATION_METHOD_HMAC_SHA256,\n \"did:web:not-the-right.actor\",\n )\n ).ticket,\n ),\n );\n\n await expect(\n objectEncoding.validate(object, tags, objectBytes, {\n allowedTickets: wrongAllowedTickets,\n }),\n ).rejects.toThrow();\n });\n\n test(\"wrong recipients\", async () => {\n const wrongRecipients = allowed.map((recipient) => recipient + \"-wrong\");\n\n await expect(\n objectEncoding.validate(\n {\n ...object,\n allowed: wrongRecipients,\n },\n tags,\n objectBytes,\n {\n allowedTickets,\n },\n ),\n ).rejects.toThrow();\n });\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONTENT_ADDRESS_METHOD_SHA256
|
|
3
|
+
} from "../2-primitives/2-content-addresses";
|
|
4
|
+
import { randomBytes } from "@noble/hashes/utils.js";
|
|
5
|
+
import {
|
|
6
|
+
encode as dagCborEncode,
|
|
7
|
+
decode as dagCborDecode
|
|
8
|
+
} from "@ipld/dag-cbor";
|
|
9
|
+
import {
|
|
10
|
+
array,
|
|
11
|
+
custom,
|
|
12
|
+
looseObject,
|
|
13
|
+
optional,
|
|
14
|
+
strictObject
|
|
15
|
+
} from "zod/mini";
|
|
16
|
+
import { CHANNEL_ATTESTATION_METHOD_SHA256_ED25519 } from "../2-primitives/3-channel-attestations";
|
|
17
|
+
import { ALLOWED_ATTESTATION_METHOD_HMAC_SHA256 } from "../2-primitives/4-allowed-attestations";
|
|
18
|
+
import {
|
|
19
|
+
STRING_ENCODER_METHOD_BASE64URL
|
|
20
|
+
} from "../2-primitives/1-string-encoding";
|
|
21
|
+
const MAX_OBJECT_SIZE_BYTES = 32 * 1024;
|
|
22
|
+
class ObjectEncoding {
|
|
23
|
+
constructor(primitives) {
|
|
24
|
+
this.primitives = primitives;
|
|
25
|
+
}
|
|
26
|
+
async encode(partialObject, actor) {
|
|
27
|
+
partialObject = cleanUndefined(partialObject);
|
|
28
|
+
const channelAttestationAndPublicIds = await Promise.all(
|
|
29
|
+
partialObject.channels.map(
|
|
30
|
+
(channel) => this.primitives.channelAttestations.attest(
|
|
31
|
+
// TODO: get this from the DID document of the actor
|
|
32
|
+
CHANNEL_ATTESTATION_METHOD_SHA256_ED25519,
|
|
33
|
+
actor,
|
|
34
|
+
channel
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
const channelAttestations = channelAttestationAndPublicIds.map(
|
|
39
|
+
(c) => c.attestation
|
|
40
|
+
);
|
|
41
|
+
const channelPublicIds = channelAttestationAndPublicIds.map(
|
|
42
|
+
(c) => c.channelPublicId
|
|
43
|
+
);
|
|
44
|
+
const objectData = {
|
|
45
|
+
[VALUE_PROPERTY]: partialObject.value,
|
|
46
|
+
[CHANNEL_ATTESTATIONS_PROPERTY]: channelAttestations,
|
|
47
|
+
[NONCE_PROPERTY]: randomBytes(32)
|
|
48
|
+
};
|
|
49
|
+
let allowedTickets = void 0;
|
|
50
|
+
if (Array.isArray(partialObject.allowed)) {
|
|
51
|
+
const allowedAttestations = await Promise.all(
|
|
52
|
+
partialObject.allowed.map(
|
|
53
|
+
async (allowedActor) => this.primitives.allowedAttestations.attest(
|
|
54
|
+
// TODO: get this from the DID document of the actor
|
|
55
|
+
ALLOWED_ATTESTATION_METHOD_HMAC_SHA256,
|
|
56
|
+
allowedActor
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
objectData[ALLOWED_ATTESTATIONS_PROPERTY] = allowedAttestations.map(
|
|
61
|
+
(a) => a.attestation
|
|
62
|
+
);
|
|
63
|
+
allowedTickets = allowedAttestations.map((a) => a.ticket);
|
|
64
|
+
}
|
|
65
|
+
const objectBytes = dagCborEncode(objectData);
|
|
66
|
+
if (objectBytes.byteLength > MAX_OBJECT_SIZE_BYTES) {
|
|
67
|
+
throw new Error("The object is too large");
|
|
68
|
+
}
|
|
69
|
+
const objectContentAddressBytes = await this.primitives.contentAddresses.register(
|
|
70
|
+
// TODO: get this from the DID document of the actor
|
|
71
|
+
CONTENT_ADDRESS_METHOD_SHA256,
|
|
72
|
+
objectBytes
|
|
73
|
+
);
|
|
74
|
+
const objectContentAddress = await this.primitives.stringEncoder.encode(
|
|
75
|
+
STRING_ENCODER_METHOD_BASE64URL,
|
|
76
|
+
objectContentAddressBytes
|
|
77
|
+
);
|
|
78
|
+
const objectUrl = encodeObjectUrl(actor, objectContentAddress);
|
|
79
|
+
const tags = [new TextEncoder().encode(objectUrl), ...channelPublicIds];
|
|
80
|
+
const object = {
|
|
81
|
+
value: partialObject.value,
|
|
82
|
+
channels: partialObject.channels,
|
|
83
|
+
url: objectUrl,
|
|
84
|
+
actor,
|
|
85
|
+
...partialObject.allowed ? {
|
|
86
|
+
allowed: partialObject.allowed
|
|
87
|
+
} : {}
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
object,
|
|
91
|
+
tags,
|
|
92
|
+
objectBytes,
|
|
93
|
+
allowedTickets
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async validate(object, tags, objectBytes, privateObjectInfo) {
|
|
97
|
+
if (objectBytes.byteLength > MAX_OBJECT_SIZE_BYTES) {
|
|
98
|
+
throw new Error("Object is too big");
|
|
99
|
+
}
|
|
100
|
+
const { actor, contentAddress } = decodeObjectUrl(object.url);
|
|
101
|
+
if (actor !== object.actor) {
|
|
102
|
+
throw new Error("Object actor does not match URL actor");
|
|
103
|
+
}
|
|
104
|
+
const objectUrlTag = tags.at(0);
|
|
105
|
+
if (!objectUrlTag) {
|
|
106
|
+
throw new Error("No object URL tag");
|
|
107
|
+
}
|
|
108
|
+
if (new TextDecoder().decode(objectUrlTag) !== object.url) {
|
|
109
|
+
throw new Error("Object URL tag does not match object URL");
|
|
110
|
+
}
|
|
111
|
+
const channelPublicIds = tags.slice(1);
|
|
112
|
+
const contentAddressBytes = await this.primitives.stringEncoder.decode(contentAddress);
|
|
113
|
+
const contentAddressMethod = await this.primitives.contentAddresses.getMethod(contentAddressBytes);
|
|
114
|
+
const expectedContentAddress = await this.primitives.contentAddresses.register(
|
|
115
|
+
contentAddressMethod,
|
|
116
|
+
objectBytes
|
|
117
|
+
);
|
|
118
|
+
if (expectedContentAddress.length !== contentAddressBytes.length || !expectedContentAddress.every((b, i) => b === contentAddressBytes[i])) {
|
|
119
|
+
throw new Error("Content address is invalid");
|
|
120
|
+
}
|
|
121
|
+
const objectDataUnknown = dagCborDecode(objectBytes);
|
|
122
|
+
const objectData = ObjectDataSchema.parse(objectDataUnknown);
|
|
123
|
+
const value = objectData[VALUE_PROPERTY];
|
|
124
|
+
const channelAttestations = objectData[CHANNEL_ATTESTATIONS_PROPERTY];
|
|
125
|
+
const allowedAttestations = objectData[ALLOWED_ATTESTATIONS_PROPERTY];
|
|
126
|
+
const valueBytes = dagCborEncode(value);
|
|
127
|
+
const expectedValueBytes = dagCborEncode(object.value);
|
|
128
|
+
if (valueBytes.length !== expectedValueBytes.length || !valueBytes.every((b, i) => b === expectedValueBytes[i])) {
|
|
129
|
+
throw new Error("Object value does not match storage value");
|
|
130
|
+
}
|
|
131
|
+
if (channelAttestations.length !== channelPublicIds.length) {
|
|
132
|
+
throw new Error("Not as many channel attestations and public ids");
|
|
133
|
+
}
|
|
134
|
+
for (const [index, attestation] of channelAttestations.entries()) {
|
|
135
|
+
const channelPublicId = channelPublicIds[index];
|
|
136
|
+
const isValid = await this.primitives.channelAttestations.validate(
|
|
137
|
+
attestation,
|
|
138
|
+
actor,
|
|
139
|
+
channelPublicId
|
|
140
|
+
);
|
|
141
|
+
if (!isValid) {
|
|
142
|
+
throw new Error("Invalid channel attestation");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (object.channels.length) {
|
|
146
|
+
if (object.channels.length !== channelPublicIds.length) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
"Number of claimed channels does not match attestations/public IDs"
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const channelAttestationMethod = await this.primitives.channelAttestations.getMethod(
|
|
152
|
+
channelPublicIds[0]
|
|
153
|
+
);
|
|
154
|
+
const expectedChannelPublicIds = await Promise.all(
|
|
155
|
+
object.channels.map(
|
|
156
|
+
(channel) => this.primitives.channelAttestations.register(
|
|
157
|
+
channelAttestationMethod,
|
|
158
|
+
channel
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
);
|
|
162
|
+
for (const [
|
|
163
|
+
index,
|
|
164
|
+
expectedPublicId
|
|
165
|
+
] of expectedChannelPublicIds.entries()) {
|
|
166
|
+
const actualPublicId = channelPublicIds[index];
|
|
167
|
+
if (expectedPublicId.length !== actualPublicId.length || !expectedPublicId.every((b, i) => b === actualPublicId[i])) {
|
|
168
|
+
throw new Error("Channel public id does not match expected");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (privateObjectInfo) {
|
|
173
|
+
if (!allowedAttestations) {
|
|
174
|
+
throw new Error("Object is public but thought to be private");
|
|
175
|
+
}
|
|
176
|
+
let recipients;
|
|
177
|
+
let allowedTickets;
|
|
178
|
+
let attestations;
|
|
179
|
+
if ("recipient" in privateObjectInfo) {
|
|
180
|
+
recipients = [privateObjectInfo.recipient];
|
|
181
|
+
allowedTickets = [privateObjectInfo.allowedTicket];
|
|
182
|
+
attestations = allowedAttestations.filter(
|
|
183
|
+
(_, i) => i === privateObjectInfo.allowedIndex
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
recipients = [...object.allowed ?? []];
|
|
187
|
+
allowedTickets = privateObjectInfo.allowedTickets;
|
|
188
|
+
attestations = allowedAttestations;
|
|
189
|
+
}
|
|
190
|
+
if (recipients.length !== object.allowed?.length) {
|
|
191
|
+
throw new Error("Recipient count does not match object allowed list");
|
|
192
|
+
}
|
|
193
|
+
if (!recipients.every((r) => object.allowed?.includes(r))) {
|
|
194
|
+
throw new Error("Recipient not in object allowed list");
|
|
195
|
+
}
|
|
196
|
+
for (const [index, recipient] of recipients.entries()) {
|
|
197
|
+
const allowedTicket = allowedTickets.at(index);
|
|
198
|
+
const allowedAttestation = attestations.at(index);
|
|
199
|
+
if (!allowedTicket) {
|
|
200
|
+
throw new Error("Missing allowed ticket for recipient");
|
|
201
|
+
}
|
|
202
|
+
if (!allowedAttestation) {
|
|
203
|
+
throw new Error("Missing allowed attestation for recipient");
|
|
204
|
+
}
|
|
205
|
+
const isValid = await this.primitives.allowedAttestations.validate(
|
|
206
|
+
allowedAttestation,
|
|
207
|
+
recipient,
|
|
208
|
+
allowedTicket
|
|
209
|
+
);
|
|
210
|
+
if (!isValid) {
|
|
211
|
+
throw new Error("Invalid allowed attestation for recipient");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} else if (allowedAttestations) {
|
|
215
|
+
throw new Error("Object is private but no recipient info provided");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const VALUE_PROPERTY = "v";
|
|
220
|
+
const CHANNEL_ATTESTATIONS_PROPERTY = "c";
|
|
221
|
+
const ALLOWED_ATTESTATIONS_PROPERTY = "a";
|
|
222
|
+
const NONCE_PROPERTY = "n";
|
|
223
|
+
const Uint8ArraySchema = custom(
|
|
224
|
+
(v) => v instanceof Uint8Array
|
|
225
|
+
);
|
|
226
|
+
const ObjectDataSchema = strictObject({
|
|
227
|
+
[VALUE_PROPERTY]: looseObject({}),
|
|
228
|
+
[CHANNEL_ATTESTATIONS_PROPERTY]: array(Uint8ArraySchema),
|
|
229
|
+
[ALLOWED_ATTESTATIONS_PROPERTY]: optional(array(Uint8ArraySchema)),
|
|
230
|
+
[NONCE_PROPERTY]: Uint8ArraySchema
|
|
231
|
+
});
|
|
232
|
+
const GRAFFITI_OBJECT_URL_PREFIX = "graffiti:";
|
|
233
|
+
function encodeObjectUrlComponent(value) {
|
|
234
|
+
const replaced = value.replace(/:/g, "!").replace(/\//g, "~");
|
|
235
|
+
return encodeURIComponent(replaced);
|
|
236
|
+
}
|
|
237
|
+
function decodeObjectUrlComponent(value) {
|
|
238
|
+
const decoded = decodeURIComponent(value);
|
|
239
|
+
return decoded.replace(/!/g, ":").replace(/~/g, "/");
|
|
240
|
+
}
|
|
241
|
+
function encodeObjectUrl(actor, contentAddress) {
|
|
242
|
+
return `${GRAFFITI_OBJECT_URL_PREFIX}${encodeObjectUrlComponent(actor)}:${encodeObjectUrlComponent(contentAddress)}`;
|
|
243
|
+
}
|
|
244
|
+
function decodeObjectUrl(objectUrl) {
|
|
245
|
+
if (!objectUrl.startsWith(GRAFFITI_OBJECT_URL_PREFIX)) {
|
|
246
|
+
throw new Error("Invalid object URL");
|
|
247
|
+
}
|
|
248
|
+
const rest = objectUrl.slice(GRAFFITI_OBJECT_URL_PREFIX.length);
|
|
249
|
+
const parts = rest.split(":");
|
|
250
|
+
if (parts.length !== 2) {
|
|
251
|
+
throw new Error("Invalid object URL format");
|
|
252
|
+
}
|
|
253
|
+
const [actor, contentAddress] = parts;
|
|
254
|
+
return {
|
|
255
|
+
actor: decodeObjectUrlComponent(actor),
|
|
256
|
+
contentAddress: decodeObjectUrlComponent(contentAddress)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function cleanUndefined(value) {
|
|
260
|
+
if (value === void 0) return null;
|
|
261
|
+
if (Array.isArray(value)) {
|
|
262
|
+
return value.map(cleanUndefined);
|
|
263
|
+
}
|
|
264
|
+
if (typeof value === "object") {
|
|
265
|
+
return Object.fromEntries(
|
|
266
|
+
Object.entries(value).filter(([, v]) => v !== void 0).map(([k, v]) => [k, cleanUndefined(v)])
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
return value;
|
|
270
|
+
}
|
|
271
|
+
export {
|
|
272
|
+
GRAFFITI_OBJECT_URL_PREFIX,
|
|
273
|
+
MAX_OBJECT_SIZE_BYTES,
|
|
274
|
+
ObjectEncoding,
|
|
275
|
+
decodeObjectUrl,
|
|
276
|
+
decodeObjectUrlComponent,
|
|
277
|
+
encodeObjectUrl,
|
|
278
|
+
encodeObjectUrlComponent
|
|
279
|
+
};
|
|
280
|
+
//# sourceMappingURL=3-object-encoding.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/3-protocol/3-object-encoding.ts"],
|
|
4
|
+
"sourcesContent": ["import type { JSONSchema } from \"json-schema-to-ts\";\nimport type {\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiPostObject,\n} from \"@graffiti-garden/api\";\nimport type { ChannelAttestations } from \"../2-primitives/3-channel-attestations\";\nimport type { AllowedAttestations } from \"../2-primitives/4-allowed-attestations\";\nimport {\n CONTENT_ADDRESS_METHOD_SHA256,\n type ContentAddresses,\n} from \"../2-primitives/2-content-addresses\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\nimport {\n encode as dagCborEncode,\n decode as dagCborDecode,\n} from \"@ipld/dag-cbor\";\nimport {\n type infer as infer_,\n array,\n custom,\n looseObject,\n optional,\n strictObject,\n} from \"zod/mini\";\nimport { CHANNEL_ATTESTATION_METHOD_SHA256_ED25519 } from \"../2-primitives/3-channel-attestations\";\nimport { ALLOWED_ATTESTATION_METHOD_HMAC_SHA256 } from \"../2-primitives/4-allowed-attestations\";\nimport {\n STRING_ENCODER_METHOD_BASE64URL,\n type StringEncoder,\n} from \"../2-primitives/1-string-encoding\";\n\n// Objects have a max size of 32kb\n// If each channel and allowed actor takes 32 bytes\n// of space (i.e. they are hashed with 256 bit security)\n// then this means that the combined number of channels\n// and recipients of object has cannot exceed one thousand.\n// This seems like a reasonable limit and on par with\n// signal's group chat limit of 1000\nexport const MAX_OBJECT_SIZE_BYTES = 32 * 1024;\n\nexport class ObjectEncoding {\n constructor(\n protected readonly primitives: {\n readonly stringEncoder: StringEncoder;\n readonly channelAttestations: ChannelAttestations;\n readonly allowedAttestations: AllowedAttestations;\n readonly contentAddresses: ContentAddresses;\n },\n ) {}\n\n async encode<Schema extends JSONSchema>(\n partialObject: GraffitiPostObject<Schema>,\n actor: string,\n ): Promise<{\n object: GraffitiObject<Schema>;\n tags: Uint8Array[];\n objectBytes: Uint8Array;\n allowedTickets: Uint8Array[] | undefined;\n }> {\n // Clean out any undefineds\n partialObject = cleanUndefined(partialObject);\n\n // Create a verifiable attestation that the actor\n // knows the included channels without\n // directly revealing any channel to anyone who doesn't\n // know the channel already\n const channelAttestationAndPublicIds = await Promise.all(\n partialObject.channels.map((channel) =>\n this.primitives.channelAttestations.attest(\n // TODO: get this from the DID document of the actor\n CHANNEL_ATTESTATION_METHOD_SHA256_ED25519,\n actor,\n channel,\n ),\n ),\n );\n const channelAttestations = channelAttestationAndPublicIds.map(\n (c) => c.attestation,\n );\n const channelPublicIds = channelAttestationAndPublicIds.map(\n (c) => c.channelPublicId,\n );\n\n const objectData: infer_<typeof ObjectDataSchema> = {\n [VALUE_PROPERTY]: partialObject.value,\n [CHANNEL_ATTESTATIONS_PROPERTY]: channelAttestations,\n [NONCE_PROPERTY]: randomBytes(32),\n };\n\n let allowedTickets: Uint8Array[] | undefined = undefined;\n\n // If the object is private...\n if (Array.isArray(partialObject.allowed)) {\n // Create an attestation that the object's allowed list\n // includes the given actors, without revealing the\n // presence of an actor on the list to anyone except\n // that actor themselves. Each actor will receive a\n // \"ticket\" that they can use to verify their own membership\n // on the allowed list.\n const allowedAttestations = await Promise.all(\n partialObject.allowed.map(async (allowedActor) =>\n this.primitives.allowedAttestations.attest(\n // TODO: get this from the DID document of the actor\n ALLOWED_ATTESTATION_METHOD_HMAC_SHA256,\n allowedActor,\n ),\n ),\n );\n objectData[ALLOWED_ATTESTATIONS_PROPERTY] = allowedAttestations.map(\n (a) => a.attestation,\n );\n allowedTickets = allowedAttestations.map((a) => a.ticket);\n }\n\n // Encode the mixed JSON/binary data using CBOR\n const objectBytes = dagCborEncode(objectData);\n if (objectBytes.byteLength > MAX_OBJECT_SIZE_BYTES) {\n throw new Error(\"The object is too large\");\n }\n\n // Compute a public identifier (hash) of the object data\n const objectContentAddressBytes =\n await this.primitives.contentAddresses.register(\n // TODO: get this from the DID document of the actor\n CONTENT_ADDRESS_METHOD_SHA256,\n objectBytes,\n );\n const objectContentAddress = await this.primitives.stringEncoder.encode(\n STRING_ENCODER_METHOD_BASE64URL,\n objectContentAddressBytes,\n );\n // Use it to compute the object's URL\n const objectUrl = encodeObjectUrl(actor, objectContentAddress);\n\n const tags = [new TextEncoder().encode(objectUrl), ...channelPublicIds];\n\n const object: GraffitiObject<Schema> = {\n value: partialObject.value,\n channels: partialObject.channels,\n url: objectUrl,\n actor,\n ...(partialObject.allowed\n ? {\n allowed: partialObject.allowed,\n }\n : {}),\n } as GraffitiObject<Schema>;\n\n // Return object URL and allowed secrets\n return {\n object,\n tags,\n objectBytes,\n allowedTickets,\n };\n }\n\n async validate(\n object: GraffitiObjectBase,\n tags: Uint8Array[],\n objectBytes: Uint8Array,\n privateObjectInfo?:\n | {\n recipient: string;\n allowedTicket: Uint8Array;\n allowedIndex: number;\n }\n | {\n allowedTickets: Uint8Array[];\n },\n ): Promise<void> {\n if (objectBytes.byteLength > MAX_OBJECT_SIZE_BYTES) {\n throw new Error(\"Object is too big\");\n }\n const { actor, contentAddress } = decodeObjectUrl(object.url);\n if (actor !== object.actor) {\n throw new Error(\"Object actor does not match URL actor\");\n }\n\n const objectUrlTag = tags.at(0);\n if (!objectUrlTag) {\n throw new Error(\"No object URL tag\");\n }\n if (new TextDecoder().decode(objectUrlTag) !== object.url) {\n throw new Error(\"Object URL tag does not match object URL\");\n }\n const channelPublicIds = tags.slice(1);\n\n // Make sure the object content address matches the object content\n const contentAddressBytes =\n await this.primitives.stringEncoder.decode(contentAddress);\n const contentAddressMethod =\n await this.primitives.contentAddresses.getMethod(contentAddressBytes);\n const expectedContentAddress =\n await this.primitives.contentAddresses.register(\n contentAddressMethod,\n objectBytes,\n );\n if (\n expectedContentAddress.length !== contentAddressBytes.length ||\n !expectedContentAddress.every((b, i) => b === contentAddressBytes[i])\n ) {\n throw new Error(\"Content address is invalid\");\n }\n\n // Convert the raw object data from CBOR\n // back to a javascript object\n const objectDataUnknown = dagCborDecode(objectBytes);\n const objectData = ObjectDataSchema.parse(objectDataUnknown);\n\n // And extract the values\n const value = objectData[VALUE_PROPERTY];\n const channelAttestations = objectData[CHANNEL_ATTESTATIONS_PROPERTY];\n const allowedAttestations = objectData[ALLOWED_ATTESTATIONS_PROPERTY];\n\n // Validate that the object's value matches\n const valueBytes = dagCborEncode(value);\n const expectedValueBytes = dagCborEncode(object.value);\n if (\n valueBytes.length !== expectedValueBytes.length ||\n !valueBytes.every((b, i) => b === expectedValueBytes[i])\n ) {\n throw new Error(\"Object value does not match storage value\");\n }\n\n // Validate the object's channels\n if (channelAttestations.length !== channelPublicIds.length) {\n throw new Error(\"Not as many channel attestations and public ids\");\n }\n for (const [index, attestation] of channelAttestations.entries()) {\n const channelPublicId = channelPublicIds[index];\n const isValid = await this.primitives.channelAttestations.validate(\n attestation,\n actor,\n channelPublicId,\n );\n if (!isValid) {\n throw new Error(\"Invalid channel attestation\");\n }\n }\n if (object.channels.length) {\n // If any channels are included, they all must be included\n if (object.channels.length !== channelPublicIds.length) {\n throw new Error(\n \"Number of claimed channels does not match attestations/public IDs\",\n );\n }\n const channelAttestationMethod =\n await this.primitives.channelAttestations.getMethod(\n channelPublicIds[0],\n );\n const expectedChannelPublicIds = await Promise.all(\n object.channels.map((channel) =>\n this.primitives.channelAttestations.register(\n channelAttestationMethod,\n channel,\n ),\n ),\n );\n for (const [\n index,\n expectedPublicId,\n ] of expectedChannelPublicIds.entries()) {\n const actualPublicId = channelPublicIds[index];\n if (\n expectedPublicId.length !== actualPublicId.length ||\n !expectedPublicId.every((b, i) => b === actualPublicId[i])\n ) {\n throw new Error(\"Channel public id does not match expected\");\n }\n }\n }\n\n // Validate the recipient\n if (privateObjectInfo) {\n if (!allowedAttestations) {\n throw new Error(\"Object is public but thought to be private\");\n }\n\n let recipients: string[];\n let allowedTickets: Uint8Array[];\n let attestations: Uint8Array[];\n if (\"recipient\" in privateObjectInfo) {\n recipients = [privateObjectInfo.recipient];\n allowedTickets = [privateObjectInfo.allowedTicket];\n attestations = allowedAttestations.filter(\n (_, i) => i === privateObjectInfo.allowedIndex,\n );\n } else {\n recipients = [...(object.allowed ?? [])];\n allowedTickets = privateObjectInfo.allowedTickets;\n attestations = allowedAttestations;\n }\n\n // All recipients must be in the allowed list\n if (recipients.length !== object.allowed?.length) {\n throw new Error(\"Recipient count does not match object allowed list\");\n }\n if (!recipients.every((r) => object.allowed?.includes(r))) {\n throw new Error(\"Recipient not in object allowed list\");\n }\n\n for (const [index, recipient] of recipients.entries()) {\n const allowedTicket = allowedTickets.at(index);\n const allowedAttestation = attestations.at(index);\n if (!allowedTicket) {\n throw new Error(\"Missing allowed ticket for recipient\");\n }\n if (!allowedAttestation) {\n throw new Error(\"Missing allowed attestation for recipient\");\n }\n const isValid = await this.primitives.allowedAttestations.validate(\n allowedAttestation,\n recipient,\n allowedTicket,\n );\n\n if (!isValid) {\n throw new Error(\"Invalid allowed attestation for recipient\");\n }\n }\n } else if (allowedAttestations) {\n throw new Error(\"Object is private but no recipient info provided\");\n }\n }\n}\n\n// A compact data representation of the object data\nconst VALUE_PROPERTY = \"v\";\nconst CHANNEL_ATTESTATIONS_PROPERTY = \"c\";\nconst ALLOWED_ATTESTATIONS_PROPERTY = \"a\";\nconst NONCE_PROPERTY = \"n\";\n\nconst Uint8ArraySchema = custom<Uint8Array>(\n (v): v is Uint8Array => v instanceof Uint8Array,\n);\n\nconst ObjectDataSchema = strictObject({\n [VALUE_PROPERTY]: looseObject({}),\n [CHANNEL_ATTESTATIONS_PROPERTY]: array(Uint8ArraySchema),\n [ALLOWED_ATTESTATIONS_PROPERTY]: optional(array(Uint8ArraySchema)),\n [NONCE_PROPERTY]: Uint8ArraySchema,\n});\n\nexport const GRAFFITI_OBJECT_URL_PREFIX = \"graffiti:\";\n\n// Methods to encode and decode object URLs\nexport function encodeObjectUrlComponent(value: string) {\n const replaced = value.replace(/:/g, \"!\").replace(/\\//g, \"~\");\n return encodeURIComponent(replaced);\n}\nexport function decodeObjectUrlComponent(value: string) {\n const decoded = decodeURIComponent(value);\n return decoded.replace(/!/g, \":\").replace(/~/g, \"/\");\n}\nexport function encodeObjectUrl(actor: string, contentAddress: string) {\n return `${GRAFFITI_OBJECT_URL_PREFIX}${encodeObjectUrlComponent(actor)}:${encodeObjectUrlComponent(contentAddress)}`;\n}\nexport function decodeObjectUrl(objectUrl: string) {\n if (!objectUrl.startsWith(GRAFFITI_OBJECT_URL_PREFIX)) {\n throw new Error(\"Invalid object URL\");\n }\n\n const rest = objectUrl.slice(GRAFFITI_OBJECT_URL_PREFIX.length);\n const parts = rest.split(\":\");\n\n if (parts.length !== 2) {\n throw new Error(\"Invalid object URL format\");\n }\n\n const [actor, contentAddress] = parts;\n\n return {\n actor: decodeObjectUrlComponent(actor),\n contentAddress: decodeObjectUrlComponent(contentAddress),\n };\n}\n\nfunction cleanUndefined(value: any): any {\n if (value === undefined) return null;\n\n if (Array.isArray(value)) {\n return value.map(cleanUndefined);\n }\n\n if (typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => [k, cleanUndefined(v)]),\n );\n }\n\n return value;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|