Skip to content

Commit

Permalink
Merge pull request #310 from o1-labs/feature/compatible-hashing
Browse files Browse the repository at this point in the history
Hash inputs in JS
  • Loading branch information
mitschabaude committed Aug 1, 2022
2 parents c0bd0de + 15718f9 commit d180ddb
Show file tree
Hide file tree
Showing 27 changed files with 766 additions and 421 deletions.
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 {
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');
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());
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;
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

0 comments on commit d180ddb

Please sign in to comment.