Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use HashInput packing for computing call data #1458

Merged
merged 10 commits into from
Feb 22, 2024
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- `transaction.send()` no longer throws an error if the transaction was not successful for `Mina.LocalBlockchain` and `Mina.Network`. Instead, it returns a `PendingTransaction` object that contains the error. Use `transaction.sendOrThrowIfError` to throw the error if the transaction was not successful.
- `transaction.wait()` no longer throws an error if the transaction was not successful for `Mina.LocalBlockchain` and `Mina.Network`. Instead, it returns either a `IncludedTransaction` or `RejectedTransaction`. Use `transaction.waitOrThrowIfError` to throw the error if the transaction was not successful.
- `transaction.hash()` is no longer a function, it is now a property that returns the hash of the transaction.
- Improved efficiency of computing `AccountUpdate.callData` by packing field elements into as few field elements as possible https://github.com/o1-labs/o1js/pull/1458
- This leads to a large reduction in the number of constraints used when inputs to a zkApp method are many field elements (e.g. a long list of `Bool`s)

### Added

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"prepublish:web": "NODE_ENV=production node src/build/build-web.js",
"prepublish:node": "node src/build/copy-artifacts.js && rimraf ./dist/node && npx tsc -p tsconfig.node.json && node src/build/copy-to-dist.js && NODE_ENV=production node src/build/build-node.js",
"prepublishOnly": "npm run prepublish:web && npm run prepublish:node",
"dump-vks": "./run tests/vk-regression/vk-regression.ts --bundle --dump",
"dump-vks": "npm run build && ./run tests/vk-regression/vk-regression.ts --bundle --dump",
"format": "prettier --write --ignore-unknown **/*",
"clean": "rimraf ./dist && rimraf ./src/bindings/compiled/_node_bindings",
"clean-all": "npm run clean && rimraf ./tests/report && rimraf ./tests/test-artifacts",
Expand Down
10 changes: 10 additions & 0 deletions src/lib/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export {
packToFields,
emptyReceiptChainHash,
hashConstant,
isHashable,
};

type Hashable<T> = { toInput: (x: T) => HashInput; empty: () => T };
Expand Down Expand Up @@ -180,6 +181,15 @@ function packToFields({ fields = [], packed = [] }: HashInput) {
return fields.concat(packedBits);
}

function isHashable<T>(obj: any): obj is Hashable<T> {
if (!obj) {
return false;
}
const hasToInput = 'toInput' in obj && typeof obj.toInput === 'function';
const hasEmpty = 'empty' in obj && typeof obj.empty === 'function';
return hasToInput && hasEmpty;
}

const TokenSymbolPure: ProvableExtended<
{ symbol: string; field: Field },
string
Expand Down
14 changes: 14 additions & 0 deletions src/lib/scalar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,20 @@ class Scalar {
return Scalar.toFields(this);
}

/**
* **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer.
*
* This function is the implementation of `ProvableExtended.toInput()` for the {@link Scalar} type.
*
* @param value - The {@link Scalar} element to get the `input` array.
*
* @return An object where the `fields` key is a {@link Field} array of length 1 created from this {@link Field}.
*
*/
static toInput(x: Scalar): { packed: [Field, number][] } {
return { packed: Scalar.toFields(x).map((f) => [f, 1]) };
}

/**
* Part of the {@link Provable} interface.
*
Expand Down
34 changes: 27 additions & 7 deletions src/lib/zkapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ import {
} from './circuit-value.js';
import { Provable, getBlindingValue, memoizationContext } from './provable.js';
import * as Encoding from '../bindings/lib/encoding.js';
import { Poseidon, hashConstant } from './hash.js';
import {
HashInput,
Poseidon,
hashConstant,
isHashable,
packToFields,
} from './hash.js';
import { UInt32, UInt64 } from './int.js';
import * as Mina from './mina.js';
import {
Expand Down Expand Up @@ -479,16 +485,30 @@ function computeCallData(
) {
let { returnType, methodName } = methodIntf;
let args = methodArgumentTypesAndValues(methodIntf, argumentValues);
let argSizesAndFields: Field[][] = args.map(({ type, value }) => [
Field(type.sizeInFields()),
...type.toFields(value),
]);

let input: HashInput = { fields: [], packed: [] };
for (let { type, value } of args) {
if (isHashable(type)) {
input = HashInput.append(input, type.toInput(value));
} else {
input.fields!.push(
...[Field(type.sizeInFields()), ...type.toFields(value)]
);
}
}
const totalArgFields = packToFields(input);
let totalArgSize = Field(
args.map(({ type }) => type.sizeInFields()).reduce((s, t) => s + t, 0)
);
let totalArgFields = argSizesAndFields.flat();

let returnSize = Field(returnType?.sizeInFields() ?? 0);
let returnFields = returnType?.toFields(returnValue) ?? [];
input = { fields: [], packed: [] };
if (isHashable(returnType)) {
input = HashInput.append(input, returnType.toInput(returnValue));
} else {
input.fields!.push(...(returnType?.toFields(returnValue) ?? []));
}
let returnFields = packToFields(input);
let methodNameFields = Encoding.stringToFields(methodName);
return [
// we have to encode the sizes of arguments / return value, so that fields can't accidentally shift
Expand Down
Loading
Loading