Skip to content

Commit

Permalink
Merge branch 'main' into feature/eddsa/sha512
Browse files Browse the repository at this point in the history
  • Loading branch information
querolita committed Jan 9, 2025
2 parents b66e6a5 + a5c15ad commit 7be3376
Show file tree
Hide file tree
Showing 25 changed files with 644 additions and 59 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-bindings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
run: |
cd src/bindings
git add .
git diff HEAD > ../../bindings.patch
git diff HEAD --textconv --text > ../../bindings.patch
- name: Upload patch
uses: actions/upload-artifact@v4
with:
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added

- `ZkProgram` to support non-pure provable types as inputs and outputs https://github.com/o1-labs/o1js/pull/1828
- API for recursively proving a ZkProgram method from within another https://github.com/o1-labs/o1js/pull/1931
- APIs for recursively proving a ZkProgram method from within another https://github.com/o1-labs/o1js/pull/1931 https://github.com/o1-labs/o1js/pull/1932
- `let recursive = Experimental.Recursive(program);`
- `recursive.<methodName>(...args): Promise<PublicOutput>`
- `recursive.<methodName>.if(condition, ...args): Promise<PublicOutput>`
- This also works within the same program, as long as the return value is type-annotated
- Add `enforceTransactionLimits` parameter on Network https://github.com/o1-labs/o1js/issues/1910
- Method for optional types to assert none https://github.com/o1-labs/o1js/pull/1922
Expand All @@ -44,6 +45,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

- Compiling stuck in the browser for recursive zkprograms https://github.com/o1-labs/o1js/pull/1906
- Error message in `rangeCheck16` gadget https://github.com/o1-labs/o1js/pull/1920
- Deprecate `testnet` `networkId` in favor of `devnet` https://github.com/o1-labs/o1js/pull/1938

## [2.1.0](https://github.com/o1-labs/o1js/compare/b04520d...e1bac02) - 2024-11-13

Expand Down
4 changes: 2 additions & 2 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion generate-keys.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node
import Client from './dist/node/mina-signer/mina-signer.js';

let client = new Client({ network: 'testnet' });
let client = new Client({ network: 'devnet' });

console.log(client.genKeys());
72 changes: 72 additions & 0 deletions src/examples/zkprogram/hash-chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* This shows how to prove an arbitrarily long chain of hashes using ZkProgram, i.e.
* `hash^n(x) = y`.
*
* We implement this as a self-recursive ZkProgram, using `proveRecursivelyIf()`
*/
import {
assert,
Bool,
Experimental,
Field,
Poseidon,
Provable,
Struct,
ZkProgram,
} from 'o1js';

const HASHES_PER_PROOF = 30;

class HashChainSpec extends Struct({ x: Field, n: Field }) {}

const hashChain = ZkProgram({
name: 'hash-chain',
publicInput: HashChainSpec,
publicOutput: Field,

methods: {
chain: {
privateInputs: [],

async method({ x, n }: HashChainSpec) {
Provable.log('hashChain (start method)', n);
let y = x;
let k = Field(0);
let reachedN = Bool(false);

for (let i = 0; i < HASHES_PER_PROOF; i++) {
reachedN = k.equals(n);
y = Provable.if(reachedN, y, Poseidon.hash([y]));
k = Provable.if(reachedN, n, k.add(1));
}

// we have y = hash^k(x)
// now do z = hash^(n-k)(y) = hash^n(x) by calling this method recursively
// except if we have k = n, then ignore the output and use y
let z: Field = await hashChainRecursive.chain.if(reachedN.not(), {
x: y,
n: n.sub(k),
});
z = Provable.if(reachedN, y, z);
Provable.log('hashChain (start proving)', n);
return { publicOutput: z };
},
},
},
});
let hashChainRecursive = Experimental.Recursive(hashChain);

await hashChain.compile();

let n = 100;
let x = Field.random();

let { proof } = await hashChain.chain({ x, n: Field(n) });

assert(await hashChain.verify(proof), 'Proof invalid');

// check that the output is correct
let z = Array.from({ length: n }, () => 0).reduce((y) => Poseidon.hash([y]), x);
proof.publicOutput.assertEquals(z, 'Output is incorrect');

