@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.
Files changed (112) hide show
  1. package/LICENSE.md +201 -0
  2. package/dist/Constants.d.ts +4 -0
  3. package/dist/Constants.d.ts.map +1 -0
  4. package/dist/Constants.js +3 -0
  5. package/dist/config/ConfigurableModule.d.ts +21 -0
  6. package/dist/config/ConfigurableModule.d.ts.map +1 -0
  7. package/dist/config/ConfigurableModule.js +19 -0
  8. package/dist/config/ConfigurationAggregator.d.ts +10 -0
  9. package/dist/config/ConfigurationAggregator.d.ts.map +1 -0
  10. package/dist/config/ConfigurationAggregator.js +35 -0
  11. package/dist/config/ConfigurationReceiver.d.ts +25 -0
  12. package/dist/config/ConfigurationReceiver.d.ts.map +1 -0
  13. package/dist/config/ConfigurationReceiver.js +36 -0
  14. package/dist/config/ModuleContainer.d.ts +103 -0
  15. package/dist/config/ModuleContainer.d.ts.map +1 -0
  16. package/dist/config/ModuleContainer.js +163 -0
  17. package/dist/config/types.d.ts +2 -0
  18. package/dist/config/types.d.ts.map +1 -0
  19. package/dist/config/types.js +1 -0
  20. package/dist/index.d.ts +9 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +9 -0
  23. package/dist/model/MethodPublicInput.d.ts +51 -0
  24. package/dist/model/MethodPublicInput.d.ts.map +1 -0
  25. package/dist/model/MethodPublicInput.js +11 -0
  26. package/dist/model/Option.d.ts +89 -0
  27. package/dist/model/Option.d.ts.map +1 -0
  28. package/dist/model/Option.js +86 -0
  29. package/dist/model/Path.d.ts +31 -0
  30. package/dist/model/Path.d.ts.map +1 -0
  31. package/dist/model/Path.js +44 -0
  32. package/dist/model/StateTransition.d.ts +85 -0
  33. package/dist/model/StateTransition.d.ts.map +1 -0
  34. package/dist/model/StateTransition.js +58 -0
  35. package/dist/model/StateTransitionProvableBatch.d.ts +56 -0
  36. package/dist/model/StateTransitionProvableBatch.d.ts.map +1 -0
  37. package/dist/model/StateTransitionProvableBatch.js +20 -0
  38. package/dist/prover/block/BlockProver.d.ts +199 -0
  39. package/dist/prover/block/BlockProver.d.ts.map +1 -0
  40. package/dist/prover/block/BlockProver.js +119 -0
  41. package/dist/prover/block/BlockScopedModule.d.ts +3 -0
  42. package/dist/prover/block/BlockScopedModule.d.ts.map +1 -0
  43. package/dist/prover/block/BlockScopedModule.js +6 -0
  44. package/dist/prover/statetransition/StateTransitionProver.d.ts +92 -0
  45. package/dist/prover/statetransition/StateTransitionProver.d.ts.map +1 -0
  46. package/dist/prover/statetransition/StateTransitionProver.js +127 -0
  47. package/dist/prover/statetransition/StateTransitionWitnessProvider.d.ts +16 -0
  48. package/dist/prover/statetransition/StateTransitionWitnessProvider.d.ts.map +1 -0
  49. package/dist/prover/statetransition/StateTransitionWitnessProvider.js +17 -0
  50. package/dist/src/model/Option.d.ts +158 -0
  51. package/dist/src/model/Option.d.ts.map +1 -0
  52. package/dist/src/model/Option.js +53 -0
  53. package/dist/src/model/Path.d.ts +35 -0
  54. package/dist/src/model/Path.d.ts.map +1 -0
  55. package/dist/src/model/Path.js +51 -0
  56. package/dist/src/model/StateTransition.d.ts +201 -0
  57. package/dist/src/model/StateTransition.d.ts.map +1 -0
  58. package/dist/src/model/StateTransition.js +43 -0
  59. package/dist/src/utils/PrefixedHashList.d.ts +15 -0
  60. package/dist/src/utils/PrefixedHashList.d.ts.map +1 -0
  61. package/dist/src/utils/PrefixedHashList.js +28 -0
  62. package/dist/src/utils/ProvableHashList.d.ts +30 -0
  63. package/dist/src/utils/ProvableHashList.d.ts.map +1 -0
  64. package/dist/src/utils/ProvableHashList.js +43 -0
  65. package/dist/types.d.ts +11 -0
  66. package/dist/types.d.ts.map +1 -0
  67. package/dist/types.js +1 -0
  68. package/dist/utils/PrefixedHashList.d.ts +14 -0
  69. package/dist/utils/PrefixedHashList.d.ts.map +1 -0
  70. package/dist/utils/PrefixedHashList.js +12 -0
  71. package/dist/utils/PrefixedProvableHashList.d.ts +8 -0
  72. package/dist/utils/PrefixedProvableHashList.d.ts.map +1 -0
  73. package/dist/utils/PrefixedProvableHashList.js +12 -0
  74. package/dist/utils/ProvableHashList.d.ts +26 -0
  75. package/dist/utils/ProvableHashList.d.ts.map +1 -0
  76. package/dist/utils/ProvableHashList.js +35 -0
  77. package/dist/utils/Utils.d.ts +22 -0
  78. package/dist/utils/Utils.d.ts.map +1 -0
  79. package/dist/utils/Utils.js +41 -0
  80. package/dist/utils/merkletree/MemoryMerkleTreeStorage.d.ts +26 -0
  81. package/dist/utils/merkletree/MemoryMerkleTreeStorage.d.ts.map +1 -0
  82. package/dist/utils/merkletree/MemoryMerkleTreeStorage.js +79 -0
  83. package/dist/utils/merkletree/RollupMerkleTree.d.ts +143 -0
  84. package/dist/utils/merkletree/RollupMerkleTree.d.ts.map +1 -0
  85. package/dist/utils/merkletree/RollupMerkleTree.js +246 -0
  86. package/dist/utils.d.ts +2 -0
  87. package/dist/utils.d.ts.map +1 -0
  88. package/dist/utils.js +7 -0
  89. package/dist/zkProgrammable/ProvableMethodExecutionContext.d.ts +53 -0
  90. package/dist/zkProgrammable/ProvableMethodExecutionContext.d.ts.map +1 -0
  91. package/dist/zkProgrammable/ProvableMethodExecutionContext.js +98 -0
  92. package/dist/zkProgrammable/ZkProgrammable.d.ts +32 -0
  93. package/dist/zkProgrammable/ZkProgrammable.d.ts.map +1 -0
  94. package/dist/zkProgrammable/ZkProgrammable.js +60 -0
  95. package/dist/zkProgrammable/provableMethod.d.ts +16 -0
  96. package/dist/zkProgrammable/provableMethod.d.ts.map +1 -0
  97. package/dist/zkProgrammable/provableMethod.js +69 -0
  98. package/jest.config.cjs +1 -0
  99. package/package.json +35 -0
  100. package/src/config/ConfigurableModule.ts +44 -0
  101. package/src/config/ModuleContainer.ts +265 -0
  102. package/src/index.ts +9 -0
  103. package/src/types.ts +17 -0
  104. package/src/utils.ts +10 -0
  105. package/src/zkProgrammable/ProvableMethodExecutionContext.ts +122 -0
  106. package/src/zkProgrammable/ZkProgrammable.ts +119 -0
  107. package/src/zkProgrammable/provableMethod.ts +109 -0
  108. package/test/config/ModuleContainer.test.ts +82 -0
  109. package/test/tsconfig.json +4 -0
  110. package/test/zkProgrammable/ZkProgrammable.test.ts +283 -0
  111. package/tsconfig.json +8 -0
  112. 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,10 @@
1
+ export function requireTrue(
2
+ condition: boolean,
3
+ errorOrFunction: Error | (() => Error)
4
+ ): void {
5
+ if (!condition) {
6
+ throw typeof errorOrFunction === "function"
7
+ ? errorOrFunction()
8
+ : errorOrFunction;
9
+ }
10
+ }
@@ -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
+ }