@silvana-one/upgradable 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/upgrade.ts ADDED
@@ -0,0 +1,294 @@
1
+ import {
2
+ Bool,
3
+ method,
4
+ Permissions,
5
+ Provable,
6
+ SmartContract,
7
+ State,
8
+ state,
9
+ VerificationKey,
10
+ Field,
11
+ Struct,
12
+ UInt32,
13
+ } from "o1js";
14
+ import {
15
+ loadIndexedMerkleMap,
16
+ createIpfsURL,
17
+ IndexedMapSerialized,
18
+ Storage,
19
+ } from "@silvana-one/storage";
20
+ import {
21
+ UpgradeAuthorityBase,
22
+ VerificationKeyUpgradeData,
23
+ UpgradeAuthorityAnswer,
24
+ } from "./upgradable.js";
25
+ import {
26
+ UpgradeAuthorityDatabase,
27
+ ValidatorsState,
28
+ UpgradeDatabaseState,
29
+ ValidatorsVotingProof,
30
+ ValidatorDecisionType,
31
+ UpgradeDatabaseStatePacked,
32
+ } from "./validators.js";
33
+
34
+ export {
35
+ VerificationKeyUpgradeAuthority,
36
+ UpgradeAuthorityDatabase,
37
+ ValidatorsListEvent,
38
+ ValidatorsListData,
39
+ };
40
+
41
+ /**
42
+ * Interface representing the data for a list of validators.
43
+ */
44
+ interface ValidatorsListData {
45
+ validators: {
46
+ publicKey: string;
47
+ authorizedToVote: boolean;
48
+ }[];
49
+ validatorsCount: number;
50
+ root: string;
51
+ map: IndexedMapSerialized;
52
+ }
53
+
54
+ /**
55
+ * Event emitted when the validators list is updated.
56
+ */
57
+ class ValidatorsListEvent extends Struct({
58
+ validators: ValidatorsState,
59
+ storage: Storage,
60
+ }) {}
61
+
62
+ /**
63
+ * Error messages for the VerificationKeyUpgradeAuthority contract.
64
+ */
65
+ const VerificationKeyUpgradeAuthorityErrors = {
66
+ WrongNewVerificationKeyHash: "Wrong new verification key hash",
67
+ };
68
+
69
+ /**
70
+ * **VerificationKeyUpgradeAuthority** is a smart contract that provides a secure mechanism
71
+ * for upgrading the verification keys of other contracts without requiring redeployment.
72
+ * It manages a list of validators who can vote on upgrade proposals, ensuring that only
73
+ * authorized upgrades are applied.
74
+ *
75
+ * **Key Features:**
76
+ * - **Verification Key Management**: Allows for secure upgrades of verification keys for other contracts.
77
+ * - **Validators Governance**: Maintains a list of authorized validators who can vote on upgrade proposals.
78
+ * - **Secure Voting Mechanism**: Implements Zero-Knowledge proofs to validate votes from validators without revealing sensitive information.
79
+ * - **Upgrade Database Management**: Keeps track of upgrade proposals and their validity periods.
80
+ * - **Event Emissions**: Emits events when validators list or upgrade database is updated.
81
+ */
82
+ class VerificationKeyUpgradeAuthority
83
+ extends SmartContract
84
+ implements UpgradeAuthorityBase
85
+ {
86
+ /**
87
+ * The hash of the verification key.
88
+ * @type {State<Field>}
89
+ */
90
+ @state(Field) verificationKeyHash = State<Field>();
91
+
92
+ /**
93
+ * The hash representing the current state of the validators list.
94
+ * @type {State<Field>}
95
+ */
96
+ @state(Field) validators = State<Field>();
97
+
98
+ /**
99
+ * Packed state containing the upgrade database information.
100
+ * @type {State<UpgradeDatabaseStatePacked>}
101
+ */
102
+ @state(UpgradeDatabaseStatePacked)
103
+ upgradeDatabasePacked = State<UpgradeDatabaseStatePacked>();
104
+
105
+ /**
106
+ * The events emitted by the VerificationKeyUpgradeAuthority contract.
107
+ */
108
+ events = {
109
+ validatorsList: ValidatorsListEvent,
110
+ updateDatabase: UpgradeDatabaseState,
111
+ };
112
+
113
+ /**
114
+ * Deploys the contract and sets the initial state.
115
+ */
116
+ async deploy() {
117
+ await super.deploy();
118
+ this.upgradeDatabasePacked.set(UpgradeDatabaseState.empty().pack());
119
+ this.account.permissions.set({
120
+ ...Permissions.default(),
121
+ setVerificationKey:
122
+ // The contract needs to be redeployed in the case of an upgrade.
123
+ Permissions.VerificationKey.impossibleDuringCurrentVersion(),
124
+ setPermissions: Permissions.impossible(),
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Initializes the contract with validators and sets the verification key hash.
130
+ *
131
+ * @param {ValidatorsState} validators - The initial validators state.
132
+ * @param {Storage} storage - Off-chain storage information, e.g., IPFS hash.
133
+ * @param {Field} verificationKeyHash - The hash of the verification key.
134
+ */
135
+ @method
136
+ async initialize(
137
+ validators: ValidatorsState,
138
+ storage: Storage,
139
+ verificationKeyHash: Field
140
+ ) {
141
+ this.account.provedState.requireEquals(Bool(false));
142
+ await this.setValidatorsList(validators, storage);
143
+ this.verificationKeyHash.set(verificationKeyHash);
144
+ }
145
+
146
+ /**
147
+ * Sets the validators list and emits an event.
148
+ *
149
+ * @param {ValidatorsState} validators - The validators state to set.
150
+ * @param {Storage} storage - The storage associated with the validators list.
151
+ */
152
+ async setValidatorsList(validators: ValidatorsState, storage: Storage) {
153
+ this.validators.set(validators.hash());
154
+ this.emitEvent(
155
+ "validatorsList",
156
+ new ValidatorsListEvent({ validators, storage })
157
+ );
158
+ }
159
+
160
+ /**
161
+ * Verifies the upgrade data provided by another contract.
162
+ *
163
+ * @param {VerificationKeyUpgradeData} data - The upgrade data to verify.
164
+ * @returns {Promise<UpgradeAuthorityAnswer>} - The answer indicating verification result.
165
+ */
166
+ @method.returns(UpgradeAuthorityAnswer)
167
+ public async verifyUpgradeData(
168
+ data: VerificationKeyUpgradeData
169
+ ): Promise<UpgradeAuthorityAnswer> {
170
+ const upgradeDatabase = UpgradeDatabaseState.unpack(
171
+ this.upgradeDatabasePacked.getAndRequireEquals()
172
+ );
173
+ this.network.globalSlotSinceGenesis.requireBetween(
174
+ upgradeDatabase.validFrom,
175
+ UInt32.MAXINT()
176
+ );
177
+ const map = await Provable.witnessAsync(
178
+ UpgradeAuthorityDatabase,
179
+ async () => {
180
+ return await loadIndexedMerkleMap({
181
+ url: createIpfsURL({ hash: upgradeDatabase.storage.toString() }),
182
+ type: UpgradeAuthorityDatabase,
183
+ });
184
+ }
185
+ );
186
+ map.root.assertEquals(upgradeDatabase.root);
187
+ const key = data.hash();
188
+ const newVerificationKeyHash = map.get(key);
189
+ newVerificationKeyHash.assertEquals(
190
+ data.newVerificationKeyHash,
191
+ VerificationKeyUpgradeAuthorityErrors.WrongNewVerificationKeyHash
192
+ );
193
+ return new UpgradeAuthorityAnswer({
194
+ // Should be public key of the next upgrade authority in case
195
+ // new version of o1js breaks the verification key of upgrade authority
196
+ nextUpgradeAuthority: upgradeDatabase.nextUpgradeAuthority,
197
+ isVerified: Bool(true),
198
+ });
199
+ }
200
+
201
+ /**
202
+ * Updates the upgrade database after validator consensus.
203
+ *
204
+ * @param {ValidatorsVotingProof} proof - The proof of validators voting.
205
+ * @param {VerificationKey} vk - The verification key to validate the proof.
206
+ */
207
+ @method
208
+ async updateDatabase(
209
+ proof: ValidatorsVotingProof,
210
+ vk: VerificationKey,
211
+ validators: ValidatorsState
212
+ ) {
213
+ const oldUpgradeDatabase = UpgradeDatabaseState.unpack(
214
+ this.upgradeDatabasePacked.getAndRequireEquals()
215
+ );
216
+ const upgradeDatabase = proof.publicInput.decision.upgradeDatabase;
217
+ upgradeDatabase.version.assertGreaterThan(oldUpgradeDatabase.version);
218
+ await this.checkValidatorsDecision(
219
+ proof,
220
+ vk,
221
+ ValidatorDecisionType["updateDatabase"],
222
+ validators
223
+ );
224
+
225
+ // This does not create a constraint on the storage,
226
+ // serves to prevent deployment errors.
227
+ // Can be replaced with Data Availability proof in the future.
228
+ // TODO: consider using Celestia DA for this.
229
+ const map = await Provable.witnessAsync(
230
+ UpgradeAuthorityDatabase,
231
+ async () => {
232
+ return await loadIndexedMerkleMap({
233
+ url: createIpfsURL({ hash: upgradeDatabase.storage.toString() }),
234
+ type: UpgradeAuthorityDatabase,
235
+ });
236
+ }
237
+ );
238
+ map.root.assertEquals(upgradeDatabase.root);
239
+ this.upgradeDatabasePacked.set(upgradeDatabase.pack());
240
+ this.emitEvent("updateDatabase", upgradeDatabase);
241
+ }
242
+
243
+ /**
244
+ * Updates the validators list based on validator votes.
245
+ *
246
+ * @param {ValidatorsState} validators - The new validators state.
247
+ * @param {Storage} storage - The storage associated with the validators list.
248
+ * @param {ValidatorsVotingProof} proof - The proof of validators voting.
249
+ * @param {VerificationKey} vk - The verification key to validate the proof.
250
+ */
251
+ @method // add proof of validators voting
252
+ async updateValidatorsList(
253
+ validators: ValidatorsState,
254
+ storage: Storage,
255
+ proof: ValidatorsVotingProof,
256
+ vk: VerificationKey
257
+ ) {
258
+ await this.checkValidatorsDecision(
259
+ proof,
260
+ vk,
261
+ ValidatorDecisionType["updateValidatorsList"],
262
+ validators
263
+ );
264
+ await this.setValidatorsList(validators, storage);
265
+ }
266
+
267
+ /**
268
+ * Checks the validators' decision by verifying the provided proof.
269
+ *
270
+ * @param {ValidatorsVotingProof} proof - The proof to verify.
271
+ * @param {VerificationKey} vk - The verification key to validate the proof.
272
+ * @param {Field} decisionType - The type of decision being validated.
273
+ */
274
+ async checkValidatorsDecision(
275
+ proof: ValidatorsVotingProof,
276
+ vk: VerificationKey,
277
+ decisionType: Field,
278
+ validatorsState: ValidatorsState
279
+ ) {
280
+ this.network.globalSlotSinceGenesis.requireBetween(
281
+ UInt32.zero,
282
+ proof.publicInput.decision.expiry
283
+ );
284
+ vk.hash.assertEquals(this.verificationKeyHash.getAndRequireEquals());
285
+ proof.verify(vk);
286
+ proof.publicInput.decision.validators
287
+ .hash()
288
+ .assertEquals(this.validators.getAndRequireEquals());
289
+ proof.publicInput.decision.decisionType.assertEquals(decisionType);
290
+ proof.publicInput.decision.contractAddress.assertEquals(this.address);
291
+ validatorsState.hash().assertEquals(this.validators.getAndRequireEquals());
292
+ proof.publicOutput.yesVotes.mul(2).assertGreaterThan(validatorsState.count);
293
+ }
294
+ }