cojson 0.17.9 → 0.17.11
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +18 -0
- package/dist/coValueCore/SessionMap.d.ts +45 -0
- package/dist/coValueCore/SessionMap.d.ts.map +1 -0
- package/dist/coValueCore/SessionMap.js +118 -0
- package/dist/coValueCore/SessionMap.js.map +1 -0
- package/dist/coValueCore/coValueCore.d.ts +10 -4
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +55 -68
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +15 -19
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +24 -87
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/account.d.ts +4 -0
- package/dist/coValues/account.d.ts.map +1 -1
- package/dist/coValues/account.js +24 -4
- package/dist/coValues/account.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +6 -2
- package/dist/coValues/group.js.map +1 -1
- package/dist/crypto/PureJSCrypto.d.ts +31 -3
- package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
- package/dist/crypto/PureJSCrypto.js +115 -0
- package/dist/crypto/PureJSCrypto.js.map +1 -1
- package/dist/crypto/WasmCrypto.d.ts +23 -4
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js +44 -2
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/crypto/crypto.d.ts +17 -1
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/crypto/crypto.js.map +1 -1
- package/dist/localNode.d.ts +1 -0
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +10 -5
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.d.ts +17 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +55 -49
- package/dist/sync.js.map +1 -1
- package/dist/tests/PureJSCrypto.test.d.ts +2 -0
- package/dist/tests/PureJSCrypto.test.d.ts.map +1 -0
- package/dist/tests/PureJSCrypto.test.js +102 -0
- package/dist/tests/PureJSCrypto.test.js.map +1 -0
- package/dist/tests/WasmCrypto.test.d.ts +2 -0
- package/dist/tests/WasmCrypto.test.d.ts.map +1 -0
- package/dist/tests/WasmCrypto.test.js +88 -0
- package/dist/tests/WasmCrypto.test.js.map +1 -0
- package/dist/tests/coValueCore.test.js +62 -187
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coreWasm.test.d.ts +2 -0
- package/dist/tests/coreWasm.test.d.ts.map +1 -0
- package/dist/tests/coreWasm.test.js +80 -0
- package/dist/tests/coreWasm.test.js.map +1 -0
- package/dist/tests/group.addMember.test.js +6 -11
- package/dist/tests/group.addMember.test.js.map +1 -1
- package/dist/tests/sync.load.test.js +40 -1
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.test.js +1 -1
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +3 -0
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +4 -1
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +3 -3
- package/src/coValueCore/SessionMap.ts +229 -0
- package/src/coValueCore/coValueCore.ts +106 -121
- package/src/coValueCore/verifiedState.ts +61 -132
- package/src/coValues/account.ts +28 -4
- package/src/coValues/group.ts +10 -2
- package/src/crypto/PureJSCrypto.ts +206 -2
- package/src/crypto/WasmCrypto.ts +95 -4
- package/src/crypto/crypto.ts +38 -1
- package/src/localNode.ts +18 -10
- package/src/permissions.ts +17 -1
- package/src/sync.ts +63 -59
- package/src/tests/PureJSCrypto.test.ts +153 -0
- package/src/tests/WasmCrypto.test.ts +128 -0
- package/src/tests/coValueCore.test.ts +81 -293
- package/src/tests/coreWasm.test.ts +142 -0
- package/src/tests/group.addMember.test.ts +69 -63
- package/src/tests/sync.load.test.ts +52 -0
- package/src/tests/sync.test.ts +0 -2
- package/src/tests/testUtils.ts +9 -1
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
Encrypted,
|
|
11
11
|
Hash,
|
|
12
12
|
KeyID,
|
|
13
|
+
KeySecret,
|
|
13
14
|
Signature,
|
|
14
15
|
SignerID,
|
|
15
16
|
StreamingHash,
|
|
@@ -21,6 +22,8 @@ import { PermissionsDef as RulesetDef } from "../permissions.js";
|
|
|
21
22
|
import { CoValueKnownState, NewContentMessage } from "../sync.js";
|
|
22
23
|
import { InvalidHashError, InvalidSignatureError } from "./coValueCore.js";
|
|
23
24
|
import { TryAddTransactionsError } from "./coValueCore.js";
|
|
25
|
+
import { SessionLog, SessionMap } from "./SessionMap.js";
|
|
26
|
+
import { ControlledAccountOrAgent } from "../coValues/account.js";
|
|
24
27
|
|
|
25
28
|
export type CoValueHeader = {
|
|
26
29
|
type: AnyRawCoValue["type"];
|
|
@@ -48,20 +51,11 @@ export type TrustingTransaction = {
|
|
|
48
51
|
|
|
49
52
|
export type Transaction = PrivateTransaction | TrustingTransaction;
|
|
50
53
|
|
|
51
|
-
type SessionLog = {
|
|
52
|
-
readonly transactions: Transaction[];
|
|
53
|
-
streamingHash?: StreamingHash;
|
|
54
|
-
readonly signatureAfter: { [txIdx: number]: Signature | undefined };
|
|
55
|
-
lastSignature: Signature;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export type ValidatedSessions = Map<SessionID, SessionLog>;
|
|
59
|
-
|
|
60
54
|
export class VerifiedState {
|
|
61
55
|
readonly id: RawCoID;
|
|
62
56
|
readonly crypto: CryptoProvider;
|
|
63
57
|
readonly header: CoValueHeader;
|
|
64
|
-
readonly sessions:
|
|
58
|
+
readonly sessions: SessionMap;
|
|
65
59
|
private _cachedKnownState?: CoValueKnownState;
|
|
66
60
|
private _cachedNewContentSinceEmpty: NewContentMessage[] | undefined;
|
|
67
61
|
private streamingKnownState?: CoValueKnownState["sessions"];
|
|
@@ -71,171 +65,98 @@ export class VerifiedState {
|
|
|
71
65
|
id: RawCoID,
|
|
72
66
|
crypto: CryptoProvider,
|
|
73
67
|
header: CoValueHeader,
|
|
74
|
-
sessions
|
|
68
|
+
sessions?: SessionMap,
|
|
75
69
|
streamingKnownState?: CoValueKnownState["sessions"],
|
|
76
70
|
) {
|
|
77
71
|
this.id = id;
|
|
78
72
|
this.crypto = crypto;
|
|
79
73
|
this.header = header;
|
|
80
|
-
this.sessions = sessions;
|
|
74
|
+
this.sessions = sessions ?? new SessionMap(id, crypto);
|
|
81
75
|
this.streamingKnownState = streamingKnownState
|
|
82
76
|
? { ...streamingKnownState }
|
|
83
77
|
: undefined;
|
|
84
78
|
}
|
|
85
79
|
|
|
86
80
|
clone(): VerifiedState {
|
|
87
|
-
// do a deep clone, including the sessions
|
|
88
|
-
const clonedSessions = new Map();
|
|
89
|
-
for (let [sessionID, sessionLog] of this.sessions) {
|
|
90
|
-
clonedSessions.set(sessionID, {
|
|
91
|
-
lastSignature: sessionLog.lastSignature,
|
|
92
|
-
streamingHash: sessionLog.streamingHash?.clone(),
|
|
93
|
-
signatureAfter: { ...sessionLog.signatureAfter },
|
|
94
|
-
transactions: sessionLog.transactions.slice(),
|
|
95
|
-
} satisfies SessionLog);
|
|
96
|
-
}
|
|
97
81
|
return new VerifiedState(
|
|
98
82
|
this.id,
|
|
99
83
|
this.crypto,
|
|
100
84
|
this.header,
|
|
101
|
-
|
|
102
|
-
this.streamingKnownState,
|
|
85
|
+
this.sessions.clone(),
|
|
86
|
+
this.streamingKnownState ? { ...this.streamingKnownState } : undefined,
|
|
103
87
|
);
|
|
104
88
|
}
|
|
105
89
|
|
|
106
90
|
tryAddTransactions(
|
|
107
91
|
sessionID: SessionID,
|
|
108
|
-
signerID: SignerID,
|
|
92
|
+
signerID: SignerID | undefined,
|
|
109
93
|
newTransactions: Transaction[],
|
|
110
|
-
givenExpectedNewHash: Hash | undefined,
|
|
111
94
|
newSignature: Signature,
|
|
112
95
|
skipVerify: boolean = false,
|
|
113
|
-
givenNewStreamingHash?: StreamingHash,
|
|
114
96
|
): Result<true, TryAddTransactionsError> {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
} else {
|
|
123
|
-
const { expectedNewHash, newStreamingHash } = this.expectedNewHashAfter(
|
|
124
|
-
sessionID,
|
|
125
|
-
newTransactions,
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
if (givenExpectedNewHash && givenExpectedNewHash !== expectedNewHash) {
|
|
129
|
-
return err({
|
|
130
|
-
type: "InvalidHash",
|
|
131
|
-
id: this.id,
|
|
132
|
-
expectedNewHash,
|
|
133
|
-
givenExpectedNewHash,
|
|
134
|
-
} satisfies InvalidHashError);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (!this.crypto.verify(newSignature, expectedNewHash, signerID)) {
|
|
138
|
-
return err({
|
|
139
|
-
type: "InvalidSignature",
|
|
140
|
-
id: this.id,
|
|
141
|
-
newSignature,
|
|
142
|
-
sessionID,
|
|
143
|
-
signerID,
|
|
144
|
-
} satisfies InvalidSignatureError);
|
|
145
|
-
}
|
|
97
|
+
const result = this.sessions.addTransaction(
|
|
98
|
+
sessionID,
|
|
99
|
+
signerID,
|
|
100
|
+
newTransactions,
|
|
101
|
+
newSignature,
|
|
102
|
+
skipVerify,
|
|
103
|
+
);
|
|
146
104
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
newSignature,
|
|
151
|
-
newStreamingHash,
|
|
152
|
-
);
|
|
105
|
+
if (result.isOk()) {
|
|
106
|
+
this._cachedNewContentSinceEmpty = undefined;
|
|
107
|
+
this._cachedKnownState = undefined;
|
|
153
108
|
}
|
|
154
109
|
|
|
155
|
-
return
|
|
110
|
+
return result;
|
|
156
111
|
}
|
|
157
112
|
|
|
158
|
-
|
|
159
|
-
|
|
113
|
+
makeNewTrustingTransaction(
|
|
114
|
+
sessionID: SessionID,
|
|
115
|
+
signerAgent: ControlledAccountOrAgent,
|
|
116
|
+
changes: JsonValue[],
|
|
117
|
+
) {
|
|
118
|
+
const result = this.sessions.makeNewTrustingTransaction(
|
|
119
|
+
sessionID,
|
|
120
|
+
signerAgent,
|
|
121
|
+
changes,
|
|
122
|
+
);
|
|
160
123
|
|
|
161
|
-
|
|
124
|
+
this._cachedNewContentSinceEmpty = undefined;
|
|
125
|
+
this._cachedKnownState = undefined;
|
|
162
126
|
|
|
163
|
-
return
|
|
164
|
-
(max, idx) => Math.max(max, parseInt(idx)),
|
|
165
|
-
-1,
|
|
166
|
-
);
|
|
127
|
+
return result;
|
|
167
128
|
}
|
|
168
129
|
|
|
169
|
-
|
|
130
|
+
makeNewPrivateTransaction(
|
|
170
131
|
sessionID: SessionID,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
132
|
+
signerAgent: ControlledAccountOrAgent,
|
|
133
|
+
changes: JsonValue[],
|
|
134
|
+
keyID: KeyID,
|
|
135
|
+
keySecret: KeySecret,
|
|
174
136
|
) {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const signatureAfter = sessionLog?.signatureAfter ?? {};
|
|
183
|
-
const lastInbetweenSignatureIdx =
|
|
184
|
-
this.getLastSignatureCheckpoint(sessionID);
|
|
185
|
-
|
|
186
|
-
const sizeOfTxsSinceLastInbetweenSignature = transactions
|
|
187
|
-
.slice(lastInbetweenSignatureIdx + 1)
|
|
188
|
-
.reduce((sum, tx) => sum + getTransactionSize(tx), 0);
|
|
189
|
-
|
|
190
|
-
if (exceedsRecommendedSize(sizeOfTxsSinceLastInbetweenSignature)) {
|
|
191
|
-
signatureAfter[transactions.length - 1] = newSignature;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
this.sessions.set(sessionID, {
|
|
195
|
-
transactions,
|
|
196
|
-
streamingHash: newStreamingHash,
|
|
197
|
-
lastSignature: newSignature,
|
|
198
|
-
signatureAfter: signatureAfter,
|
|
199
|
-
});
|
|
137
|
+
const result = this.sessions.makeNewPrivateTransaction(
|
|
138
|
+
sessionID,
|
|
139
|
+
signerAgent,
|
|
140
|
+
changes,
|
|
141
|
+
keyID,
|
|
142
|
+
keySecret,
|
|
143
|
+
);
|
|
200
144
|
|
|
201
145
|
this._cachedNewContentSinceEmpty = undefined;
|
|
202
146
|
this._cachedKnownState = undefined;
|
|
147
|
+
|
|
148
|
+
return result;
|
|
203
149
|
}
|
|
204
150
|
|
|
205
|
-
|
|
206
|
-
sessionID: SessionID,
|
|
207
|
-
newTransactions: Transaction[],
|
|
208
|
-
): { expectedNewHash: Hash; newStreamingHash: StreamingHash } {
|
|
151
|
+
getLastSignatureCheckpoint(sessionID: SessionID): number {
|
|
209
152
|
const sessionLog = this.sessions.get(sessionID);
|
|
210
153
|
|
|
211
|
-
if (!sessionLog?.
|
|
212
|
-
const streamingHash = new StreamingHash(this.crypto);
|
|
213
|
-
const oldTransactions = sessionLog?.transactions ?? [];
|
|
214
|
-
|
|
215
|
-
for (const transaction of oldTransactions) {
|
|
216
|
-
streamingHash.update(transaction);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
for (const transaction of newTransactions) {
|
|
220
|
-
streamingHash.update(transaction);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
expectedNewHash: streamingHash.digest(),
|
|
225
|
-
newStreamingHash: streamingHash,
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const streamingHash = sessionLog.streamingHash.clone();
|
|
230
|
-
|
|
231
|
-
for (const transaction of newTransactions) {
|
|
232
|
-
streamingHash.update(transaction);
|
|
233
|
-
}
|
|
154
|
+
if (!sessionLog?.signatureAfter) return -1;
|
|
234
155
|
|
|
235
|
-
return
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
156
|
+
return Object.keys(sessionLog.signatureAfter).reduce(
|
|
157
|
+
(max, idx) => Math.max(max, parseInt(idx)),
|
|
158
|
+
-1,
|
|
159
|
+
);
|
|
239
160
|
}
|
|
240
161
|
|
|
241
162
|
newContentSince(
|
|
@@ -424,6 +345,14 @@ export class VerifiedState {
|
|
|
424
345
|
sessions,
|
|
425
346
|
};
|
|
426
347
|
}
|
|
348
|
+
|
|
349
|
+
decryptTransaction(
|
|
350
|
+
sessionID: SessionID,
|
|
351
|
+
txIndex: number,
|
|
352
|
+
keySecret: KeySecret,
|
|
353
|
+
): JsonValue[] | undefined {
|
|
354
|
+
return this.sessions.decryptTransaction(sessionID, txIndex, keySecret);
|
|
355
|
+
}
|
|
427
356
|
}
|
|
428
357
|
|
|
429
358
|
function getNextKnownSignatureIdx(
|
package/src/coValues/account.ts
CHANGED
|
@@ -92,6 +92,10 @@ export class ControlledAccount implements ControlledAccountOrAgent {
|
|
|
92
92
|
account: RawAccount<AccountMeta>;
|
|
93
93
|
agentSecret: AgentSecret;
|
|
94
94
|
_cachedCurrentAgentID: AgentID | undefined;
|
|
95
|
+
_cachedCurrentSignerID: SignerID | undefined;
|
|
96
|
+
_cachedCurrentSignerSecret: SignerSecret | undefined;
|
|
97
|
+
_cachedCurrentSealerID: SealerID | undefined;
|
|
98
|
+
_cachedCurrentSealerSecret: SealerSecret | undefined;
|
|
95
99
|
crypto: CryptoProvider;
|
|
96
100
|
|
|
97
101
|
constructor(account: RawAccount<AccountMeta>, agentSecret: AgentSecret) {
|
|
@@ -114,19 +118,39 @@ export class ControlledAccount implements ControlledAccountOrAgent {
|
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
currentSignerID() {
|
|
117
|
-
|
|
121
|
+
if (this._cachedCurrentSignerID) {
|
|
122
|
+
return this._cachedCurrentSignerID;
|
|
123
|
+
}
|
|
124
|
+
const signerID = this.crypto.getAgentSignerID(this.currentAgentID());
|
|
125
|
+
this._cachedCurrentSignerID = signerID;
|
|
126
|
+
return signerID;
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
currentSignerSecret(): SignerSecret {
|
|
121
|
-
|
|
130
|
+
if (this._cachedCurrentSignerSecret) {
|
|
131
|
+
return this._cachedCurrentSignerSecret;
|
|
132
|
+
}
|
|
133
|
+
const signerSecret = this.crypto.getAgentSignerSecret(this.agentSecret);
|
|
134
|
+
this._cachedCurrentSignerSecret = signerSecret;
|
|
135
|
+
return signerSecret;
|
|
122
136
|
}
|
|
123
137
|
|
|
124
138
|
currentSealerID() {
|
|
125
|
-
|
|
139
|
+
if (this._cachedCurrentSealerID) {
|
|
140
|
+
return this._cachedCurrentSealerID;
|
|
141
|
+
}
|
|
142
|
+
const sealerID = this.crypto.getAgentSealerID(this.currentAgentID());
|
|
143
|
+
this._cachedCurrentSealerID = sealerID;
|
|
144
|
+
return sealerID;
|
|
126
145
|
}
|
|
127
146
|
|
|
128
147
|
currentSealerSecret(): SealerSecret {
|
|
129
|
-
|
|
148
|
+
if (this._cachedCurrentSealerSecret) {
|
|
149
|
+
return this._cachedCurrentSealerSecret;
|
|
150
|
+
}
|
|
151
|
+
const sealerSecret = this.crypto.getAgentSealerSecret(this.agentSecret);
|
|
152
|
+
this._cachedCurrentSealerSecret = sealerSecret;
|
|
153
|
+
return sealerSecret;
|
|
130
154
|
}
|
|
131
155
|
}
|
|
132
156
|
|
package/src/coValues/group.ts
CHANGED
|
@@ -370,16 +370,24 @@ export class RawGroup<
|
|
|
370
370
|
if (role === "writeOnly" || role === "writeOnlyInvite") {
|
|
371
371
|
const previousRole = this.get(memberKey);
|
|
372
372
|
|
|
373
|
-
|
|
373
|
+
if (
|
|
374
|
+
previousRole === "admin" &&
|
|
375
|
+
memberKey !== this.core.node.getCurrentAgent().id
|
|
376
|
+
) {
|
|
377
|
+
throw new Error(
|
|
378
|
+
"Administrators cannot demote other administrators in a group",
|
|
379
|
+
);
|
|
380
|
+
}
|
|
374
381
|
|
|
375
382
|
if (
|
|
376
383
|
previousRole === "reader" ||
|
|
377
384
|
previousRole === "writer" ||
|
|
378
385
|
previousRole === "admin"
|
|
379
386
|
) {
|
|
380
|
-
this.rotateReadKey();
|
|
387
|
+
this.rotateReadKey(memberKey);
|
|
381
388
|
}
|
|
382
389
|
|
|
390
|
+
this.set(memberKey, role, "trusting");
|
|
383
391
|
this.internalCreateWriteOnlyKeyForMember(memberKey, agent);
|
|
384
392
|
} else {
|
|
385
393
|
const currentReadKey = this.getCurrentReadKey();
|
|
@@ -3,23 +3,32 @@ import { ed25519, x25519 } from "@noble/curves/ed25519";
|
|
|
3
3
|
import { blake3 } from "@noble/hashes/blake3";
|
|
4
4
|
import { base58 } from "@scure/base";
|
|
5
5
|
import { base64URLtoBytes, bytesToBase64url } from "../base64url.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
PrivateTransaction,
|
|
8
|
+
Transaction,
|
|
9
|
+
TrustingTransaction,
|
|
10
|
+
} from "../coValueCore/verifiedState.js";
|
|
11
|
+
import { RawCoID, SessionID, TransactionID } from "../ids.js";
|
|
7
12
|
import { Stringified, stableStringify } from "../jsonStringify.js";
|
|
8
13
|
import { JsonValue } from "../jsonValue.js";
|
|
9
14
|
import { logger } from "../logger.js";
|
|
10
15
|
import {
|
|
11
16
|
CryptoProvider,
|
|
12
17
|
Encrypted,
|
|
18
|
+
KeyID,
|
|
13
19
|
KeySecret,
|
|
14
20
|
Sealed,
|
|
15
21
|
SealerID,
|
|
16
22
|
SealerSecret,
|
|
23
|
+
SessionLogImpl,
|
|
17
24
|
Signature,
|
|
18
25
|
SignerID,
|
|
19
26
|
SignerSecret,
|
|
27
|
+
StreamingHash,
|
|
20
28
|
textDecoder,
|
|
21
29
|
textEncoder,
|
|
22
30
|
} from "./crypto.js";
|
|
31
|
+
import { ControlledAccountOrAgent } from "../coValues/account.js";
|
|
23
32
|
|
|
24
33
|
type Blake3State = ReturnType<typeof blake3.create>;
|
|
25
34
|
|
|
@@ -67,7 +76,7 @@ export class PureJSCrypto extends CryptoProvider<Blake3State> {
|
|
|
67
76
|
return this.blake3HashOnce(input).slice(0, 24);
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
|
|
79
|
+
generateJsonNonce(material: JsonValue): Uint8Array {
|
|
71
80
|
return this.generateNonce(textEncoder.encode(stableStringify(material)));
|
|
72
81
|
}
|
|
73
82
|
|
|
@@ -199,4 +208,199 @@ export class PureJSCrypto extends CryptoProvider<Blake3State> {
|
|
|
199
208
|
return undefined;
|
|
200
209
|
}
|
|
201
210
|
}
|
|
211
|
+
|
|
212
|
+
createSessionLog(
|
|
213
|
+
coID: RawCoID,
|
|
214
|
+
sessionID: SessionID,
|
|
215
|
+
signerID?: SignerID,
|
|
216
|
+
): SessionLogImpl {
|
|
217
|
+
return new PureJSSessionLog(coID, sessionID, signerID, this);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export class PureJSSessionLog implements SessionLogImpl {
|
|
222
|
+
transactions: string[] = [];
|
|
223
|
+
lastSignature: Signature | undefined;
|
|
224
|
+
streamingHash: Blake3State;
|
|
225
|
+
|
|
226
|
+
constructor(
|
|
227
|
+
private readonly coID: RawCoID,
|
|
228
|
+
private readonly sessionID: SessionID,
|
|
229
|
+
private readonly signerID: SignerID | undefined,
|
|
230
|
+
private readonly crypto: PureJSCrypto,
|
|
231
|
+
) {
|
|
232
|
+
this.streamingHash = this.crypto.emptyBlake3State();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
clone(): SessionLogImpl {
|
|
236
|
+
const newLog = new PureJSSessionLog(
|
|
237
|
+
this.coID,
|
|
238
|
+
this.sessionID,
|
|
239
|
+
this.signerID,
|
|
240
|
+
this.crypto,
|
|
241
|
+
);
|
|
242
|
+
newLog.transactions = this.transactions.slice();
|
|
243
|
+
newLog.lastSignature = this.lastSignature;
|
|
244
|
+
newLog.streamingHash = this.crypto.cloneBlake3State(this.streamingHash);
|
|
245
|
+
return newLog;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
tryAdd(
|
|
249
|
+
transactions: Transaction[],
|
|
250
|
+
newSignature: Signature,
|
|
251
|
+
skipVerify: boolean,
|
|
252
|
+
): void {
|
|
253
|
+
this.internalTryAdd(
|
|
254
|
+
transactions.map((tx) => stableStringify(tx)),
|
|
255
|
+
newSignature,
|
|
256
|
+
skipVerify,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
internalTryAdd(
|
|
261
|
+
transactions: string[],
|
|
262
|
+
newSignature: Signature,
|
|
263
|
+
skipVerify: boolean,
|
|
264
|
+
) {
|
|
265
|
+
if (!skipVerify) {
|
|
266
|
+
if (!this.signerID) {
|
|
267
|
+
throw new Error("Tried to add transactions without signer ID");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const checkHasher = this.crypto.cloneBlake3State(this.streamingHash);
|
|
271
|
+
|
|
272
|
+
for (const tx of transactions) {
|
|
273
|
+
checkHasher.update(textEncoder.encode(tx));
|
|
274
|
+
}
|
|
275
|
+
const newHash = checkHasher.digest();
|
|
276
|
+
const newHashEncoded = `hash_z${base58.encode(newHash)}`;
|
|
277
|
+
|
|
278
|
+
if (!this.crypto.verify(newSignature, newHashEncoded, this.signerID)) {
|
|
279
|
+
throw new Error("Signature verification failed");
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
for (const tx of transactions) {
|
|
284
|
+
this.crypto.blake3IncrementalUpdate(
|
|
285
|
+
this.streamingHash,
|
|
286
|
+
textEncoder.encode(tx),
|
|
287
|
+
);
|
|
288
|
+
this.transactions.push(tx);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
this.lastSignature = newSignature;
|
|
292
|
+
|
|
293
|
+
return newSignature;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
expectedHashAfter(transactionsJson: string[]): string {
|
|
297
|
+
const hasher = this.crypto.cloneBlake3State(this.streamingHash);
|
|
298
|
+
for (const tx of transactionsJson) {
|
|
299
|
+
hasher.update(textEncoder.encode(tx));
|
|
300
|
+
}
|
|
301
|
+
const newHash = hasher.digest();
|
|
302
|
+
return `hash_z${base58.encode(newHash)}`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
internalAddNewTransaction(
|
|
306
|
+
transaction: string,
|
|
307
|
+
signerAgent: ControlledAccountOrAgent,
|
|
308
|
+
) {
|
|
309
|
+
this.crypto.blake3IncrementalUpdate(
|
|
310
|
+
this.streamingHash,
|
|
311
|
+
textEncoder.encode(transaction),
|
|
312
|
+
);
|
|
313
|
+
const newHash = this.crypto.blake3DigestForState(this.streamingHash);
|
|
314
|
+
const newHashEncoded = `hash_z${base58.encode(newHash)}`;
|
|
315
|
+
const signature = this.crypto.sign(
|
|
316
|
+
signerAgent.currentSignerSecret(),
|
|
317
|
+
newHashEncoded,
|
|
318
|
+
);
|
|
319
|
+
this.transactions.push(transaction);
|
|
320
|
+
this.lastSignature = signature;
|
|
321
|
+
|
|
322
|
+
return signature;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
addNewPrivateTransaction(
|
|
326
|
+
signerAgent: ControlledAccountOrAgent,
|
|
327
|
+
changes: JsonValue[],
|
|
328
|
+
keyID: KeyID,
|
|
329
|
+
keySecret: KeySecret,
|
|
330
|
+
madeAt: number,
|
|
331
|
+
): { signature: Signature; transaction: PrivateTransaction } {
|
|
332
|
+
const encryptedChanges = this.crypto.encrypt(changes, keySecret, {
|
|
333
|
+
in: this.coID,
|
|
334
|
+
tx: { sessionID: this.sessionID, txIndex: this.transactions.length },
|
|
335
|
+
});
|
|
336
|
+
const tx = {
|
|
337
|
+
encryptedChanges: encryptedChanges,
|
|
338
|
+
madeAt: madeAt,
|
|
339
|
+
privacy: "private",
|
|
340
|
+
keyUsed: keyID,
|
|
341
|
+
} satisfies Transaction;
|
|
342
|
+
const signature = this.internalAddNewTransaction(
|
|
343
|
+
stableStringify(tx),
|
|
344
|
+
signerAgent,
|
|
345
|
+
);
|
|
346
|
+
return {
|
|
347
|
+
signature: signature as Signature,
|
|
348
|
+
transaction: tx,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
addNewTrustingTransaction(
|
|
353
|
+
signerAgent: ControlledAccountOrAgent,
|
|
354
|
+
changes: JsonValue[],
|
|
355
|
+
madeAt: number,
|
|
356
|
+
): { signature: Signature; transaction: TrustingTransaction } {
|
|
357
|
+
const tx = {
|
|
358
|
+
changes: stableStringify(changes),
|
|
359
|
+
madeAt: madeAt,
|
|
360
|
+
privacy: "trusting",
|
|
361
|
+
} satisfies Transaction;
|
|
362
|
+
const signature = this.internalAddNewTransaction(
|
|
363
|
+
stableStringify(tx),
|
|
364
|
+
signerAgent,
|
|
365
|
+
);
|
|
366
|
+
return {
|
|
367
|
+
signature: signature as Signature,
|
|
368
|
+
transaction: tx,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
decryptNextTransactionChangesJson(
|
|
373
|
+
txIndex: number,
|
|
374
|
+
keySecret: KeySecret,
|
|
375
|
+
): string {
|
|
376
|
+
const txJson = this.transactions[txIndex];
|
|
377
|
+
if (!txJson) {
|
|
378
|
+
throw new Error("Transaction not found");
|
|
379
|
+
}
|
|
380
|
+
const tx = JSON.parse(txJson) as Transaction;
|
|
381
|
+
if (tx.privacy === "private") {
|
|
382
|
+
const nOnceMaterial = {
|
|
383
|
+
in: this.coID,
|
|
384
|
+
tx: { sessionID: this.sessionID, txIndex: txIndex },
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const nOnce = this.crypto.generateJsonNonce(nOnceMaterial);
|
|
388
|
+
|
|
389
|
+
const ciphertext = base64URLtoBytes(
|
|
390
|
+
tx.encryptedChanges.substring("encrypted_U".length),
|
|
391
|
+
);
|
|
392
|
+
const keySecretBytes = base58.decode(
|
|
393
|
+
keySecret.substring("keySecret_z".length),
|
|
394
|
+
);
|
|
395
|
+
const plaintext = xsalsa20(keySecretBytes, nOnce, ciphertext);
|
|
396
|
+
|
|
397
|
+
return textDecoder.decode(plaintext);
|
|
398
|
+
} else {
|
|
399
|
+
return tx.changes;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
free(): void {
|
|
404
|
+
// no-op
|
|
405
|
+
}
|
|
202
406
|
}
|