console.log('Finished hash chain proof');
2 changes: 1 addition & 1 deletion src/lib/mina/local-blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ async function LocalBlockchain({
const originalProofsEnabled = proofsEnabled;

return {
getNetworkId: () => 'testnet' as NetworkId,
getNetworkId: () => 'devnet' as NetworkId,
proofsEnabled,
getNetworkConstants() {
return {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/mina/mina-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ let activeInstance: Mina = {
fetchActions: noActiveInstance,
getActions: noActiveInstance,
proofsEnabled: true,
getNetworkId: () => 'testnet',
getNetworkId: () => 'devnet',
};

/**
Expand Down
2 changes: 1 addition & 1 deletion src/lib/mina/mina.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ function Network(
}
| string
): Mina {
let minaNetworkId: NetworkId = 'testnet';
let minaNetworkId: NetworkId = 'devnet';
let minaGraphqlEndpoint: string;
let archiveEndpoint: string;
let lightnetAccountManagerEndpoint: string;
Expand Down
3 changes: 2 additions & 1 deletion src/lib/mina/token/forest-iterator.unit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import assert from 'assert';
import { Field, Bool } from '../../provable/wrapped.js';
import { PublicKey } from '../../provable/crypto/signature.js';
import { NetworkId } from '../../../mina-signer/index.js';

// RANDOM NUMBER GENERATORS for account updates

Expand Down Expand Up @@ -56,7 +57,7 @@ test.custom({ timeBudget: 1000 })(
(flatUpdatesBigint) => {
// reference: bigint callforest hash from mina-signer
let forestBigint = accountUpdatesToCallForest(flatUpdatesBigint);
let expectedHash = callForestHash(forestBigint, 'testnet');
let expectedHash = callForestHash(forestBigint, 'devnet');

let flatUpdates = flatUpdatesBigint.map(accountUpdateFromBigint);
let forest = AccountUpdateForest.fromFlatArray(flatUpdates);
Expand Down
97 changes: 78 additions & 19 deletions src/lib/proof-system/recursive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Tuple } from '../util/types.js';
import { Proof } from './proof.js';
import { mapObject, mapToObject, zip } from '../util/arrays.js';
import { Undefined, Void } from './zkprogram.js';
import { Bool } from '../provable/bool.js';

export { Recursive };

Expand All @@ -25,6 +26,7 @@ function Recursive<
...args: any
) => Promise<{ publicOutput: InferProvable<PublicOutputType> }>;
};
maxProofsVerified: () => Promise<0 | 1 | 2>;
} & {
[Key in keyof PrivateInputs]: (...args: any) => Promise<{
proof: Proof<
Expand All @@ -38,7 +40,13 @@ function Recursive<
InferProvable<PublicInputType>,
InferProvable<PublicOutputType>,
PrivateInputs[Key]
>;
> & {
if: ConditionalRecursiveProver<
InferProvable<PublicInputType>,
InferProvable<PublicOutputType>,
PrivateInputs[Key]
>;
};
} {
type PublicInput = InferProvable<PublicInputType>;
type PublicOutput = InferProvable<PublicOutputType>;
Expand All @@ -64,9 +72,15 @@ function Recursive<

let regularRecursiveProvers = mapToObject(methodKeys, (key) => {
return async function proveRecursively_(
conditionAndConfig: Bool | { condition: Bool; domainLog2?: number },
publicInput: PublicInput,
...args: TupleToInstances<PrivateInputs[MethodKey]>
) {
): Promise<PublicOutput> {
let condition =
conditionAndConfig instanceof Bool
? conditionAndConfig
: conditionAndConfig.condition;

// create the base proof in a witness block
let proof = await Provable.witnessAsync(SelfProof, async () => {
// move method args to constants
Expand All @@ -78,6 +92,20 @@ function Recursive<
Provable.toConstant(type, arg)
);

if (!condition.toBoolean()) {
let publicOutput: PublicOutput =
ProvableType.synthesize(publicOutputType);
let maxProofsVerified = await zkprogram.maxProofsVerified();
return SelfProof.dummy(
publicInput,
publicOutput,
maxProofsVerified,
conditionAndConfig instanceof Bool
? undefined
: conditionAndConfig.domainLog2
);
}

let prover = zkprogram[key];

if (hasPublicInput) {
Expand All @@ -96,32 +124,48 @@ function Recursive<

// declare and verify the proof, and return its public output
proof.declare();
proof.verify();
proof.verifyIf(condition);
return proof.publicOutput;
};
});

type RecursiveProver_<K extends MethodKey> = RecursiveProver<
PublicInput,
PublicOutput,
PrivateInputs[K]
>;
type RecursiveProvers = {
[K in MethodKey]: RecursiveProver_<K>;
};
let proveRecursively: RecursiveProvers = mapToObject(
methodKeys,
(key: MethodKey) => {
return mapObject(
regularRecursiveProvers,
(
prover
): RecursiveProver<PublicInput, PublicOutput, PrivateInputs[MethodKey]> & {
if: ConditionalRecursiveProver<
PublicInput,
PublicOutput,
PrivateInputs[MethodKey]
>;
} => {
if (!hasPublicInput) {
return ((...args: any) =>
regularRecursiveProvers[key](undefined as any, ...args)) as any;
return Object.assign(
((...args: any) =>
prover(new Bool(true), undefined as any, ...args)) as any,
{
if: (
condition: Bool | { condition: Bool; domainLog2?: number },
...args: any
) => prover(condition, undefined as any, ...args),
}
);
} else {
return regularRecursiveProvers[key] as any;
return Object.assign(
((pi: PublicInput, ...args: any) =>
prover(new Bool(true), pi, ...args)) as any,
{
if: (
condition: Bool | { condition: Bool; domainLog2?: number },
pi: PublicInput,
...args: any
) => prover(condition, pi, ...args),
}
);
}
}
);

return proveRecursively;
}

type RecursiveProver<
Expand All @@ -135,6 +179,21 @@ type RecursiveProver<
...args: TupleToInstances<Args>
) => Promise<PublicOutput>;

type ConditionalRecursiveProver<
PublicInput,
PublicOutput,
Args extends Tuple<ProvableType>
> = PublicInput extends undefined
? (
condition: Bool | { condition: Bool; domainLog2?: number },
...args: TupleToInstances<Args>
) => Promise<PublicOutput>
: (
condition: Bool | { condition: Bool; domainLog2?: number },
publicInput: PublicInput,
...args: TupleToInstances<Args>
) => Promise<PublicOutput>;

type TupleToInstances<T> = {
[I in keyof T]: InferProvable<T[I]>;
};
3 changes: 1 addition & 2 deletions src/lib/proof-system/zkprogram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
unsetSrsCache,
} from '../../bindings/crypto/bindings/srs.js';
import {
ProvablePure,
ProvableType,
ProvableTypePure,
ToProvable,
Expand All @@ -55,7 +54,7 @@ import {
import { emptyWitness } from '../provable/types/util.js';
import { InferValue } from '../../bindings/lib/provable-generic.js';
import { DeclaredProof, ZkProgramContext } from './zkprogram-context.js';
import { mapObject, mapToObject, zip } from '../util/arrays.js';
import { mapObject, mapToObject } from '../util/arrays.js';

// public API
export {
Expand Down
10 changes: 5 additions & 5 deletions src/lib/provable/crypto/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,22 +266,22 @@ class Signature extends CircuitValue {
let publicKey = PublicKey.fromPrivateKey(privKey).toGroup();
let d = privKey.s;

// we chose an arbitrary prefix for the signature, and it happened to be 'testnet'
// we chose an arbitrary prefix for the signature
// there's no consequences in practice and the signatures can be used with any network
// if there needs to be a custom nonce, include it in the message itself
let kPrime = Scalar.from(
deriveNonce(
{ fields: msg.map((f) => f.toBigInt()) },
{ x: publicKey.x.toBigInt(), y: publicKey.y.toBigInt() },
d.toBigInt(),
'testnet'
'devnet'
)
);

let { x: r, y: ry } = Group.generator.scale(kPrime);
let k = ry.isOdd().toBoolean() ? kPrime.neg() : kPrime;
let h = hashWithPrefix(
signaturePrefix('testnet'),
signaturePrefix('devnet'),
msg.concat([publicKey.x, publicKey.y, r])
);
let e = Scalar.fromField(h);
Expand All @@ -296,11 +296,11 @@ class Signature extends CircuitValue {
verify(publicKey: PublicKey, msg: Field[]): Bool {
let point = publicKey.toGroup();

// we chose an arbitrary prefix for the signature, and it happened to be 'testnet'
// we chose an arbitrary prefix for the signature
// there's no consequences in practice and the signatures can be used with any network
// if there needs to be a custom nonce, include it in the message itself
let h = hashWithPrefix(
signaturePrefix('testnet'),
signaturePrefix('devnet'),
msg.concat([point.x, point.y, this.r])
);

Expand Down
4 changes: 2 additions & 2 deletions src/mina-signer/mina-signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class Client {
*/
signFields(fields: bigint[], privateKey: Json.PrivateKey): Signed<bigint[]> {
let privateKey_ = PrivateKey.fromBase58(privateKey);
let signature = sign({ fields }, privateKey_, 'testnet');
let signature = sign({ fields }, privateKey_, 'devnet');
return {
signature: Signature.toBase58(signature),
publicKey: PublicKey.toBase58(PrivateKey.toPublicKey(privateKey_)),
Expand All @@ -172,7 +172,7 @@ class Client {
Signature.fromBase58(signature),
{ fields: data },
PublicKey.fromBase58(publicKey),
'testnet'
'devnet'
);
}

Expand Down
Loading

0 comments on commit 7be3376

Please sign in to comment.