@proto-kit/common 0.1.1-develop.153
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.md +201 -0
- package/dist/Constants.d.ts +4 -0
- package/dist/Constants.d.ts.map +1 -0
- package/dist/Constants.js +3 -0
- package/dist/config/ConfigurableModule.d.ts +21 -0
- package/dist/config/ConfigurableModule.d.ts.map +1 -0
- package/dist/config/ConfigurableModule.js +19 -0
- package/dist/config/ConfigurationAggregator.d.ts +10 -0
- package/dist/config/ConfigurationAggregator.d.ts.map +1 -0
- package/dist/config/ConfigurationAggregator.js +35 -0
- package/dist/config/ConfigurationReceiver.d.ts +25 -0
- package/dist/config/ConfigurationReceiver.d.ts.map +1 -0
- package/dist/config/ConfigurationReceiver.js +36 -0
- package/dist/config/ModuleContainer.d.ts +103 -0
- package/dist/config/ModuleContainer.d.ts.map +1 -0
- package/dist/config/ModuleContainer.js +163 -0
- package/dist/config/types.d.ts +2 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/model/MethodPublicInput.d.ts +51 -0
- package/dist/model/MethodPublicInput.d.ts.map +1 -0
- package/dist/model/MethodPublicInput.js +11 -0
- package/dist/model/Option.d.ts +89 -0
- package/dist/model/Option.d.ts.map +1 -0
- package/dist/model/Option.js +86 -0
- package/dist/model/Path.d.ts +31 -0
- package/dist/model/Path.d.ts.map +1 -0
- package/dist/model/Path.js +44 -0
- package/dist/model/StateTransition.d.ts +85 -0
- package/dist/model/StateTransition.d.ts.map +1 -0
- package/dist/model/StateTransition.js +58 -0
- package/dist/model/StateTransitionProvableBatch.d.ts +56 -0
- package/dist/model/StateTransitionProvableBatch.d.ts.map +1 -0
- package/dist/model/StateTransitionProvableBatch.js +20 -0
- package/dist/prover/block/BlockProver.d.ts +199 -0
- package/dist/prover/block/BlockProver.d.ts.map +1 -0
- package/dist/prover/block/BlockProver.js +119 -0
- package/dist/prover/block/BlockScopedModule.d.ts +3 -0
- package/dist/prover/block/BlockScopedModule.d.ts.map +1 -0
- package/dist/prover/block/BlockScopedModule.js +6 -0
- package/dist/prover/statetransition/StateTransitionProver.d.ts +92 -0
- package/dist/prover/statetransition/StateTransitionProver.d.ts.map +1 -0
- package/dist/prover/statetransition/StateTransitionProver.js +127 -0
- package/dist/prover/statetransition/StateTransitionWitnessProvider.d.ts +16 -0
- package/dist/prover/statetransition/StateTransitionWitnessProvider.d.ts.map +1 -0
- package/dist/prover/statetransition/StateTransitionWitnessProvider.js +17 -0
- package/dist/src/model/Option.d.ts +158 -0
- package/dist/src/model/Option.d.ts.map +1 -0
- package/dist/src/model/Option.js +53 -0
- package/dist/src/model/Path.d.ts +35 -0
- package/dist/src/model/Path.d.ts.map +1 -0
- package/dist/src/model/Path.js +51 -0
- package/dist/src/model/StateTransition.d.ts +201 -0
- package/dist/src/model/StateTransition.d.ts.map +1 -0
- package/dist/src/model/StateTransition.js +43 -0
- package/dist/src/utils/PrefixedHashList.d.ts +15 -0
- package/dist/src/utils/PrefixedHashList.d.ts.map +1 -0
- package/dist/src/utils/PrefixedHashList.js +28 -0
- package/dist/src/utils/ProvableHashList.d.ts +30 -0
- package/dist/src/utils/ProvableHashList.d.ts.map +1 -0
- package/dist/src/utils/ProvableHashList.js +43 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/utils/PrefixedHashList.d.ts +14 -0
- package/dist/utils/PrefixedHashList.d.ts.map +1 -0
- package/dist/utils/PrefixedHashList.js +12 -0
- package/dist/utils/PrefixedProvableHashList.d.ts +8 -0
- package/dist/utils/PrefixedProvableHashList.d.ts.map +1 -0
- package/dist/utils/PrefixedProvableHashList.js +12 -0
- package/dist/utils/ProvableHashList.d.ts +26 -0
- package/dist/utils/ProvableHashList.d.ts.map +1 -0
- package/dist/utils/ProvableHashList.js +35 -0
- package/dist/utils/Utils.d.ts +22 -0
- package/dist/utils/Utils.d.ts.map +1 -0
- package/dist/utils/Utils.js +41 -0
- package/dist/utils/merkletree/MemoryMerkleTreeStorage.d.ts +26 -0
- package/dist/utils/merkletree/MemoryMerkleTreeStorage.d.ts.map +1 -0
- package/dist/utils/merkletree/MemoryMerkleTreeStorage.js +79 -0
- package/dist/utils/merkletree/RollupMerkleTree.d.ts +143 -0
- package/dist/utils/merkletree/RollupMerkleTree.d.ts.map +1 -0
- package/dist/utils/merkletree/RollupMerkleTree.js +246 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +7 -0
- package/dist/zkProgrammable/ProvableMethodExecutionContext.d.ts +53 -0
- package/dist/zkProgrammable/ProvableMethodExecutionContext.d.ts.map +1 -0
- package/dist/zkProgrammable/ProvableMethodExecutionContext.js +98 -0
- package/dist/zkProgrammable/ZkProgrammable.d.ts +32 -0
- package/dist/zkProgrammable/ZkProgrammable.d.ts.map +1 -0
- package/dist/zkProgrammable/ZkProgrammable.js +60 -0
- package/dist/zkProgrammable/provableMethod.d.ts +16 -0
- package/dist/zkProgrammable/provableMethod.d.ts.map +1 -0
- package/dist/zkProgrammable/provableMethod.js +69 -0
- package/jest.config.cjs +1 -0
- package/package.json +35 -0
- package/src/config/ConfigurableModule.ts +44 -0
- package/src/config/ModuleContainer.ts +265 -0
- package/src/index.ts +9 -0
- package/src/types.ts +17 -0
- package/src/utils.ts +10 -0
- package/src/zkProgrammable/ProvableMethodExecutionContext.ts +122 -0
- package/src/zkProgrammable/ZkProgrammable.ts +119 -0
- package/src/zkProgrammable/provableMethod.ts +109 -0
- package/test/config/ModuleContainer.test.ts +82 -0
- package/test/tsconfig.json +4 -0
- package/test/zkProgrammable/ZkProgrammable.test.ts +283 -0
- package/tsconfig.json +8 -0
- package/tsconfig.test.json +9 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const errors = {
|
|
2
|
+
configNotSet: (moduleName: string) =>
|
|
3
|
+
new Error(
|
|
4
|
+
`Trying to retrieve config of ${moduleName}, which was not yet set`
|
|
5
|
+
),
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// defines how presets can be provided, either a function or a record
|
|
9
|
+
export type Preset<Config> = Config | ((...args: any[]) => Config);
|
|
10
|
+
export type Presets<Config> = Record<string, Preset<Config>>;
|
|
11
|
+
|
|
12
|
+
// describes the interface of a configurable module
|
|
13
|
+
export interface Configurable<Config> {
|
|
14
|
+
config: Config;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Used by various module sub-types that may need to be configured
|
|
19
|
+
*/
|
|
20
|
+
export class ConfigurableModule<Config> implements Configurable<Config> {
|
|
21
|
+
/**
|
|
22
|
+
* Store the config separately, so that we can apply additional
|
|
23
|
+
* checks when retrieving it via the getter
|
|
24
|
+
*/
|
|
25
|
+
protected currentConfig: Config | undefined;
|
|
26
|
+
|
|
27
|
+
// retrieve the existing config
|
|
28
|
+
public get config(): Config {
|
|
29
|
+
if (this.currentConfig === undefined) {
|
|
30
|
+
throw errors.configNotSet(this.constructor.name);
|
|
31
|
+
}
|
|
32
|
+
return this.currentConfig;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// set the config
|
|
36
|
+
public set config(config: Config) {
|
|
37
|
+
this.currentConfig = config;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Helps ensure that the target class implements static presets
|
|
42
|
+
export interface StaticConfigurableModule<Config> {
|
|
43
|
+
presets: Presets<Config>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
|
|
3
|
+
import { container, Frequency, InjectionToken, Lifecycle } from "tsyringe";
|
|
4
|
+
import log from "loglevel";
|
|
5
|
+
|
|
6
|
+
import { StringKeyOf, TypedClass } from "../types";
|
|
7
|
+
|
|
8
|
+
import { Configurable, ConfigurableModule } from "./ConfigurableModule";
|
|
9
|
+
|
|
10
|
+
const errors = {
|
|
11
|
+
configNotSetInContainer: (moduleName: string) =>
|
|
12
|
+
new Error(
|
|
13
|
+
`Trying to get config of ${moduleName}, but it was not yet set in the module container`
|
|
14
|
+
),
|
|
15
|
+
|
|
16
|
+
onlyValidModuleNames: (moduleName: NonNullable<unknown>) =>
|
|
17
|
+
new Error(
|
|
18
|
+
// eslint-disable-next-line max-len
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string,@typescript-eslint/restrict-template-expressions
|
|
20
|
+
`Only known module names are allowed, using unknown module name: ${moduleName}`
|
|
21
|
+
),
|
|
22
|
+
|
|
23
|
+
unableToDecorateModule: (moduleName: InjectionToken<unknown>) =>
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
25
|
+
new Error(`Unable to decorate module ${moduleName.toString()}`),
|
|
26
|
+
|
|
27
|
+
nonModuleDependency: (runtimeModuleName: string) =>
|
|
28
|
+
new Error(`
|
|
29
|
+
Unable to register module: ${runtimeModuleName}, attempting to inject a non-module dependency`),
|
|
30
|
+
|
|
31
|
+
unknownDependency: (runtimeModuleName: string, name: string) =>
|
|
32
|
+
new Error(
|
|
33
|
+
`Unable to register module: ${runtimeModuleName},
|
|
34
|
+
attempting to inject a dependency that is not registered
|
|
35
|
+
as a runtime module for this chain: ${name}`
|
|
36
|
+
),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const ModuleContainerErrors = errors;
|
|
40
|
+
|
|
41
|
+
// determines that a module should be configurable by default
|
|
42
|
+
export type BaseModuleType = TypedClass<Configurable<unknown>>;
|
|
43
|
+
|
|
44
|
+
// allows to specify what kind of modules can be passed into a container
|
|
45
|
+
export interface ModulesRecord<
|
|
46
|
+
// use the default configurable module type
|
|
47
|
+
ModuleType extends BaseModuleType = BaseModuleType
|
|
48
|
+
> {
|
|
49
|
+
[name: string]: ModuleType;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// config record derived from the provided modules and their config types
|
|
53
|
+
export type ModulesConfig<Modules extends ModulesRecord> = {
|
|
54
|
+
// this will translate into = key: module name, value: module.config
|
|
55
|
+
[ConfigKey in StringKeyOf<Modules>]: InstanceType<
|
|
56
|
+
Modules[ConfigKey]
|
|
57
|
+
> extends Configurable<infer Config>
|
|
58
|
+
? Config
|
|
59
|
+
: never;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parameters required when creating a module container instance
|
|
64
|
+
*/
|
|
65
|
+
export interface ModuleContainerDefinition<Modules extends ModulesRecord> {
|
|
66
|
+
modules: Modules;
|
|
67
|
+
// config is optional, as it may be provided by the parent/wrapper class
|
|
68
|
+
config?: ModulesConfig<Modules>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Reusable module container facilitating registration, resolution
|
|
73
|
+
* configuration, decoration and validation of modules
|
|
74
|
+
*/
|
|
75
|
+
export class ModuleContainer<
|
|
76
|
+
Modules extends ModulesRecord
|
|
77
|
+
> extends ConfigurableModule<unknown> {
|
|
78
|
+
/**
|
|
79
|
+
* Determines how often are modules decorated upon resolution
|
|
80
|
+
* from the tsyringe DI container
|
|
81
|
+
*/
|
|
82
|
+
private static readonly moduleDecorationFrequency: Frequency = "Once";
|
|
83
|
+
|
|
84
|
+
// DI container holding all the registered modules
|
|
85
|
+
protected readonly container = container.createChildContainer();
|
|
86
|
+
|
|
87
|
+
public constructor(public definition: ModuleContainerDefinition<Modules>) {
|
|
88
|
+
super();
|
|
89
|
+
// register all provided modules when the container is created
|
|
90
|
+
this.registerModules(definition.modules);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @returns list of module names
|
|
95
|
+
*/
|
|
96
|
+
public get moduleNames() {
|
|
97
|
+
return Object.keys(this.definition.modules);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if the provided module satisfies the container requirements,
|
|
102
|
+
* such as only injecting other known modules.
|
|
103
|
+
*
|
|
104
|
+
* @param moduleName
|
|
105
|
+
* @param containedModule
|
|
106
|
+
*/
|
|
107
|
+
protected validateModule(
|
|
108
|
+
moduleName: StringKeyOf<Modules>,
|
|
109
|
+
containedModule: ConfigurableModule<unknown>
|
|
110
|
+
): void {
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
112
|
+
const dependencies: { name?: string }[] | string[] | undefined =
|
|
113
|
+
Reflect.getMetadata("design:paramtypes", containedModule);
|
|
114
|
+
|
|
115
|
+
dependencies?.forEach((dependency: string | { name?: string }) => {
|
|
116
|
+
const name =
|
|
117
|
+
typeof dependency === "string" ? dependency : dependency.name;
|
|
118
|
+
|
|
119
|
+
if (name === undefined) {
|
|
120
|
+
throw errors.nonModuleDependency(moduleName);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!this.moduleNames.includes(name)) {
|
|
124
|
+
throw errors.unknownDependency(moduleName, name);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Assert that the iterated `moduleName` is of ModuleName type,
|
|
131
|
+
* otherwise it may be just string e.g. when modules are iterated over
|
|
132
|
+
* using e.g. a for loop.
|
|
133
|
+
*/
|
|
134
|
+
public assertIsValidModuleName(
|
|
135
|
+
modules: Modules,
|
|
136
|
+
moduleName: string
|
|
137
|
+
): asserts moduleName is StringKeyOf<Modules> {
|
|
138
|
+
this.isValidModuleName(modules, moduleName);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public isValidModuleName(
|
|
142
|
+
modules: Modules,
|
|
143
|
+
moduleName: number | string | symbol
|
|
144
|
+
): asserts moduleName is StringKeyOf<Modules> {
|
|
145
|
+
if (!Object.prototype.hasOwnProperty.call(modules, moduleName)) {
|
|
146
|
+
throw errors.onlyValidModuleNames(moduleName);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Register modules into the current container, and registers
|
|
152
|
+
* a respective resolution hook in order to decorate the module
|
|
153
|
+
* upon/after resolution.
|
|
154
|
+
*
|
|
155
|
+
* @param modules
|
|
156
|
+
*/
|
|
157
|
+
protected registerModules(modules: Modules) {
|
|
158
|
+
for (const moduleName in modules) {
|
|
159
|
+
if (Object.prototype.hasOwnProperty.call(modules, moduleName)) {
|
|
160
|
+
this.assertIsValidModuleName(modules, moduleName);
|
|
161
|
+
|
|
162
|
+
log.debug(`Registering module: ${moduleName}`);
|
|
163
|
+
|
|
164
|
+
this.container.register(
|
|
165
|
+
moduleName,
|
|
166
|
+
{ useClass: modules[moduleName] },
|
|
167
|
+
{ lifecycle: Lifecycle.ContainerScoped }
|
|
168
|
+
);
|
|
169
|
+
this.onAfterModuleResolution(moduleName);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Register a non-module value into the current container
|
|
176
|
+
* @param modules
|
|
177
|
+
*/
|
|
178
|
+
// eslint-disable-next-line no-warning-comments
|
|
179
|
+
// TODO Rename to plural since object is param
|
|
180
|
+
public registerValue<Value>(modules: Record<string, Value>) {
|
|
181
|
+
Object.entries(modules).forEach(([moduleName, useValue]) => {
|
|
182
|
+
this.container.register(moduleName, { useValue });
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Provide additional configuration after the ModuleContainer was created.
|
|
188
|
+
*
|
|
189
|
+
* Keep in mind that modules are only decorated once after they are resolved,
|
|
190
|
+
* therefore applying any configuration must happen
|
|
191
|
+
* before the first resolution.
|
|
192
|
+
* @param config
|
|
193
|
+
*/
|
|
194
|
+
public configure(config: ModulesConfig<Modules>) {
|
|
195
|
+
this.definition.config = config;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Resolves a module from the current module container
|
|
200
|
+
*
|
|
201
|
+
* We have to narrow down the `ModuleName` type here to
|
|
202
|
+
* `ResolvableModuleName`, otherwise the resolved value might
|
|
203
|
+
* be any module instance, not the one specifically requested as argument.
|
|
204
|
+
*
|
|
205
|
+
* @param moduleName
|
|
206
|
+
* @returns
|
|
207
|
+
*/
|
|
208
|
+
public resolve<ResolvableModuleName extends StringKeyOf<Modules>>(
|
|
209
|
+
moduleName: ResolvableModuleName
|
|
210
|
+
): InstanceType<Modules[ResolvableModuleName]> {
|
|
211
|
+
return this.container.resolve<InstanceType<Modules[ResolvableModuleName]>>(
|
|
212
|
+
moduleName
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public resolveOrFail<ModuleType>(
|
|
217
|
+
moduleName: string,
|
|
218
|
+
moduleType: TypedClass<ModuleType>
|
|
219
|
+
) {
|
|
220
|
+
const instance = this.container.resolve<ModuleType>(moduleName);
|
|
221
|
+
const isValidModuleInstance = instance instanceof moduleType;
|
|
222
|
+
|
|
223
|
+
if (!isValidModuleInstance) {
|
|
224
|
+
throw new Error("Incompatible module instance");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return instance;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Override this in the child class to provide custom
|
|
232
|
+
* features or module checks
|
|
233
|
+
*/
|
|
234
|
+
protected decorateModule(
|
|
235
|
+
moduleName: StringKeyOf<Modules>,
|
|
236
|
+
containedModule: InstanceType<Modules[StringKeyOf<Modules>]>
|
|
237
|
+
) {
|
|
238
|
+
const config = this.definition.config?.[moduleName];
|
|
239
|
+
|
|
240
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
241
|
+
if (!config) {
|
|
242
|
+
throw errors.configNotSetInContainer(moduleName.toString());
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
containedModule.config = config;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Handle module resolution, e.g. by decorating resolved modules
|
|
250
|
+
* @param moduleName
|
|
251
|
+
*/
|
|
252
|
+
protected onAfterModuleResolution(moduleName: StringKeyOf<Modules>) {
|
|
253
|
+
this.container.afterResolution<InstanceType<Modules[StringKeyOf<Modules>]>>(
|
|
254
|
+
moduleName,
|
|
255
|
+
(containedModuleName, containedModule) => {
|
|
256
|
+
// special case where tsyringe may return multiple known instances (?)
|
|
257
|
+
if (Array.isArray(containedModule)) {
|
|
258
|
+
throw errors.unableToDecorateModule(containedModuleName);
|
|
259
|
+
}
|
|
260
|
+
this.decorateModule(moduleName, containedModule);
|
|
261
|
+
},
|
|
262
|
+
{ frequency: ModuleContainer.moduleDecorationFrequency }
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./config/ModuleContainer";
|
|
2
|
+
export * from "./config/ConfigurableModule";
|
|
3
|
+
export * from "./types";
|
|
4
|
+
export * from "./zkProgrammable/ZkProgrammable";
|
|
5
|
+
export * from "./zkProgrammable/ProvableMethodExecutionContext";
|
|
6
|
+
export * from "./zkProgrammable/provableMethod";
|
|
7
|
+
export * from "./utils";
|
|
8
|
+
// eslint-disable-next-line import/no-unused-modules
|
|
9
|
+
export { default as log } from "loglevel";
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// allows to reference interfaces as 'classes' rather than instances
|
|
2
|
+
export type TypedClass<Class> = new (...args: any[]) => Class;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Using simple `keyof Target` would result into the key
|
|
6
|
+
* being `string | number | symbol`, but we want just a `string`
|
|
7
|
+
*/
|
|
8
|
+
export type StringKeyOf<Target extends object> = Extract<keyof Target, string> &
|
|
9
|
+
string;
|
|
10
|
+
// export type StringKeyOf<Target extends object> = keyof Target
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Utility type to infer element type from an array type
|
|
14
|
+
*/
|
|
15
|
+
export type ArrayElement<ArrayType extends readonly unknown[]> =
|
|
16
|
+
// eslint-disable-next-line putout/putout
|
|
17
|
+
ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { FlexibleProvable, Proof } from "snarkyjs";
|
|
2
|
+
import { singleton } from "tsyringe";
|
|
3
|
+
import uniqueId from "lodash/uniqueId";
|
|
4
|
+
|
|
5
|
+
const errors = {
|
|
6
|
+
moduleOrMethodNameNotSet: () => new Error("Module or method name not set"),
|
|
7
|
+
|
|
8
|
+
proverNotSet: (moduleName: string, methodName: string) =>
|
|
9
|
+
new Error(
|
|
10
|
+
`Prover not set for '${moduleName}.${methodName}', did you forget to decorate your method?`
|
|
11
|
+
),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export class ProvableMethodExecutionResult {
|
|
15
|
+
public moduleName?: string;
|
|
16
|
+
|
|
17
|
+
public methodName?: string;
|
|
18
|
+
|
|
19
|
+
public args?: FlexibleProvable<unknown>[];
|
|
20
|
+
|
|
21
|
+
public prover?: () => Promise<Proof<unknown, unknown>>;
|
|
22
|
+
|
|
23
|
+
public async prove<
|
|
24
|
+
// eslint-disable-next-line etc/no-misused-generics
|
|
25
|
+
ProofType extends Proof<unknown, unknown>
|
|
26
|
+
>(): Promise<ProofType> {
|
|
27
|
+
if (!this.prover) {
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
29
|
+
if (!this.moduleName || !this.methodName) {
|
|
30
|
+
throw errors.moduleOrMethodNameNotSet();
|
|
31
|
+
}
|
|
32
|
+
throw errors.proverNotSet(this.moduleName, this.methodName);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// turn the prover result into the desired proof type
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
37
|
+
return (await this.prover()) as ProofType;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Execution context used to wrap runtime module methods,
|
|
43
|
+
* allowing them to post relevant information (such as execution status)
|
|
44
|
+
* into the context without any unnecessary 'prop drilling'.
|
|
45
|
+
*/
|
|
46
|
+
@singleton()
|
|
47
|
+
export class ProvableMethodExecutionContext {
|
|
48
|
+
public id = uniqueId();
|
|
49
|
+
|
|
50
|
+
public methods: string[] = [];
|
|
51
|
+
|
|
52
|
+
public result: ProvableMethodExecutionResult =
|
|
53
|
+
new ProvableMethodExecutionResult();
|
|
54
|
+
|
|
55
|
+
// eslint-disable-next-line no-warning-comments,max-len
|
|
56
|
+
// TODO See if we should make this class generic, bc I think we can persist the type
|
|
57
|
+
/**
|
|
58
|
+
* Adds a method prover to the current execution context,
|
|
59
|
+
* which can be collected and ran asynchronously at a later point in time.
|
|
60
|
+
*
|
|
61
|
+
* @param prove - Prover function to be ran later,
|
|
62
|
+
* when the method execution needs to be proven
|
|
63
|
+
*/
|
|
64
|
+
public setProver(prover: () => Promise<Proof<unknown, unknown>>) {
|
|
65
|
+
this.result.prover = prover;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Adds a method to the method execution stack, reseting the execution context
|
|
70
|
+
* in a case a new top-level (non nested) method call is made.
|
|
71
|
+
*
|
|
72
|
+
* @param methodName - Name of the method being captured in the context
|
|
73
|
+
*/
|
|
74
|
+
public beforeMethod(
|
|
75
|
+
moduleName: string,
|
|
76
|
+
methodName: string,
|
|
77
|
+
args: FlexibleProvable<unknown>[]
|
|
78
|
+
) {
|
|
79
|
+
if (this.isFinished) {
|
|
80
|
+
this.clear();
|
|
81
|
+
this.result.moduleName = moduleName;
|
|
82
|
+
this.result.methodName = methodName;
|
|
83
|
+
this.result.args = args;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.methods.push(methodName);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Removes the latest method from the execution context stack,
|
|
91
|
+
* keeping track of the amount of 'unfinished' methods. Allowing
|
|
92
|
+
* for the context to distinguish between top-level and nested method calls.
|
|
93
|
+
*/
|
|
94
|
+
public afterMethod() {
|
|
95
|
+
this.methods.pop();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public get isTopLevel() {
|
|
99
|
+
return this.methods.length === 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public get isFinished() {
|
|
103
|
+
return this.methods.length === 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @returns - Current execution context state
|
|
108
|
+
*/
|
|
109
|
+
public current() {
|
|
110
|
+
return {
|
|
111
|
+
isFinished: this.isFinished,
|
|
112
|
+
result: this.result,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Manually clears/resets the execution context
|
|
118
|
+
*/
|
|
119
|
+
public clear() {
|
|
120
|
+
this.result = new ProvableMethodExecutionResult();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Experimental, FlexibleProvablePure, Proof } from "snarkyjs";
|
|
2
|
+
import { Memoize } from "typescript-memoize";
|
|
3
|
+
|
|
4
|
+
import { mockProof } from "./provableMethod";
|
|
5
|
+
|
|
6
|
+
const errors = {
|
|
7
|
+
appChainNotSet: (name: string) =>
|
|
8
|
+
new Error(`Appchain was not injected for: ${name}`),
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export interface CompileArtifact {
|
|
12
|
+
verificationKey: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AreProofsEnabled {
|
|
16
|
+
areProofsEnabled: boolean;
|
|
17
|
+
setProofsEnabled: (areProofsEnabled: boolean) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface Verify<PublicInput, PublicOutput> {
|
|
21
|
+
(proof: Proof<PublicInput, PublicOutput>): Promise<boolean>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Compile {
|
|
25
|
+
(): Promise<CompileArtifact>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PlainZkProgram<PublicInput = undefined, PublicOutput = void> {
|
|
29
|
+
compile: Compile;
|
|
30
|
+
verify: Verify<PublicInput, PublicOutput>;
|
|
31
|
+
Proof: ReturnType<
|
|
32
|
+
typeof Experimental.ZkProgram.Proof<
|
|
33
|
+
FlexibleProvablePure<PublicInput>,
|
|
34
|
+
FlexibleProvablePure<PublicOutput>
|
|
35
|
+
>
|
|
36
|
+
>;
|
|
37
|
+
methods: Record<
|
|
38
|
+
string,
|
|
39
|
+
| ((
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
+
...args: any
|
|
42
|
+
) => Promise<Proof<PublicInput, PublicOutput>>)
|
|
43
|
+
| ((
|
|
44
|
+
publicInput: PublicInput,
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
...args: any
|
|
47
|
+
) => Promise<Proof<PublicInput, PublicOutput>>)
|
|
48
|
+
>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function verifyToMockable<PublicInput, PublicOutput>(
|
|
52
|
+
verify: Verify<PublicInput, PublicOutput>,
|
|
53
|
+
{ areProofsEnabled }: AreProofsEnabled
|
|
54
|
+
) {
|
|
55
|
+
return async (proof: Proof<PublicInput, PublicOutput>) => {
|
|
56
|
+
if (areProofsEnabled) {
|
|
57
|
+
let verified = false;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
verified = await verify(proof);
|
|
61
|
+
} catch (error: unknown) {
|
|
62
|
+
// silently fail verification
|
|
63
|
+
console.error(error);
|
|
64
|
+
verified = false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return verified;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return proof.proof === mockProof;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const mockVerificationKey = "mock-verification-key";
|
|
75
|
+
export function compileToMockable(
|
|
76
|
+
compile: Compile,
|
|
77
|
+
{ areProofsEnabled }: AreProofsEnabled
|
|
78
|
+
): () => Promise<CompileArtifact> {
|
|
79
|
+
return async () => {
|
|
80
|
+
if (areProofsEnabled) {
|
|
81
|
+
return await compile();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
verificationKey: mockVerificationKey,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export abstract class ZkProgrammable<
|
|
91
|
+
PublicInput = undefined,
|
|
92
|
+
PublicOutput = void
|
|
93
|
+
> {
|
|
94
|
+
public abstract get appChain(): AreProofsEnabled | undefined;
|
|
95
|
+
|
|
96
|
+
public abstract zkProgramFactory(): PlainZkProgram<PublicInput, PublicOutput>;
|
|
97
|
+
|
|
98
|
+
@Memoize()
|
|
99
|
+
public get zkProgram(): PlainZkProgram<PublicInput, PublicOutput> {
|
|
100
|
+
const zkProgram = this.zkProgramFactory();
|
|
101
|
+
|
|
102
|
+
if (!this.appChain) {
|
|
103
|
+
throw errors.appChainNotSet(this.constructor.name);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
...zkProgram,
|
|
108
|
+
verify: verifyToMockable(zkProgram.verify, this.appChain),
|
|
109
|
+
compile: compileToMockable(zkProgram.compile, this.appChain),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface WithZkProgrammable<
|
|
115
|
+
PublicInput = undefined,
|
|
116
|
+
PublicOutput = void
|
|
117
|
+
> {
|
|
118
|
+
zkProgrammable: ZkProgrammable<PublicInput, PublicOutput>;
|
|
119
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { FlexibleProvable } from "snarkyjs";
|
|
2
|
+
import { container } from "tsyringe";
|
|
3
|
+
|
|
4
|
+
import { ProvableMethodExecutionContext } from "./ProvableMethodExecutionContext";
|
|
5
|
+
import type { ZkProgrammable } from "./ZkProgrammable";
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line etc/prefer-interface
|
|
8
|
+
export type DecoratedMethod = (...args: unknown[]) => unknown;
|
|
9
|
+
|
|
10
|
+
export const mockProof = "mock-proof";
|
|
11
|
+
|
|
12
|
+
export function toProver(
|
|
13
|
+
methodName: string,
|
|
14
|
+
simulatedMethod: DecoratedMethod,
|
|
15
|
+
isFirstParameterPublicInput: boolean,
|
|
16
|
+
...args: unknown[]
|
|
17
|
+
) {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
return async function prover(this: ZkProgrammable<any, any>) {
|
|
20
|
+
const areProofsEnabled = this.appChain?.areProofsEnabled;
|
|
21
|
+
if (areProofsEnabled ?? false) {
|
|
22
|
+
const programProvableMethod = this.zkProgram.methods[methodName];
|
|
23
|
+
return await Reflect.apply(programProvableMethod, this, args);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// create a mock proof by simulating method execution in JS
|
|
27
|
+
const publicOutput = Reflect.apply(simulatedMethod, this, args);
|
|
28
|
+
|
|
29
|
+
return new this.zkProgram.Proof({
|
|
30
|
+
proof: mockProof,
|
|
31
|
+
|
|
32
|
+
// eslint-disable-next-line no-warning-comments
|
|
33
|
+
// TODO: provide undefined if public input is not used
|
|
34
|
+
publicInput: isFirstParameterPublicInput ? args[0] : undefined,
|
|
35
|
+
publicOutput,
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* We set this to the max possible number, to avoid having
|
|
39
|
+
* to manually count in-circuit proof verifications
|
|
40
|
+
*/
|
|
41
|
+
maxProofsVerified: 2,
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Decorates a provable method on a 'prover class', depending on
|
|
48
|
+
* if proofs are enabled or not, either runs the respective zkProgram prover,
|
|
49
|
+
* or simulates the method execution and issues a mock proof.
|
|
50
|
+
*
|
|
51
|
+
* @param isFirstParameterPublicInput
|
|
52
|
+
* @param executionContext
|
|
53
|
+
* @returns
|
|
54
|
+
*/
|
|
55
|
+
export function provableMethod(
|
|
56
|
+
isFirstParameterPublicInput = true,
|
|
57
|
+
executionContext: ProvableMethodExecutionContext = container.resolve<ProvableMethodExecutionContext>(
|
|
58
|
+
ProvableMethodExecutionContext
|
|
59
|
+
)
|
|
60
|
+
) {
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
|
+
return <Target extends ZkProgrammable<any, any>>(
|
|
63
|
+
target: Target,
|
|
64
|
+
methodName: string,
|
|
65
|
+
descriptor: PropertyDescriptor
|
|
66
|
+
) => {
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
68
|
+
const simulatedMethod = descriptor.value as DecoratedMethod;
|
|
69
|
+
|
|
70
|
+
descriptor.value = function value(
|
|
71
|
+
this: ZkProgrammable<unknown, unknown>,
|
|
72
|
+
...args: FlexibleProvable<unknown>[]
|
|
73
|
+
) {
|
|
74
|
+
const prover = toProver(
|
|
75
|
+
methodName,
|
|
76
|
+
simulatedMethod,
|
|
77
|
+
isFirstParameterPublicInput,
|
|
78
|
+
...args
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
executionContext.beforeMethod(this.constructor.name, methodName, args);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if the method is called at the top level,
|
|
85
|
+
* if yes then create a prover.
|
|
86
|
+
*/
|
|
87
|
+
if (executionContext.isTopLevel) {
|
|
88
|
+
executionContext.setProver(prover.bind(this));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Regardless of if the method is called from the top level
|
|
93
|
+
* or not, execute its simulated (Javascript) version and
|
|
94
|
+
* return the result.
|
|
95
|
+
*/
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/init-declarations
|
|
97
|
+
let result: unknown;
|
|
98
|
+
try {
|
|
99
|
+
result = Reflect.apply(simulatedMethod, this, args);
|
|
100
|
+
} finally {
|
|
101
|
+
executionContext.afterMethod();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return result;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return descriptor;
|
|
108
|
+
};
|
|
109
|
+
}
|