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

Hash inputs in JS #310

Merged
merged 22 commits into from
Aug 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MINA_COMMIT
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
The mina commit used to generate the backends for node and chrome is
122d163109df05c7295983ebe187e2ad898956a0
ed1244c761f957ebf99652f28b29411e434048f7
2 changes: 1 addition & 1 deletion src/build/jsLayoutToTypes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function writeTsContent(types, isJson) {
let output = '';
let dependencies = new Set();
let converters = {};
let exports = new Set();
let exports = new Set(isJson ? [] : ['customTypes']);
for (let [Type, value] of Object.entries(types)) {
let inner = writeType(value, isJson);
exports.add(Type);
Expand Down
Binary file modified src/chrome_bindings/plonk_wasm_bg.wasm
Binary file not shown.
128 changes: 64 additions & 64 deletions src/chrome_bindings/snarky_js_chrome.bc.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/examples/simple_zkapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {
Experimental,
} from 'snarkyjs';

const doProofs = true;

await isReady;

class SimpleZkapp extends SmartContract {
Expand Down Expand Up @@ -77,8 +79,6 @@ class SimpleZkapp extends SmartContract {
}
}

const doProofs = true;

let Local = Mina.LocalBlockchain();
Mina.setActiveInstance(Local);

Expand Down
179 changes: 179 additions & 0 deletions src/examples/to-hash-input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a sort of unit test, which is also run in Mina CI. I left it here just because for the Mina version, I had to remove the TS types, and it's easier to read & modify with the types in place

isReady,
Party,
PrivateKey,
Types,
Field,
Ledger,
UInt64,
UInt32,
Experimental,
Bool,
Permissions,
Sign,
Token,
} from 'snarkyjs';

await isReady;

let { asFieldsAndAux, jsLayout, packToFields } = Experimental;

let party = Party.defaultParty(PrivateKey.random().toPublicKey());

// types
type Body = Types.Party['body'];
type Update = Body['update'];
type Timing = Update['timing']['value'];
type AccountPrecondition = Body['preconditions']['account'];
type NetworkPrecondition = Body['preconditions']['network'];

// timing
let Timing = asFieldsAndAux<Timing, any>(
jsLayout.Party.entries.body.entries.update.entries.timing.inner
);
let timing = party.body.update.timing.value;
timing.initialMinimumBalance = UInt64.one;
timing.vestingPeriod = UInt32.one;
timing.vestingIncrement = UInt64.from(2);
testInput(Timing, Ledger.hashInputFromJson.timing, timing);

// permissions
let Permissions_ = asFieldsAndAux<Permissions, any>(
jsLayout.Party.entries.body.entries.update.entries.permissions.inner
);
let permissions = party.body.update.permissions;
permissions.isSome = Bool(true);
permissions.value = {
...Permissions.default(),
setVerificationKey: Permissions.none(),
setPermissions: Permissions.none(),
receive: Permissions.proof(),
};
testInput(
Permissions_,
Ledger.hashInputFromJson.permissions,
permissions.value
);

// update
let Update = asFieldsAndAux<Update, any>(
jsLayout.Party.entries.body.entries.update
);
let update = party.body.update;

update.timing.isSome = Bool(true);
update.appState[0].isSome = Bool(true);
update.appState[0].value = Field(9);
update.delegate.isSome = Bool(true);
let delegate = PrivateKey.random().toPublicKey();
update.delegate.value = delegate;

party.tokenSymbol.set('BLABLA');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hahaha! This token symbol is funny.

testInput(Update, Ledger.hashInputFromJson.update, update);

// account precondition
let AccountPrecondition = asFieldsAndAux<AccountPrecondition, any>(
jsLayout.Party.entries.body.entries.preconditions.entries.account
);
let account = party.body.preconditions.account;
party.account.balance.assertEquals(UInt64.from(1e9));
party.account.isNew.assertEquals(Bool(true));
party.account.delegate.assertEquals(delegate);
account.state[0].isSome = Bool(true);
account.state[0].value = Field(9);
testInput(
AccountPrecondition,
Ledger.hashInputFromJson.accountPrecondition,
account
);

// network precondition
let NetworkPrecondition = asFieldsAndAux<NetworkPrecondition, any>(
jsLayout.Party.entries.body.entries.preconditions.entries.network
);
let network = party.body.preconditions.network;
party.network.stakingEpochData.ledger.hash.assertEquals(Field.random());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does invoking assertEquals on a random Field element do here?

Copy link
Contributor Author

@mitschabaude mitschabaude Aug 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It changes a precondition on the Party. I'm trying to add as much changes to the party here as possible, to not miss a part of it where the hashing logic doesn't agree between JS and OCaml (if I were just using the default party that I start out with, the hash input would be mostly zeros -- so two of those zeros could be switched and we wouldn't notice)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok that makes sense.

party.network.nextEpochData.lockCheckpoint.assertEquals(Field.random());

testInput(
NetworkPrecondition,
Ledger.hashInputFromJson.networkPrecondition,
network
);

// body
let Body = asFieldsAndAux<Body, any>(jsLayout.Party.entries.body);
let body = party.body;
body.balanceChange.magnitude = UInt64.from(14197832);
body.balanceChange.sgn = Sign.minusOne;
body.callData = Field.random();
body.callDepth = 1;
body.incrementNonce = Bool(true);
let tokenOwner = PrivateKey.random().toPublicKey();
body.tokenId = new Token({ tokenOwner }).id;
body.caller = body.tokenId;
testInput(Body, Ledger.hashInputFromJson.body, body);

// party (should be same as body)
testInput(
Types.Party,
(partyJson) =>
Ledger.hashInputFromJson.body(JSON.stringify(JSON.parse(partyJson).body)),
party
);

console.log('all hash inputs are consistent! 🎉');

function testInput<T>(
Module: Experimental.AsFieldsAndAux<T, any>,
toInputOcaml: (json: string) => InputOcaml,
value: T
) {
let json = Module.toJson(value);
// console.log(json);
let input1 = inputFromOcaml(toInputOcaml(JSON.stringify(json)));
let input2 = Module.toInput(value);
// console.log('snarkyjs', JSON.stringify(input2));
// console.log();
// console.log('protocol', JSON.stringify(input1));
let ok1 = JSON.stringify(input2) === JSON.stringify(input1);
// console.log('ok?', ok1);
let fields1 = Ledger.hashInputFromJson.packInput(inputToOcaml(input1));
let fields2 = packToFields(input2);
let ok2 = JSON.stringify(fields1) === JSON.stringify(fields2);
// console.log('packed ok?', ok2);
// console.log();
if (!ok1 || !ok2) {
throw Error('inconsistent toInput');
}
}

type InputOcaml = {
fields: Field[];
packed: { field: Field; size: number }[];
};

function inputFromOcaml({
fields,
packed,
}: {
fields: Field[];
packed: { field: Field; size: number }[];
}) {
return {
fields,
packed: packed.map(({ field, size }) => [field, size] as [Field, number]),
};
}
function inputToOcaml({
fields,
packed,
}: {
fields: Field[];
packed: [field: Field, size: number][];
}) {
return {
fields,
packed: packed.map(([field, size]) => ({ field, size })),
};
}
21 changes: 19 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export {
circuitMain,
circuitValue,
} from './lib/circuit_value';
export * from './lib/int';
export { UInt32, UInt64, Int64, Sign } from './lib/int';
export { Types } from './snarky/types';

export * as Mina from './lib/mina';
Expand Down Expand Up @@ -59,10 +59,27 @@ export { Character, CircuitString } from './lib/string';
import { Reducer } from './lib/zkapp';
import { createChildParty } from './lib/party';
import { memoizeWitness } from './lib/circuit_value';
import {
jsLayout,
asFieldsAndAux,
AsFieldsAndAux as AsFieldsAndAux_,
} from './snarky/types';
import { packToFields } from './lib/hash';
export { Experimental };

/**
* This module exposes APIs that are unstable, in the sense that the API surface is expected to change.
* (Not unstable in the sense that they are less functional or tested than other parts.)
*/
const Experimental = { Reducer, createChildParty, memoizeWitness };
const Experimental = {
Reducer,
createChildParty,
memoizeWitness,
// TODO: for testing, maybe remove later
jsLayout,
asFieldsAndAux,
packToFields,
};
namespace Experimental {
export type AsFieldsAndAux<T, TJson> = AsFieldsAndAux_<T, TJson>;
}
13 changes: 7 additions & 6 deletions src/lib/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ function toPermission(p: AuthRequired): Permission {
type FetchedAccount = {
publicKey: string;
nonce: string;
tokenId: string;
token: string;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file has some unrelated fixes which turned up when debugging snarkyjs transactions against a local Mina node, while debugging timestamp preconditions

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch on figuring out these fixes!

tokenSymbol: string;
zkappUri?: string;
zkappState: string[] | null;
Expand Down Expand Up @@ -210,7 +210,7 @@ const accountQuery = (publicKey: string, tokenId: string) => `{
balance { total }
delegateAccount { publicKey }
sequenceEvents
tokenId
token
tokenSymbol
}
}
Expand All @@ -230,7 +230,7 @@ function parseFetchedAccount({
delegateAccount,
receiptChainHash,
sequenceEvents,
tokenId,
token,
tokenSymbol,
}: Partial<FetchedAccount>): Partial<Account> {
return {
Expand All @@ -254,7 +254,7 @@ function parseFetchedAccount({
// : undefined,
delegate:
delegateAccount && PublicKey.fromBase58(delegateAccount.publicKey),
tokenId: tokenId !== undefined ? Ledger.fieldOfBase58(tokenId) : undefined,
tokenId: token !== undefined ? Ledger.fieldOfBase58(token) : undefined,
tokenSymbol: tokenSymbol !== undefined ? tokenSymbol : undefined,
};
}
Expand All @@ -269,7 +269,7 @@ function stringifyAccount(account: FlexibleAccount): FetchedAccount {
zkapp?.appState.map((s) => s.toString()) ??
Array(ZkappStateLength).fill('0'),
balance: { total: balance?.toString() ?? '0' },
tokenId: tokenId ?? Ledger.fieldToBase58(getDefaultTokenId()),
token: tokenId ?? Ledger.fieldToBase58(getDefaultTokenId()),
tokenSymbol: tokenSymbol ?? '',
};
}
Expand Down Expand Up @@ -384,7 +384,7 @@ function addCachedAccountInternal(
account: FetchedAccount,
graphqlEndpoint: string
) {
accountCache[`${account.publicKey};${account.tokenId};${graphqlEndpoint}`] = {
accountCache[`${account.publicKey};${account.token};${graphqlEndpoint}`] = {
account,
graphqlEndpoint,
timestamp: Date.now(),
Expand Down Expand Up @@ -570,6 +570,7 @@ function removeJsonQuotes(json: string) {
);
}

// TODO it seems we're not actually catching most errors here
async function makeGraphqlRequest(
query: string,
graphqlEndpoint = defaultGraphqlEndpoint,
Expand Down
Loading