o1js
Version:
TypeScript framework for zk-SNARKs and zkApps
419 lines (418 loc) • 19.4 kB
TypeScript
import 'reflect-metadata';
import { Gate, Pickles } from '../../../bindings.js';
import { Field, Bool } from '../../provable/wrapped.js';
import { AccountUpdate, ZkappPublicInput, AccountUpdateForest, AccountUpdateTree } from './account-update.js';
import { FlexibleProvablePure, InferProvable } from '../../provable/types/struct.js';
import { Provable } from '../../provable/provable.js';
import { UInt32, UInt64 } from '../../provable/int.js';
import { MethodInterface } from '../../proof-system/zkprogram.js';
import { Proof, ProofClass } from '../../proof-system/proof.js';
import { PublicKey } from '../../provable/crypto/signature.js';
import { Cache } from '../../proof-system/cache.js';
import { SmartContractBase } from './smart-contract-base.js';
import { ProvablePure, ProvableType } from '../../provable/types/provable-intf.js';
export { SmartContract, method, DeployArgs, declareMethods };
/**
* A decorator to use in a zkApp to mark a method as provable.
* You can use inside your zkApp class as:
*
* ```
* \@method async myMethod(someArg: Field) {
* // your code here
* }
* ```
*
* To return a value from the method, you have to explicitly declare the return type using the {@link method.returns} decorator:
* ```
* \@method.returns(Field)
* async myMethod(someArg: Field): Promise<Field> {
* // your code here
* }
* ```
*/
declare function method<K extends string, T extends SmartContract>(target: T & {
[k in K]: (...args: any) => Promise<void>;
}, methodName: K & string & keyof T, descriptor: PropertyDescriptor, returnType?: Provable<any>): void;
declare namespace method {
var returns: <K extends string, T extends SmartContract, R extends ProvableType>(returnType: R) => (target: T & { [k in K]: (...args: any) => Promise<InferProvable<R>>; }, methodName: K & string & keyof T, descriptor: PropertyDescriptor) => void;
}
/**
* The main zkapp class. To write a zkapp, extend this class as such:
*
* ```
* class YourSmartContract extends SmartContract {
* // your smart contract code here
* }
* ```
*
*/
declare class SmartContract extends SmartContractBase {
#private;
address: PublicKey;
tokenId: Field;
static _methods?: MethodInterface[];
static _methodMetadata?: Record<string, {
actions: number;
rows: number;
digest: string;
gates: Gate[];
proofs: ProofClass[];
}>;
static _provers?: Pickles.Prover[];
static _verificationKey?: {
data: string;
hash: Field;
};
/**
* Returns a Proof type that belongs to this {@link SmartContract}.
*/
static Proof(): {
new ({ proof, publicInput, publicOutput, maxProofsVerified, }: {
proof: unknown;
publicInput: ZkappPublicInput;
publicOutput: undefined;
maxProofsVerified: 0 | 2 | 1;
}): {
verify(): void;
verifyIf(condition: import("../../provable/bool.js").Bool): void;
publicInput: ZkappPublicInput;
publicOutput: undefined;
proof: unknown;
maxProofsVerified: 0 | 2 | 1;
shouldVerify: import("../../provable/bool.js").Bool;
declare(): boolean;
toJSON(): import("../../proof-system/zkprogram.js").JsonProof;
publicFields(): {
input: import("../../provable/field.js").Field[];
output: import("../../provable/field.js").Field[];
};
};
publicInputType: Omit<import("../../provable/types/provable-intf.js").Provable<{
accountUpdate: import("../../provable/field.js").Field;
calls: import("../../provable/field.js").Field;
}, {
accountUpdate: bigint;
calls: bigint;
}>, "fromFields"> & {
fromFields: (fields: import("../../provable/field.js").Field[]) => {
accountUpdate: import("../../provable/field.js").Field;
calls: import("../../provable/field.js").Field;
};
} & {
toInput: (x: {
accountUpdate: import("../../provable/field.js").Field;
calls: import("../../provable/field.js").Field;
}) => {
fields?: import("../../provable/field.js").Field[] | undefined;
packed?: [import("../../provable/field.js").Field, number][] | undefined;
};
toJSON: (x: {
accountUpdate: import("../../provable/field.js").Field;
calls: import("../../provable/field.js").Field;
}) => {
accountUpdate: string;
calls: string;
};
fromJSON: (x: {
accountUpdate: string;
calls: string;
}) => {
accountUpdate: import("../../provable/field.js").Field;
calls: import("../../provable/field.js").Field;
};
empty: () => {
accountUpdate: import("../../provable/field.js").Field;
calls: import("../../provable/field.js").Field;
};
};
publicOutputType: import("../../provable/types/struct.js").ProvablePureExtended<undefined, undefined, null>;
tag: () => typeof SmartContract;
fromJSON<S extends import("../../util/types.js").Subclass<typeof Proof>>(this: S, { maxProofsVerified, proof: proofString, publicInput: publicInputJson, publicOutput: publicOutputJson, }: import("../../proof-system/zkprogram.js").JsonProof): Promise<Proof<InferProvable<S["publicInputType"]>, InferProvable<S["publicOutputType"]>>>;
dummy<Input, OutPut>(publicInput: Input, publicOutput: OutPut, maxProofsVerified: 0 | 2 | 1, domainLog2?: number): Promise<Proof<Input, OutPut>>;
readonly provable: {
toFields: (value: Proof<any, any>) => import("../../provable/field.js").Field[];
toAuxiliary: (value?: Proof<any, any> | undefined) => any[];
fromFields: (fields: import("../../provable/field.js").Field[], aux: any[]) => Proof<any, any>;
sizeInFields(): number;
check: (value: Proof<any, any>) => void;
toValue: (x: Proof<any, any>) => import("../../proof-system/proof.js").ProofValue<any, any>;
fromValue: (x: Proof<any, any> | import("../../proof-system/proof.js").ProofValue<any, any>) => Proof<any, any>;
toCanonical?: ((x: Proof<any, any>) => Proof<any, any>) | undefined;
};
publicFields(value: import("../../proof-system/proof.js").ProofBase<any, any>): {
input: import("../../provable/field.js").Field[];
output: import("../../provable/field.js").Field[];
};
_proofFromBase64(proofString: string, maxProofsVerified: 0 | 2 | 1): unknown;
_proofToBase64(proof: unknown, maxProofsVerified: 0 | 2 | 1): string;
};
constructor(address: PublicKey, tokenId?: Field);
/**
* Compile your smart contract.
*
* This generates both the prover functions, needed to create proofs for running `@method`s,
* and the verification key, needed to deploy your zkApp.
*
* Although provers and verification key are returned by this method, they are also cached internally and used when needed,
* so you don't actually have to use the return value of this function.
*
* Under the hood, "compiling" means calling into the lower-level [Pickles and Kimchi libraries](https://o1-labs.github.io/proof-systems/kimchi/overview.html) to
* create multiple prover & verifier indices (one for each smart contract method as part of a "step circuit" and one for the "wrap circuit" which recursively wraps
* it so that proofs end up in the original finite field). These are fairly expensive operations, so **expect compiling to take at least 20 seconds**,
* up to several minutes if your circuit is large or your hardware is not optimal for these operations.
*/
static compile({ cache, forceRecompile }?: {
cache?: Cache | undefined;
forceRecompile?: boolean | undefined;
}): Promise<{
verificationKey: {
data: string;
hash: import("../../provable/field.js").Field;
};
provers: Pickles.Prover[];
verify: (statement: Pickles.Statement<import("../../provable/core/fieldvar.js").FieldConst>, proof: unknown) => Promise<boolean>;
}>;
/**
* Computes a hash of your smart contract, which will reliably change _whenever one of your method circuits changes_.
* This digest is quick to compute. it is designed to help with deciding whether a contract should be re-compiled or
* a cached verification key can be used.
* @returns the digest, as a hex string
*/
static digest(): Promise<string>;
/**
* The maximum number of proofs that are verified by any of the zkApp methods.
* This is an internal parameter needed by the proof system.
*/
static getMaxProofsVerified(): Promise<0 | 2 | 1>;
/**
* Manually set the verification key.
*/
static setVerificationKeyUnsafe(verificationKey: {
data: string;
hash: Field | string;
}): void;
/**
* Deploys a {@link SmartContract}.
*
* ```ts
* let tx = await Mina.transaction(sender, async () => {
* AccountUpdate.fundNewAccount(sender);
* await zkapp.deploy();
* });
* tx.sign([senderKey, zkAppKey]);
* ```
*/
deploy({ verificationKey, }?: {
verificationKey?: {
data: string;
hash: Field | string;
};
}): Promise<void>;
/**
* `SmartContract.init()` will be called only when a {@link SmartContract} will be first deployed, not for redeployment.
* This method can be overridden as follows
* ```
* class MyContract extends SmartContract {
* init() {
* super.init();
* this.account.permissions.set(...);
* this.x.set(Field(1));
* }
* }
* ```
*/
init(): void;
/**
* Use this command if the account update created by this SmartContract should be signed by the account owner,
* instead of authorized with a proof.
*
* Note that the smart contract's {@link Permissions} determine which updates have to be (can be) authorized by a signature.
*
* If you only want to avoid creating proofs for quicker testing, we advise you to
* use `LocalBlockchain({ proofsEnabled: false })` instead of `requireSignature()`. Setting
* `proofsEnabled` to `false` allows you to test your transactions with the same authorization flow as in production,
* with the only difference being that quick mock proofs are filled in instead of real proofs.
*/
requireSignature(): void;
/**
* Use this command if the account update created by this SmartContract should have no authorization on it,
* instead of being authorized with a proof.
*
* WARNING: This is a method that should rarely be useful. If you want to disable proofs for quicker testing, take a look
* at `LocalBlockchain({ proofsEnabled: false })`, which causes mock proofs to be created and doesn't require changing the
* authorization flow.
*/
skipAuthorization(): void;
/**
* Returns the current {@link AccountUpdate} associated to this {@link SmartContract}.
*/
get self(): AccountUpdate;
/**
* Same as `SmartContract.self` but explicitly creates a new {@link AccountUpdate}.
*/
newSelf(methodName?: string): AccountUpdate;
sender: {
self: SmartContract;
/**
* The public key of the current transaction's sender account.
*
* Throws an error if not inside a transaction, or the sender wasn't passed in.
*
* **Warning**: The fact that this public key equals the current sender is not part of the proof.
* A malicious prover could use any other public key without affecting the validity of the proof.
*
* Consider using `this.sender.getAndRequireSignature()` if you need to prove that the sender controls this account.
*/
getUnconstrained(): PublicKey;
/**
* Return a public key that is forced to sign this transaction.
*
* Note: This doesn't prove that the return value is the transaction sender, but it proves that whoever created
* the transaction controls the private key associated with the returned public key.
*/
getAndRequireSignature(): PublicKey;
};
/**
* Current account of the {@link SmartContract}.
*/
get account(): import("./precondition.js").Account;
/**
* Current network state of the {@link SmartContract}.
*/
get network(): import("./precondition.js").Network;
/**
* Current global slot on the network. This is the slot at which this transaction is included in a block. Since we cannot know this value
* at the time of transaction construction, this only has the `assertBetween()` method but no `get()` (impossible to implement)
* or `assertEquals()` (confusing, because the developer can't know the exact slot at which this will be included either)
*/
get currentSlot(): import("./precondition.js").CurrentSlot;
/**
* Approve an account update or tree / forest of updates. Doing this means you include the account update in the zkApp's public input,
* which allows you to read and use its content in a proof, make assertions about it, and modify it.
*
* ```ts
* `@method` myApprovingMethod(update: AccountUpdate) {
* this.approve(update);
*
* // read balance on the account (for example)
* let balance = update.account.balance.getAndRequireEquals();
* }
* ```
*
* Under the hood, "approving" just means that the account update is made a child of the zkApp in the
* tree of account updates that forms the transaction. Similarly, if you pass in an {@link AccountUpdateTree},
* the entire tree will become a subtree of the zkApp's account update.
*
* Passing in a forest is a bit different, because it means you set the entire children of the zkApp's account update
* at once. `approve()` will fail if the zkApp's account update already has children, to prevent you from accidentally
* excluding important information from the public input.
*/
approve(update: AccountUpdate | AccountUpdateTree | AccountUpdateForest): void;
send(args: {
to: PublicKey | AccountUpdate | SmartContract;
amount: number | bigint | UInt64;
}): AccountUpdate;
/**
* Balance of this {@link SmartContract}.
*/
get balance(): {
addInPlace(x: string | number | bigint | UInt64 | UInt32 | import("../../provable/int.js").Int64): void;
subInPlace(x: string | number | bigint | UInt64 | UInt32 | import("../../provable/int.js").Int64): void;
};
/**
* A list of event types that can be emitted using this.emitEvent()`.
*/
events: {
[key: string]: FlexibleProvablePure<any>;
};
/**
* Conditionally emits an event.
*
* Events will be emitted as a part of the transaction and can be collected by archive nodes.
*/
emitEventIf<K extends keyof this['events']>(condition: Bool, type: K, event: any): void;
/**
* Emits an event. Events will be emitted as a part of the transaction and can be collected by archive nodes.
*/
emitEvent<K extends keyof this['events']>(type: K, event: any): void;
/**
* Asynchronously fetches events emitted by this {@link SmartContract} and returns an array of events with their corresponding types.
* @param [start=UInt32.from(0)] - The start height of the events to fetch.
* @param end - The end height of the events to fetch. If not provided, fetches events up to the latest height.
* @returns A promise that resolves to an array of objects, each containing the event type and event data for the specified range.
* @throws If there is an error fetching events from the Mina network.
* @example
* const startHeight = UInt32.from(1000);
* const endHeight = UInt32.from(2000);
* const events = await myZkapp.fetchEvents(startHeight, endHeight);
* console.log(events);
*/
fetchEvents(start?: UInt32, end?: UInt32): Promise<{
type: string;
event: {
data: ProvablePure<any>;
transactionInfo: {
transactionHash: string;
transactionStatus: string;
transactionMemo: string;
};
};
blockHeight: UInt32;
blockHash: string;
parentBlockHash: string;
globalSlot: UInt32;
chainStatus: string;
}[]>;
static runOutsideCircuit(run: () => void): void;
/**
* This function is run internally before compiling a smart contract, to collect metadata about what each of your
* smart contract methods does.
*
* For external usage, this function can be handy because calling it involves running all methods in the same "mode" as `compile()` does,
* so it serves as a quick-to-run check for whether your contract can be compiled without errors, which can greatly speed up iterating.
*
* `analyzeMethods()` will also return the number of `rows` of each of your method circuits (i.e., the number of constraints in the underlying proof system),
* which is a good indicator for circuit size and the time it will take to create proofs.
* To inspect the created circuit in detail, you can look at the returned `gates`.
*
* Note: If this function was already called before, it will short-circuit and just return the metadata collected the first time.
*
* @returns an object, keyed by method name, each entry containing:
* - `rows` the size of the constraint system created by this method
* - `digest` a digest of the method circuit
* - `actions` the number of actions the method dispatches
* - `gates` the constraint system, represented as an array of gates
*/
static analyzeMethods({ printSummary }?: {
printSummary?: boolean | undefined;
}): Promise<Record<string, {
actions: number;
rows: number;
digest: string;
gates: Gate[];
proofs: ProofClass[];
}>>;
}
type DeployArgs = {
verificationKey?: {
data: string;
hash: string | Field;
};
} | undefined;
/**
* `declareMethods` can be used in place of the `@method` decorator
* to declare SmartContract methods along with their list of arguments.
* It should be placed _after_ the class declaration.
* Here is an example of declaring a method `update`, which takes a single argument of type `Field`:
* ```ts
* class MyContract extends SmartContract {
* // ...
* update(x: Field) {
* // ...
* }
* }
* declareMethods(MyContract, { update: [Field] }); // `[Field]` is the list of arguments!
* ```
* Note that a method of the same name must still be defined on the class, just without the decorator.
*/
declare function declareMethods<T extends typeof SmartContract>(SmartContract: T, methodArguments: Record<string, Provable<unknown>[]>): void;