cojson 0.17.9 → 0.17.10
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 +7 -0
- package/dist/coValueCore/SessionMap.d.ts +44 -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 +1 -0
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +40 -61
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +14 -18
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +23 -86
- 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/crypto/PureJSCrypto.d.ts +31 -3
- package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
- package/dist/crypto/PureJSCrypto.js +112 -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/tests/PureJSCrypto.test.d.ts +2 -0
- package/dist/tests/PureJSCrypto.test.d.ts.map +1 -0
- package/dist/tests/PureJSCrypto.test.js +88 -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/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 +230 -0
- package/src/coValueCore/coValueCore.ts +66 -91
- package/src/coValueCore/verifiedState.ts +60 -129
- package/src/coValues/account.ts +28 -4
- package/src/crypto/PureJSCrypto.ts +202 -2
- package/src/crypto/WasmCrypto.ts +95 -4
- package/src/crypto/crypto.ts +38 -1
- package/src/localNode.ts +18 -10
- package/src/tests/PureJSCrypto.test.ts +130 -0
- package/src/tests/WasmCrypto.test.ts +130 -0
- package/src/tests/coValueCore.test.ts +84 -292
- package/src/tests/coreWasm.test.ts +142 -0
- 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,35 +65,25 @@ 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
|
|
|
@@ -112,130 +96,69 @@ export class VerifiedState {
|
|
|
112
96
|
skipVerify: boolean = false,
|
|
113
97
|
givenNewStreamingHash?: StreamingHash,
|
|
114
98
|
): 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
|
-
}
|
|
99
|
+
const result = this.sessions.addTransaction(
|
|
100
|
+
sessionID,
|
|
101
|
+
signerID,
|
|
102
|
+
newTransactions,
|
|
103
|
+
newSignature,
|
|
104
|
+
skipVerify,
|
|
105
|
+
);
|
|
146
106
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
newSignature,
|
|
151
|
-
newStreamingHash,
|
|
152
|
-
);
|
|
107
|
+
if (result.isOk()) {
|
|
108
|
+
this._cachedNewContentSinceEmpty = undefined;
|
|
109
|
+
this._cachedKnownState = undefined;
|
|
153
110
|
}
|
|
154
111
|
|
|
155
|
-
return
|
|
112
|
+
return result;
|
|
156
113
|
}
|
|
157
114
|
|
|
158
|
-
|
|
159
|
-
|
|
115
|
+
makeNewTrustingTransaction(
|
|
116
|
+
sessionID: SessionID,
|
|
117
|
+
signerAgent: ControlledAccountOrAgent,
|
|
118
|
+
changes: JsonValue[],
|
|
119
|
+
) {
|
|
120
|
+
const result = this.sessions.makeNewTrustingTransaction(
|
|
121
|
+
sessionID,
|
|
122
|
+
signerAgent,
|
|
123
|
+
changes,
|
|
124
|
+
);
|
|
160
125
|
|
|
161
|
-
|
|
126
|
+
this._cachedNewContentSinceEmpty = undefined;
|
|
127
|
+
this._cachedKnownState = undefined;
|
|
162
128
|
|
|
163
|
-
return
|
|
164
|
-
(max, idx) => Math.max(max, parseInt(idx)),
|
|
165
|
-
-1,
|
|
166
|
-
);
|
|
129
|
+
return result;
|
|
167
130
|
}
|
|
168
131
|
|
|
169
|
-
|
|
132
|
+
makeNewPrivateTransaction(
|
|
170
133
|
sessionID: SessionID,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
134
|
+
signerAgent: ControlledAccountOrAgent,
|
|
135
|
+
changes: JsonValue[],
|
|
136
|
+
keyID: KeyID,
|
|
137
|
+
keySecret: KeySecret,
|
|
174
138
|
) {
|
|
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
|
-
});
|
|
139
|
+
const result = this.sessions.makeNewPrivateTransaction(
|
|
140
|
+
sessionID,
|
|
141
|
+
signerAgent,
|
|
142
|
+
changes,
|
|
143
|
+
keyID,
|
|
144
|
+
keySecret,
|
|
145
|
+
);
|
|
200
146
|
|
|
201
147
|
this._cachedNewContentSinceEmpty = undefined;
|
|
202
148
|
this._cachedKnownState = undefined;
|
|
149
|
+
|
|
150
|
+
return result;
|
|
203
151
|
}
|
|
204
152
|
|
|
205
|
-
|
|
206
|
-
sessionID: SessionID,
|
|
207
|
-
newTransactions: Transaction[],
|
|
208
|
-
): { expectedNewHash: Hash; newStreamingHash: StreamingHash } {
|
|
153
|
+
getLastSignatureCheckpoint(sessionID: SessionID): number {
|
|
209
154
|
const sessionLog = this.sessions.get(sessionID);
|
|
210
155
|
|
|
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
|
-
}
|
|
156
|
+
if (!sessionLog?.signatureAfter) return -1;
|
|
234
157
|
|
|
235
|
-
return
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
158
|
+
return Object.keys(sessionLog.signatureAfter).reduce(
|
|
159
|
+
(max, idx) => Math.max(max, parseInt(idx)),
|
|
160
|
+
-1,
|
|
161
|
+
);
|
|
239
162
|
}
|
|
240
163
|
|
|
241
164
|
newContentSince(
|
|
@@ -424,6 +347,14 @@ export class VerifiedState {
|
|
|
424
347
|
sessions,
|
|
425
348
|
};
|
|
426
349
|
}
|
|
350
|
+
|
|
351
|
+
decryptTransaction(
|
|
352
|
+
sessionID: SessionID,
|
|
353
|
+
txIndex: number,
|
|
354
|
+
keySecret: KeySecret,
|
|
355
|
+
): JsonValue[] | undefined {
|
|
356
|
+
return this.sessions.decryptTransaction(sessionID, txIndex, keySecret);
|
|
357
|
+
}
|
|
427
358
|
}
|
|
428
359
|
|
|
429
360
|
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
|
|
|
@@ -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,195 @@ 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,
|
|
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
|
+
const checkHasher = this.crypto.cloneBlake3State(this.streamingHash);
|
|
267
|
+
|
|
268
|
+
for (const tx of transactions) {
|
|
269
|
+
checkHasher.update(textEncoder.encode(tx));
|
|
270
|
+
}
|
|
271
|
+
const newHash = checkHasher.digest();
|
|
272
|
+
const newHashEncoded = `hash_z${base58.encode(newHash)}`;
|
|
273
|
+
|
|
274
|
+
if (!this.crypto.verify(newSignature, newHashEncoded, this.signerID)) {
|
|
275
|
+
throw new Error("Signature verification failed");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
for (const tx of transactions) {
|
|
280
|
+
this.crypto.blake3IncrementalUpdate(
|
|
281
|
+
this.streamingHash,
|
|
282
|
+
textEncoder.encode(tx),
|
|
283
|
+
);
|
|
284
|
+
this.transactions.push(tx);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
this.lastSignature = newSignature;
|
|
288
|
+
|
|
289
|
+
return newSignature;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
expectedHashAfter(transactionsJson: string[]): string {
|
|
293
|
+
const hasher = this.crypto.cloneBlake3State(this.streamingHash);
|
|
294
|
+
for (const tx of transactionsJson) {
|
|
295
|
+
hasher.update(textEncoder.encode(tx));
|
|
296
|
+
}
|
|
297
|
+
const newHash = hasher.digest();
|
|
298
|
+
return `hash_z${base58.encode(newHash)}`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
internalAddNewTransaction(
|
|
302
|
+
transaction: string,
|
|
303
|
+
signerAgent: ControlledAccountOrAgent,
|
|
304
|
+
) {
|
|
305
|
+
this.crypto.blake3IncrementalUpdate(
|
|
306
|
+
this.streamingHash,
|
|
307
|
+
textEncoder.encode(transaction),
|
|
308
|
+
);
|
|
309
|
+
const newHash = this.crypto.blake3DigestForState(this.streamingHash);
|
|
310
|
+
const newHashEncoded = `hash_z${base58.encode(newHash)}`;
|
|
311
|
+
const signature = this.crypto.sign(
|
|
312
|
+
signerAgent.currentSignerSecret(),
|
|
313
|
+
newHashEncoded,
|
|
314
|
+
);
|
|
315
|
+
this.transactions.push(transaction);
|
|
316
|
+
this.lastSignature = signature;
|
|
317
|
+
|
|
318
|
+
return signature;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
addNewPrivateTransaction(
|
|
322
|
+
signerAgent: ControlledAccountOrAgent,
|
|
323
|
+
changes: JsonValue[],
|
|
324
|
+
keyID: KeyID,
|
|
325
|
+
keySecret: KeySecret,
|
|
326
|
+
madeAt: number,
|
|
327
|
+
): { signature: Signature; transaction: PrivateTransaction } {
|
|
328
|
+
const encryptedChanges = this.crypto.encrypt(changes, keySecret, {
|
|
329
|
+
in: this.coID,
|
|
330
|
+
tx: { sessionID: this.sessionID, txIndex: this.transactions.length },
|
|
331
|
+
});
|
|
332
|
+
const tx = {
|
|
333
|
+
encryptedChanges: encryptedChanges,
|
|
334
|
+
madeAt: madeAt,
|
|
335
|
+
privacy: "private",
|
|
336
|
+
keyUsed: keyID,
|
|
337
|
+
} satisfies Transaction;
|
|
338
|
+
const signature = this.internalAddNewTransaction(
|
|
339
|
+
stableStringify(tx),
|
|
340
|
+
signerAgent,
|
|
341
|
+
);
|
|
342
|
+
return {
|
|
343
|
+
signature: signature as Signature,
|
|
344
|
+
transaction: tx,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
addNewTrustingTransaction(
|
|
349
|
+
signerAgent: ControlledAccountOrAgent,
|
|
350
|
+
changes: JsonValue[],
|
|
351
|
+
madeAt: number,
|
|
352
|
+
): { signature: Signature; transaction: TrustingTransaction } {
|
|
353
|
+
const tx = {
|
|
354
|
+
changes: stableStringify(changes),
|
|
355
|
+
madeAt: madeAt,
|
|
356
|
+
privacy: "trusting",
|
|
357
|
+
} satisfies Transaction;
|
|
358
|
+
const signature = this.internalAddNewTransaction(
|
|
359
|
+
stableStringify(tx),
|
|
360
|
+
signerAgent,
|
|
361
|
+
);
|
|
362
|
+
return {
|
|
363
|
+
signature: signature as Signature,
|
|
364
|
+
transaction: tx,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
decryptNextTransactionChangesJson(
|
|
369
|
+
txIndex: number,
|
|
370
|
+
keySecret: KeySecret,
|
|
371
|
+
): string {
|
|
372
|
+
const txJson = this.transactions[txIndex];
|
|
373
|
+
if (!txJson) {
|
|
374
|
+
throw new Error("Transaction not found");
|
|
375
|
+
}
|
|
376
|
+
const tx = JSON.parse(txJson) as Transaction;
|
|
377
|
+
if (tx.privacy === "private") {
|
|
378
|
+
const nOnceMaterial = {
|
|
379
|
+
in: this.coID,
|
|
380
|
+
tx: { sessionID: this.sessionID, txIndex: txIndex },
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const nOnce = this.crypto.generateJsonNonce(nOnceMaterial);
|
|
384
|
+
|
|
385
|
+
const ciphertext = base64URLtoBytes(
|
|
386
|
+
tx.encryptedChanges.substring("encrypted_U".length),
|
|
387
|
+
);
|
|
388
|
+
const keySecretBytes = base58.decode(
|
|
389
|
+
keySecret.substring("keySecret_z".length),
|
|
390
|
+
);
|
|
391
|
+
const plaintext = xsalsa20(keySecretBytes, nOnce, ciphertext);
|
|
392
|
+
|
|
393
|
+
return textDecoder.decode(plaintext);
|
|
394
|
+
} else {
|
|
395
|
+
return tx.changes;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
free(): void {
|
|
400
|
+
// no-op
|
|
401
|
+
}
|
|
202
402
|
}
|
package/src/crypto/WasmCrypto.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SessionLog,
|
|
3
|
+
initialize,
|
|
2
4
|
Blake3Hasher,
|
|
3
5
|
blake3_empty_state,
|
|
4
6
|
blake3_hash_once,
|
|
@@ -7,16 +9,15 @@ import {
|
|
|
7
9
|
encrypt,
|
|
8
10
|
get_sealer_id,
|
|
9
11
|
get_signer_id,
|
|
10
|
-
initialize,
|
|
11
12
|
new_ed25519_signing_key,
|
|
12
13
|
new_x25519_private_key,
|
|
13
14
|
seal,
|
|
14
15
|
sign,
|
|
15
16
|
unseal,
|
|
16
17
|
verify,
|
|
17
|
-
} from "
|
|
18
|
+
} from "cojson-core-wasm";
|
|
18
19
|
import { base64URLtoBytes, bytesToBase64url } from "../base64url.js";
|
|
19
|
-
import { RawCoID, TransactionID } from "../ids.js";
|
|
20
|
+
import { RawCoID, SessionID, TransactionID } from "../ids.js";
|
|
20
21
|
import { Stringified, stableStringify } from "../jsonStringify.js";
|
|
21
22
|
import { JsonValue } from "../jsonValue.js";
|
|
22
23
|
import { logger } from "../logger.js";
|
|
@@ -24,6 +25,8 @@ import { PureJSCrypto } from "./PureJSCrypto.js";
|
|
|
24
25
|
import {
|
|
25
26
|
CryptoProvider,
|
|
26
27
|
Encrypted,
|
|
28
|
+
Hash,
|
|
29
|
+
KeyID,
|
|
27
30
|
KeySecret,
|
|
28
31
|
Sealed,
|
|
29
32
|
SealerID,
|
|
@@ -34,11 +37,17 @@ import {
|
|
|
34
37
|
textDecoder,
|
|
35
38
|
textEncoder,
|
|
36
39
|
} from "./crypto.js";
|
|
40
|
+
import { ControlledAccountOrAgent } from "../coValues/account.js";
|
|
41
|
+
import {
|
|
42
|
+
PrivateTransaction,
|
|
43
|
+
Transaction,
|
|
44
|
+
TrustingTransaction,
|
|
45
|
+
} from "../coValueCore/verifiedState.js";
|
|
37
46
|
|
|
38
47
|
type Blake3State = Blake3Hasher;
|
|
39
48
|
|
|
40
49
|
/**
|
|
41
|
-
* WebAssembly implementation of the CryptoProvider interface using
|
|
50
|
+
* WebAssembly implementation of the CryptoProvider interface using cojson-core-wasm.
|
|
42
51
|
* This provides the primary implementation using WebAssembly for optimal performance, offering:
|
|
43
52
|
* - Signing/verifying (Ed25519)
|
|
44
53
|
* - Encryption/decryption (XSalsa20)
|
|
@@ -195,4 +204,86 @@ export class WasmCrypto extends CryptoProvider<Blake3State> {
|
|
|
195
204
|
return undefined;
|
|
196
205
|
}
|
|
197
206
|
}
|
|
207
|
+
|
|
208
|
+
createSessionLog(coID: RawCoID, sessionID: SessionID, signerID: SignerID) {
|
|
209
|
+
return new SessionLogAdapter(new SessionLog(coID, sessionID, signerID));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
class SessionLogAdapter {
|
|
214
|
+
constructor(private readonly sessionLog: SessionLog) {}
|
|
215
|
+
|
|
216
|
+
tryAdd(
|
|
217
|
+
transactions: Transaction[],
|
|
218
|
+
newSignature: Signature,
|
|
219
|
+
skipVerify: boolean,
|
|
220
|
+
): void {
|
|
221
|
+
this.sessionLog.tryAdd(
|
|
222
|
+
transactions.map((tx) => stableStringify(tx)),
|
|
223
|
+
newSignature,
|
|
224
|
+
skipVerify,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
addNewPrivateTransaction(
|
|
229
|
+
signerAgent: ControlledAccountOrAgent,
|
|
230
|
+
changes: JsonValue[],
|
|
231
|
+
keyID: KeyID,
|
|
232
|
+
keySecret: KeySecret,
|
|
233
|
+
madeAt: number,
|
|
234
|
+
): { signature: Signature; transaction: PrivateTransaction } {
|
|
235
|
+
const output = this.sessionLog.addNewPrivateTransaction(
|
|
236
|
+
stableStringify(changes),
|
|
237
|
+
signerAgent.currentSignerSecret(),
|
|
238
|
+
keySecret,
|
|
239
|
+
keyID,
|
|
240
|
+
madeAt,
|
|
241
|
+
);
|
|
242
|
+
const parsedOutput = JSON.parse(output);
|
|
243
|
+
const transaction: PrivateTransaction = {
|
|
244
|
+
privacy: "private",
|
|
245
|
+
madeAt,
|
|
246
|
+
encryptedChanges: parsedOutput.encrypted_changes,
|
|
247
|
+
keyUsed: keyID,
|
|
248
|
+
};
|
|
249
|
+
return { signature: parsedOutput.signature, transaction };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
addNewTrustingTransaction(
|
|
253
|
+
signerAgent: ControlledAccountOrAgent,
|
|
254
|
+
changes: JsonValue[],
|
|
255
|
+
madeAt: number,
|
|
256
|
+
): { signature: Signature; transaction: TrustingTransaction } {
|
|
257
|
+
const stringifiedChanges = stableStringify(changes);
|
|
258
|
+
const output = this.sessionLog.addNewTrustingTransaction(
|
|
259
|
+
stringifiedChanges,
|
|
260
|
+
signerAgent.currentSignerSecret(),
|
|
261
|
+
madeAt,
|
|
262
|
+
);
|
|
263
|
+
const transaction: TrustingTransaction = {
|
|
264
|
+
privacy: "trusting",
|
|
265
|
+
madeAt,
|
|
266
|
+
changes: stringifiedChanges,
|
|
267
|
+
};
|
|
268
|
+
return { signature: output as Signature, transaction };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
decryptNextTransactionChangesJson(
|
|
272
|
+
txIndex: number,
|
|
273
|
+
keySecret: KeySecret,
|
|
274
|
+
): string {
|
|
275
|
+
const output = this.sessionLog.decryptNextTransactionChangesJson(
|
|
276
|
+
txIndex,
|
|
277
|
+
keySecret,
|
|
278
|
+
);
|
|
279
|
+
return output;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
free() {
|
|
283
|
+
this.sessionLog.free();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
clone(): SessionLogAdapter {
|
|
287
|
+
return new SessionLogAdapter(this.sessionLog.clone());
|
|
288
|
+
}
|
|
198
289
|
}
|