Skip to content
Open
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
116 changes: 97 additions & 19 deletions packages/common/src/trees/sparse/RollupMerkleTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ import { TypedClass } from "../../types";
import { MerkleTreeStore } from "./MerkleTreeStore";
import { InMemoryMerkleTreeStorage } from "./InMemoryMerkleTreeStorage";

/**
* More efficient version of `maybeSwapBad` which
* reuses an intermediate variable
*/
export function maybeSwap(b: Bool, x: Field, y: Field): [Field, Field] {
const m = b.toField().mul(x.sub(y)); // b*(x - y)
const x1 = y.add(m); // y + b*(x - y)
const y2 = x.sub(m); // x - b*(x - y) = x + b*(y - x)
return [x1, y2];
}

export class StructTemplate extends Struct({
path: Provable.Array(Field, 0),
isLeft: Provable.Array(Bool, 0),
Expand All @@ -22,6 +33,11 @@ export interface AbstractMerkleWitness extends StructTemplate {
*/
calculateRoot(hash: Field): Field;

calculateRootIncrement(
index: Field,
leaf: Field
): [Field, AbstractMerkleWitness];

/**
* Calculates the index of the leaf node that belongs to this Witness.
* @returns Index of the leaf.
Expand Down Expand Up @@ -119,6 +135,24 @@ export interface AbstractMerkleTreeClass {
* It also holds the Witness class under tree.WITNESS
*/
export function createMerkleTree(height: number): AbstractMerkleTreeClass {
function generateZeroes() {
const zeroes = [0n];
for (let index = 1; index < height; index += 1) {
const previousLevel = Field(zeroes[index - 1]);
zeroes.push(Poseidon.hash([previousLevel, previousLevel]).toBigInt());
}
return zeroes;
}

let zeroCache: bigint[] | undefined = undefined;

function getZeroes() {
if (zeroCache === undefined) {
zeroCache = generateZeroes();
}
return zeroCache;
}

/**
* The {@link RollupMerkleWitness} class defines a circuit-compatible base class
* for [Merkle Witness'](https://computersciencewiki.org/index.php/Merkle_proof).
Expand Down Expand Up @@ -147,14 +181,74 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass {

for (let index = 1; index < n; ++index) {
const isLeft = this.isLeft[index - 1];
// eslint-disable-next-line @typescript-eslint/no-use-before-define

const [left, right] = maybeSwap(isLeft, hash, this.path[index - 1]);
hash = Poseidon.hash([left, right]);
}

return hash;
}

public calculateRootIncrement(
leafIndex: Field,
leaf: Field
): [Field, RollupMerkleWitness] {
// This won't generate any constraints, since it's purely a computation on constants
const zero = getZeroes();

if (zero.length === 0) {
throw new Error("Zeroes not initialized");
}
const zeroes = zero.map((x) => Field(x));

let hash = leaf;
const n = this.height();

let notDiverged = Bool(true);
const newPath = leafIndex.add(1).toBits();
newPath.push(Bool(false));

const newSiblings: Field[] = [];
const newIsLefts: Bool[] = [];

for (let index = 0; index < n - 1; ++index) {
const isLeft = this.isLeft[index];
const sibling = this.path[index];

const newIsLeft = newPath[index].not();

// Bool(true) default for root level
let convergesNextLevel = Bool(true);
if (index < n - 2) {
convergesNextLevel = newPath[index + 1]
.equals(this.isLeft[index + 1])
.not();
}

const nextSibling = Provable.if(
convergesNextLevel.and(notDiverged),
hash,
Provable.if(notDiverged, zeroes[index], sibling)
);

notDiverged = notDiverged.and(convergesNextLevel.not());

newSiblings.push(nextSibling);
newIsLefts.push(newIsLeft);

const [left, right] = maybeSwap(isLeft, hash, sibling);
hash = Poseidon.hash([left, right]);
}

return [
hash,
new RollupMerkleWitness({
isLeft: newIsLefts,
path: newSiblings,
}),
];
}

/**
* Calculates the index of the leaf node that belongs to this Witness.
* @returns Index of the leaf.
Expand Down Expand Up @@ -215,6 +309,7 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass {
});
}
}

return class AbstractRollupMerkleTree implements AbstractMerkleTree {
public static HEIGHT = height;

Expand All @@ -238,13 +333,7 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass {

public constructor(store: MerkleTreeStore) {
this.store = store;
this.zeroes = [0n];
for (let index = 1; index < AbstractRollupMerkleTree.HEIGHT; index += 1) {
const previousLevel = Field(this.zeroes[index - 1]);
this.zeroes.push(
Poseidon.hash([previousLevel, previousLevel]).toBigInt()
);
}
this.zeroes = generateZeroes();
}

public assertIndexRange(index: bigint) {
Expand Down Expand Up @@ -414,14 +503,3 @@ export function createMerkleTree(height: number): AbstractMerkleTreeClass {

export class RollupMerkleTree extends createMerkleTree(256) {}
export class RollupMerkleTreeWitness extends RollupMerkleTree.WITNESS {}

/**
* More efficient version of `maybeSwapBad` which
* reuses an intermediate variable
*/
export function maybeSwap(b: Bool, x: Field, y: Field): [Field, Field] {
const m = b.toField().mul(x.sub(y)); // b*(x - y)
const x1 = y.add(m); // y + b*(x - y)
const y2 = x.sub(m); // x - b*(x - y) = x + b*(y - x)
return [x1, y2];
}
18 changes: 17 additions & 1 deletion packages/common/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ export function yieldSequential<Source, State, Target>(
array,
async ([state, collectedTargets], curr, index, arr) => {
const [newState, addition] = await callbackfn(state, curr, index, arr);
return [newState, collectedTargets.concat(addition)];
// The reason we wrap this in an array here is for a special case where Target is a tuple
// or array itself. In this case, js interprets by flattening the Value in the array
// (which it does when a function (like concat) uses a spread operator and the
// input is an array)
return [newState, collectedTargets.concat([addition])];
},
[initialValue, []]
);
Expand All @@ -105,6 +109,18 @@ export function mapSequential<T, R>(
}, Promise.resolve([]));
}

export function unzip<A, B>(array: [A, B][]): [A[], B[]] {
const as = array.map(([a]) => a);
const bs = array.map(([, b]) => b);
return [as, bs];
}

export function assertSizeOneOrTwo<T>(arr: T[]): asserts arr is [T] | [T, T] {
if (!(arr.length === 1 || arr.length === 2)) {
throw new Error("Given array not size 1 or 2");
}
}

/**
* Computes a dummy value for the given value type.
*
Expand Down
31 changes: 31 additions & 0 deletions packages/common/test/trees/MerkleTree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,35 @@ describe.each([4, 16, 256])("cachedMerkleTree - %s", (height) => {
tree.getNode(0, index);
}).toThrow("Index greater than maximum leaf number");
});

it("witness incrementing", () => {
tree.setLeaf(0n, Field(3256));
tree.setLeaf(1n, Field(3256));
tree.setLeaf(2n, Field(3256));

const witness = tree.getWitness(3n);

const [root, newWitness] = witness.calculateRootIncrement(
Field(3),
Field(1234)
);
tree.setLeaf(3n, Field(1234));

expect(tree.getRoot().toString()).toStrictEqual(root.toString());
expect(newWitness.calculateIndex().toString()).toStrictEqual("4");

const [root2, newWitness2] = newWitness.calculateRootIncrement(
Field(4),
Field(4321)
);
tree.setLeaf(4n, Field(4321));

expect(tree.getRoot().toString()).toStrictEqual(root2.toString());
expect(newWitness2.calculateIndex().toString()).toStrictEqual("5");

const root3 = newWitness2.calculateRoot(Field(555));
tree.setLeaf(5n, Field(555));

expect(tree.getRoot().toString()).toStrictEqual(root3.toString());
});
});
5 changes: 0 additions & 5 deletions packages/module/src/method/runtimeMethod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ const errors = {
runtimeNotProvided: (name: string) =>
new Error(`Runtime was not provided for module: ${name}`),

methodInputsNotProvided: () =>
new Error(
"Method execution inputs not provided, provide them via context.inputs"
),

runtimeNameNotSet: () => new Error("Runtime name was not set"),

fieldNotConstant: (name: string) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe("sequencer restart", () => {
};

const teardown = async () => {
await appChain.sequencer.resolve("Database").close();
await appChain.close();
};

beforeAll(async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export * from "./prover/accumulators/StateTransitionReductionList";
export * from "./prover/accumulators/AppliedBatchHashList";
export * from "./prover/accumulators/WitnessedRootHashList";
export * from "./prover/accumulators/TransactionHashList";
export * from "./prover/accumulators/BlockHashList";
export * from "./prover/block/BlockProver";
export * from "./prover/block/BlockProvable";
export * from "./prover/block/accummulators/RuntimeVerificationKeyTree";
Expand Down
41 changes: 23 additions & 18 deletions packages/protocol/src/protocol/ProvableBlockHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,52 @@ import { NoConfig } from "@proto-kit/common";

import { NetworkState } from "../model/network/NetworkState";
import {
BlockProverState,
BlockProverPublicInput,
BlockArguments,
BlockProverState,
} from "../prover/block/BlockProvable";

import { TransitioningProtocolModule } from "./TransitioningProtocolModule";

export type ProvableHookBlockState = Pick<
BlockProverPublicInput,
| "transactionsHash"
| "eternalTransactionsHash"
| "incomingMessagesHash"
| "blockHashRoot"
BlockProverPublicInput & BlockArguments,
"eternalTransactionsHash" | "incomingMessagesHash" | "blockHashRoot"
>;

export function toProvableHookBlockState(
export function toBeforeBlockHookArgument(
state: Pick<
BlockProverState,
| "transactionList"
| "eternalTransactionsList"
| "incomingMessages"
| "blockHashRoot"
"eternalTransactionsList" | "incomingMessages" | "blockHashRoot"
>
) {
const {
transactionList,
eternalTransactionsList,
incomingMessages,
blockHashRoot,
} = state;
const { eternalTransactionsList, incomingMessages, blockHashRoot } = state;
return {
transactionsHash: transactionList.commitment,
eternalTransactionsHash: eternalTransactionsList.commitment,
incomingMessagesHash: incomingMessages.commitment,
blockHashRoot,
};
}

export function toAfterBlockHookArgument(
state: Pick<
BlockProverState,
"eternalTransactionsList" | "incomingMessages" | "blockHashRoot"
>,
stateRoot: Field,
transactionsHash: Field
) {
return {
...toBeforeBlockHookArgument(state),
stateRoot,
transactionsHash,
};
}

export interface BeforeBlockHookArguments extends ProvableHookBlockState {}

export interface AfterBlockHookArguments extends BeforeBlockHookArguments {
stateRoot: Field;
transactionsHash: Field;
}

// Purpose is to build transition from -> to network state
Expand Down
14 changes: 7 additions & 7 deletions packages/protocol/src/protocol/ProvableTransactionHook.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import { NoConfig } from "@proto-kit/common";
import { Signature } from "o1js";
import { Field, Signature } from "o1js";

import { RuntimeTransaction } from "../model/transaction/RuntimeTransaction";
import { NetworkState } from "../model/network/NetworkState";
import { MethodPublicOutput } from "../model/MethodPublicOutput";
import {
TransactionProverPublicInput,
TransactionProverState,
TransactionProverTransactionArguments,
} from "../prover/transaction/TransactionProvable";

import { TransitioningProtocolModule } from "./TransitioningProtocolModule";

export type ProvableHookTransactionState = Pick<
TransactionProverPublicInput,
"transactionsHash" | "eternalTransactionsHash" | "incomingMessagesHash"
>;
export type ProvableHookTransactionState = {
transactionsHash: Field;
eternalTransactionsHash: Field;
incomingMessagesHash: Field;
};

export function toProvableHookTransactionState(
state: Pick<
TransactionProverState,
"transactionList" | "eternalTransactionsList" | "incomingMessages"
>
) {
): ProvableHookTransactionState {
const { transactionList, eternalTransactionsList, incomingMessages } = state;
return {
transactionsHash: transactionList.commitment,
Expand Down
Loading