Skip to content

Commit

Permalink
Merge branch 'main' into feature/async-methods-for-real
Browse files Browse the repository at this point in the history
  • Loading branch information
mitschabaude committed Mar 8, 2024
2 parents 402b7c8 + 9c2935a commit 0a3df06
Show file tree
Hide file tree
Showing 11 changed files with 522 additions and 559 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,16 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Change `{SmartContract,ZkProgram}.analyzeMethods()` to be async https://github.com/o1-labs/o1js/pull/1450
- `Provable.runAndCheck()`, `Provable.constraintSystem()` and `{SmartContract,ZkProgram}.digest()` are also async now
- `Provable.runAndCheckSync()` added and immediately deprecated for a smoother upgrade path for tests
- Remove `this.sender` which unintuitively did not prove that its value was the actual sender of the transaction https://github.com/o1-labs/o1js/pull/1464 [@julio4](https://github.com/julio4)
Replaced by more explicit APIs:
- `this.sender.getUnconstrained()` which has the old behavior of `this.sender`, and returns an unconstrained value (which means that the prover can set it to any value they want)
- `this.sender.getAndRequireSignature()` which requires a signature from the sender's public key and therefore proves that whoever created the transaction really owns the sender account
- `Reducer.reduce()` requires the maximum number of actions per method as an explicit (optional) argument https://github.com/o1-labs/o1js/pull/1450
- The default value is 1 and should work for most existing contracts
- `new UInt64()` and `UInt64.from()` no longer unsafely accept a field element as input. https://github.com/o1-labs/o1js/pull/1438 [@julio4](https://github.com/julio4)
As a replacement, `UInt64.Unsafe.fromField()` was introduced
- This prevents you from accidentally creating a `UInt64` without proving that it fits in 64 bits
- Equivalent changes were made to `UInt32`

### Added

Expand Down
2 changes: 1 addition & 1 deletion src/bindings
Submodule bindings updated 0 files
28 changes: 18 additions & 10 deletions src/examples/zkapps/dex/dex-with-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ class Dex extends TokenContract {

// TODO this could just use `this.approveAccountUpdate()` instead of a separate @method
@method async createAccount() {
this.internal.mint({ address: this.sender, amount: UInt64.from(0) });
this.internal.mint({
// unconstrained because we don't care which account is created
address: this.sender.getUnconstrained(),
amount: UInt64.from(0),
});
}

/**
Expand All @@ -95,7 +99,8 @@ class Dex extends TokenContract {
*/
@method.returns(UInt64)
async supplyLiquidityBase(dx: UInt64, dy: UInt64) {
let user = this.sender;
// unconstrained because `transfer()` requires sender signature anyway
let user = this.sender.getUnconstrained();
let tokenX = new TrivialCoin(this.tokenX);
let tokenY = new TrivialCoin(this.tokenY);

Expand Down Expand Up @@ -166,14 +171,15 @@ class Dex extends TokenContract {
* contracts pay you tokens when reducing the action.
*/
@method async redeemInitialize(dl: UInt64) {
this.reducer.dispatch(new RedeemAction({ address: this.sender, dl }));
this.internal.burn({ address: this.sender, amount: dl });
let sender = this.sender.getUnconstrained(); // unconstrained because `burn()` requires sender signature anyway
this.reducer.dispatch(new RedeemAction({ address: sender, dl }));
this.internal.burn({ address: sender, amount: dl });
// TODO: preconditioning on the state here ruins concurrent interactions,
// there should be another `finalize` DEX method which reduces actions & updates state
this.totalSupply.set(this.totalSupply.getAndRequireEquals().sub(dl));

// emit event
this.typedEvents.emit('redeem-liquidity', { address: this.sender, dl });
this.typedEvents.emit('redeem-liquidity', { address: sender, dl });
}

/**
Expand All @@ -199,10 +205,11 @@ class Dex extends TokenContract {
* the called methods which requires proof authorization.
*/
async swapX(dx: UInt64) {
let user = this.sender.getUnconstrained(); // unconstrained because `swap()` requires sender signature anyway
let tokenY = new TrivialCoin(this.tokenY);
let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId());
let dy = await dexY.swap(this.sender, dx, this.tokenX);
await tokenY.transfer(dexY.self, this.sender, dy);
let dy = await dexY.swap(user, dx, this.tokenX);
await tokenY.transfer(dexY.self, user, dy);
return dy;
}

Expand All @@ -217,10 +224,11 @@ class Dex extends TokenContract {
* the called methods which requires proof authorization.
*/
async swapY(dy: UInt64) {
let user = this.sender.getUnconstrained(); // unconstrained because `swap()` requires sender signature anyway
let tokenX = new TrivialCoin(this.tokenX);
let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId());
let dx = await dexX.swap(this.sender, dy, this.tokenY);
await tokenX.transfer(dexX.self, this.sender, dx);
let dx = await dexX.swap(user, dy, this.tokenY);
await tokenX.transfer(dexX.self, user, dx);
return dx;
}
}
Expand Down Expand Up @@ -318,7 +326,7 @@ class DexTokenHolder extends SmartContract {
this.balance.subInPlace(dy);

// emit event
this.typedEvents.emit('swap', { address: this.sender, dx });
this.typedEvents.emit('swap', { address: user, dx });

return dy;
}
Expand Down
22 changes: 13 additions & 9 deletions src/examples/zkapps/dex/dex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function createDex({
*/
@method.returns(UInt64)
async supplyLiquidityBase(dx: UInt64, dy: UInt64) {
let user = this.sender;
let user = this.sender.getUnconstrained(); // unconstrained because transfer() requires the signature anyway
let tokenX = new TokenContract(this.tokenX);
let tokenY = new TokenContract(this.tokenY);

Expand Down Expand Up @@ -149,11 +149,12 @@ function createDex({
*/
async redeemLiquidity(dl: UInt64) {
// call the token X holder inside a token X-approved callback
let sender = this.sender.getUnconstrained(); // unconstrained because redeemLiquidity() requires the signature anyway
let tokenX = new TokenContract(this.tokenX);
let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId());
let dxdy = await dexX.redeemLiquidity(this.sender, dl, this.tokenY);
let dxdy = await dexX.redeemLiquidity(sender, dl, this.tokenY);
let dx = dxdy[0];
await tokenX.transfer(dexX.self, this.sender, dx);
await tokenX.transfer(dexX.self, sender, dx);
return dxdy;
}

Expand All @@ -166,10 +167,11 @@ function createDex({
*/
@method.returns(UInt64)
async swapX(dx: UInt64) {
let sender = this.sender.getUnconstrained(); // unconstrained because swap() requires the signature anyway
let tokenY = new TokenContract(this.tokenY);
let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId());
let dy = await dexY.swap(this.sender, dx, this.tokenX);
await tokenY.transfer(dexY.self, this.sender, dy);
let dy = await dexY.swap(sender, dx, this.tokenX);
await tokenY.transfer(dexY.self, sender, dy);
return dy;
}

Expand All @@ -182,10 +184,11 @@ function createDex({
*/
@method.returns(UInt64)
async swapY(dy: UInt64) {
let sender = this.sender.getUnconstrained(); // unconstrained because swap() requires the signature anyway
let tokenX = new TokenContract(this.tokenX);
let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId());
let dx = await dexX.swap(this.sender, dy, this.tokenY);
await tokenX.transfer(dexX.self, this.sender, dx);
let dx = await dexX.swap(sender, dy, this.tokenY);
await tokenX.transfer(dexX.self, sender, dx);
return dx;
}

Expand Down Expand Up @@ -220,13 +223,14 @@ function createDex({

@method.returns(UInt64)
async swapX(dx: UInt64) {
let sender = this.sender.getUnconstrained(); // unconstrained because swap() requires the signature anyway
let tokenY = new TokenContract(this.tokenY);
let dexY = new ModifiedDexTokenHolder(
this.address,
tokenY.deriveTokenId()
);
let dy = await dexY.swap(this.sender, dx, this.tokenX);
await tokenY.transfer(dexY.self, this.sender, dy);
let dy = await dexY.swap(sender, dx, this.tokenX);
await tokenY.transfer(dexY.self, sender, dy);
return dy;
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/examples/zkapps/simple-zkapp-payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ class SendMINAExample extends SmartContract {
}

@method async withdraw(amount: UInt64) {
this.send({ to: this.sender, amount });
// unconstrained because we don't care where the user wants to withdraw to
let to = this.sender.getUnconstrained();
this.send({ to, amount });
}

@method async deposit(amount: UInt64) {
let senderUpdate = AccountUpdate.createSigned(this.sender);
let sender = this.sender.getUnconstrained(); // unconstrained because we're already requiring a signature in the next line
let senderUpdate = AccountUpdate.createSigned(sender);
senderUpdate.send({ to: this, amount });
}
}
Expand Down
11 changes: 6 additions & 5 deletions src/lib/circuit-value.unit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,16 @@ await Provable.runAndCheck(() => {
});

// should fail `check` if `check` of subfields doesn't pass

// manually construct an invalid uint32
let noUint32 = new UInt32(1);
noUint32.value = Field(-1);

await expect(() =>
Provable.runAndCheck(() => {
let x = Provable.witness(type, () => ({
...value,
uint: [
UInt32.zero,
// invalid Uint32
new UInt32(Field(-1)),
],
uint: [UInt32.zero, noUint32],
}));
})
).rejects.toThrow(`Constraint unsatisfied`);
Expand Down
14 changes: 7 additions & 7 deletions src/lib/gadgets/sha256.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function padding(data: FlexibleBytes): UInt32[][] {
// chunk 4 bytes into one UInt32, as expected by SHA256
// bytesToWord expects little endian, so we reverse the bytes
chunks.push(
UInt32.from(bytesToWord(paddedMessage.slice(i, i + 4).reverse()))
UInt32.Unsafe.fromField(bytesToWord(paddedMessage.slice(i, i + 4).reverse()))
);
}

Expand Down Expand Up @@ -104,7 +104,7 @@ const SHA256 = {
.add(DeltaZero(W[t - 15]).value.add(W[t - 16].value));

// mod 32bit the unreduced field element
W[t] = UInt32.from(divMod32(unreduced, 16).remainder);
W[t] = UInt32.Unsafe.fromField(divMod32(unreduced, 16).remainder);
}

// initialize working variables
Expand Down Expand Up @@ -133,11 +133,11 @@ const SHA256 = {
h = g;
g = f;
f = e;
e = UInt32.from(divMod32(d.value.add(unreducedT1), 16).remainder); // mod 32bit the unreduced field element
e = UInt32.Unsafe.fromField(divMod32(d.value.add(unreducedT1), 16).remainder); // mod 32bit the unreduced field element
d = c;
c = b;
b = a;
a = UInt32.from(divMod32(unreducedT2.add(unreducedT1), 16).remainder); // mod 32bit
a = UInt32.Unsafe.fromField(divMod32(unreducedT2.add(unreducedT1), 16).remainder); // mod 32bit
}

// new intermediate hash value
Expand All @@ -163,7 +163,7 @@ function Ch(x: UInt32, y: UInt32, z: UInt32) {
let xAndY = x.and(y).value;
let xNotAndZ = x.not().and(z).value;
let ch = xAndY.add(xNotAndZ).seal();
return UInt32.from(ch);
return UInt32.Unsafe.fromField(ch);
}

function Maj(x: UInt32, y: UInt32, z: UInt32) {
Expand All @@ -172,7 +172,7 @@ function Maj(x: UInt32, y: UInt32, z: UInt32) {
let sum = x.value.add(y.value).add(z.value).seal();
let xor = x.xor(y).xor(z).value;
let maj = sum.sub(xor).div(2).seal();
return UInt32.from(maj);
return UInt32.Unsafe.fromField(maj);
}

function SigmaZero(x: UInt32) {
Expand Down Expand Up @@ -276,5 +276,5 @@ function sigma(u: UInt32, bits: TupleN<number, 3>, firstShifted = false) {

// since xor() is implicitly range-checking both of its inputs, this provides the missing
// proof that xRotR0, xRotR1, xRotR2 < 2^32, which implies x0 < 2^d0, x1 < 2^d1, x2 < 2^d2
return UInt32.from(xRotR0).xor(new UInt32(xRotR1)).xor(new UInt32(xRotR2));
return UInt32.Unsafe.fromField(xRotR0).xor(UInt32.Unsafe.fromField(xRotR1)).xor(UInt32.Unsafe.fromField(xRotR2));
}
Loading

0 comments on commit 0a3df06

Please sign in to comment.