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

Token transfers between zkApps #300

Closed
mitschabaude opened this issue Jul 25, 2022 · 3 comments · Fixed by #326
Closed

Token transfers between zkApps #300

mitschabaude opened this issue Jul 25, 2022 · 3 comments · Fixed by #326
Assignees

Comments

@mitschabaude
Copy link
Contributor

mitschabaude commented Jul 25, 2022

The token() APIs burn and send should enable authorizing token sending with a proof

Initial discussion here: #273 (comment)

@MartinMinkov
Copy link
Contributor

MartinMinkov commented Aug 9, 2022

Current Approach

To serve as a point of discussion and documentation, we outline the current approach being used to implement token transfers between zkApps.

We want to be able to support zkApp's to transfer tokens with proofs instead of just signatures. The reason for this is to support one zkApp that is holding a custom token to transfer it without requiring a signature from the token owner zkApp. Currently, there is no way to support this since the zkApp that wants to transfer a custom token must get authorization from the token owner zkApp, and requiring a signature does not work from a programmatic standpoint. For this sort of interaction, we need to enable token transfers to be authorized by proofs from the token owner since those can be generated asynchronously without requiring a signature from a private key. we need the token owner to inspect child parties that use it's custom token id.

To solve this issue, we present a new class to be added to SnarkyJS called Callback which will represent a callback method that a child zkApp can create and then pass into the token owner contract so that a transfer can be authorized.

Using a Callback to generate proofs

The only way to generate proofs for zkApps is via a @method declaration on the Smart Contract. This is because we need to compile/generate the verification key first before creating proofs for a specific zkApp. To get the verification key of a zkApp, we must analyze all methods that a zkApp supports and take that information into account when compiling.

With this in mind, we can declare a new class called Callback which holds a function as well as parameters to authorize to the callback. The intention of the usage of this class is to use this Callback class to store a smart contract method on a child zkApp that we wish to get authorization for from the parent zkApp. Once we store this child method in the Callback, we can then pass the callback into the parent zkApp where the parent zkApp will run the contained child method to generate the Parties the child wants to create. This enables the parent zkApp to inspect the Parties layout and enforce certain conditions if the zkApp developer wishes to do so.

Code Example

Let's assume we have a parent token contract called TokenContract defined as below:

class TokenContract extends SmartContract {
  ...
  @method sendTokens(
    senderAddress: PublicKey,
    receiverAddress: PublicKey,
    callback: Experimental.Callback<any>
  ) {
    let senderParty = Experimental.partyFromCallback(this, callback);
    let amount = UInt64.from(1_000);
    let negativeAmount = Int64.fromObject(senderParty.body.balanceChange);
    negativeAmount.assertEquals(Int64.from(amount).neg());
    let tokenId = this.experimental.token.id;
    senderParty.body.tokenId.assertEquals(tokenId);
    senderParty.body.publicKey.assertEquals(senderAddress);
    let receiverParty = Experimental.createChildParty(
      this.self,
      receiverAddress,
      { caller: tokenId, tokenId }
    );
    receiverParty.balance.addInPlace(amount);
  }
}

You will notice that the sendTokens method takes a new parameter called c which is the Callback. The code below is how a child zkApp will initialize and interact with this Callback.

class ZkAppB extends SmartContract {
  // A method to send custom tokens derived from `TokenContract`
  @method authorizeSend() {
    let amount = UInt64.from(1_000);
    this.balance.subInPlace(amount);
  }
}
...
let tx = await Local.transaction(feePayer, () => {
let authorizeSendingCallback = new Experimental.Callback(
    zkAppC,
    'authorizeSend',
    []
  );
  // we call the token contract with the callback
  tokenZkApp.sendTokens(zkAppCAddress, tokenAccount1, authorizeSendingCallback);
});
await tx.prove();

This callback will be run by the parent zkApp and then authorize the callback with proof. The child zkApp can then call the parent zkApp method sendTokens specifying its own method as a callback to do inside a transaction block. Then, when the transaction block is completed, we can prove the transaction as normal methods on a zkApp. This concludes the flow of how this feature is envisioned.

Deploying a child with a custom token ID

Another change to be added is to make a tokenId an optional parameter to be passed into a SmartContract constructor. This will enable a zkApp to be deployed with a specific tokenId it can reason about and it hides the implementation details of dealing with the tokenId in the parties from the developer. All the developer has to do is get the token id of the parent contract it wishes to interact with and pass that into its constructor like so:

let tokenZkApp = new TokenContract(tokenZkAppAddress);
let tokenId = tokenZkApp.token().id;
let zkAppB = new ZkAppB(zkAppBAddress, tokenId);

One requirement that is imposed on the child zkApp when they deploy with a custom token id is that they must get authorization from the parent zkApp. Concretely, this means we cannot deploy as normal if we specify a different custom token id since we would need permissions from the parent zkApp just like regular custom token interactions. To get around this, we will add a new type of deploy method which handles this specific case of a zkApp wishing to specify a different token id and authorize the child with proof. This custom deployment would be handled by the parent zkApp by adding an additional @method in it's declaration like so:

class TokenContract extends SmartContract {
  ...
  @method tokenDeploy(deployer: PrivateKey) {
    // Authorize a child zkApp using TokenContract's custom token id to be deployed
    super.token().deploy({ deployer });
  }
}
...
let tx = await Local.transaction(feePayer, () => {
  Party.fundNewAccount(feePayer);
  tokenZkApp.tokenDeploy(zkAppBKey);
});
tx.sign([zkAppBKey]);
tx.send();

As a final thing to note, this means that each instantiation of a zkApp with a different token id must be deployed for each unique token id. The same zkApp code can be re-used but must be initialized with a different token id and redeployed.

@mrmr1993
Copy link
Member

For this sort of interaction, we need to enable token transfers to be authorized by proofs from the token owner since those can be generated asynchronously without requiring a signature from a private key.

Am I correct in reading that this is essentially a workaround for 'proving is async, but circuits are not'? If so, it seems like we should probably just fix that.

@mitschabaude
Copy link
Contributor Author

@MartinMinkov @mrmr1993 I think the description is a bit inaccurate which might lead to confusion. We can already create proofs by the token owner / token contract, what we're missing is a way to add a proof authorization from the sender - a zkApp that wants to send tokens.

We need a callback for that since the token contract wants to inspect its child parties, and the sender-zkApp needs to have freedom in what exact party it creates. So, for the token contract to support generic sender-zkApps, it has to be given a way to witness a party whose precise structure it doesn't know, and make assertions about it. That's what the callback is for (it's creating the sender-zkApp party, to be witnessed and inpected by the token contract)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants