#Signature required when using custom tokens with a ZkApp and ZkAppTokenHolder

180 messages ยท Page 1 of 1 (latest)

scenic frigate
#

I'm following the dex example with the following setup:

  • tokenContract
  • zkApp
  • zkAppTokenHolder

When i try to withdraw through zkApp -> zkAppTokenHolder, it keeps asking me for signature of the zkApp/zkAppTokenHolder (same keypair) even tho i have permissions for send set to proofOrSignature on the zkAppTokenHolder.

What am i missing? @sage radish ๐Ÿค”

EDIT: i have send: proof() permission on all of the three accounts mentioned above, still keeps asking for signature of the zkApp/zkAppTokenHolder

#

I notice that in dex.redeemLiquidity the balance is manipulated manually instead of using token.transfer, why?

#

first account update is my token holder, i want this to be proven not signed.
second account update is my user who's withdrawing and they are receiving the balance which is ok

#

TokenHolder has send: proof

#

TokenContract access: proof also didnt help

#

at this point i'd conclude that the send permission isnt respected ๐Ÿค”

coral bronze
scenic frigate
#

yep, i mean this is an obvious permission issue i remember dealing with this back then,.. but im trying to follow the DexTokenHolder approach.. and there it works somehow so lets wait for sensei to reply in the morning ๐Ÿ˜„

#

after this i can get AccountUpdate tattooed somewhere

sage radish
#

Hey, if you get authorizationKind: { isSigned: true } in your update then you must be calling some method that sets this. The DEX example doesn't do this

#

The only methods I see that make an account update expect a signature, as far as I can tell, are createSigned and requireSignature. Are you using those @scenic frigate?

scenic frigate
#

i dont do createSigned or requireSignature, i just call .send inside my token contract eventually

#

it must be set by snarky based on send/access permissions somewhere?

#

i can push this to a repo so you can check it out but again its a lot of code

scenic frigate
#

@sage radish can you please try it out with the following repository?

  1. git clone https://github.com/stove-labs/mip-token-standard
  2. cd mip-token-standard && npm i
  3. npm run test
  4. This test will fail: https://github.com/stove-labs/mip-token-standard/blob/develop/packages/token/src/token.test.ts#L239

The account structure is as follows:

  1. Token
    • ThirdPartyTokenHolder (child of token)
  2. ThirdPartyContract (not child of token, but with same address as ThirdPartyTokenHolder)

The withdraw test calls ThirdParty.withdraw(10) -> ThirdPartyTokenHolder.withdraw(10) + Token.approveAccountUpdate(tokenHolder.self), signed by Alice as the sender.

Actual behaviour:
The error is asking for a signature of ThirdParty/ThirdPartyTokenHolder.

Expected behaviour:
I'd expect the withdraw to withdraw 10 tokens through the ThirdPartyTokenHolder, authorizing by proof, not signature.

#

Please let me know if there is any more info i can provide into what happens in the test, it has a lot of moving parts

#

i'd like to solve the signature problem first, then i'll have more questions about how the account updates & approvals are composed ๐Ÿ‘ฏโ€โ™‚๏ธ

sage radish
#

Ok, so the signature is required because you're calling this.token.send, which requires a signature from the from account

#

this.token.send only works if the sender is a user account which can authorize by signature

scenic frigate
#

even tho i tried to set send: proof for both contracts?

sage radish
#

for zkApps sending with proof auth you can't use that method

scenic frigate
#

๐Ÿ˜… is that intentional?

sage radish
#

yes.. this.token was designed to be used by the token manager contract, but noone else

#

it's fundamentally wrong to use it with a contract_holding_ tokens.. it would refer to the wrong token

#

but, when sending from a zkApp, the sending has to be done on the account update that is created by the proof

scenic frigate
sage radish
#

that's true but see my last message

#

it doesn't work to go via
token holder -> token manager -> send

#

because the send then isn't part of the tokenholder.self update which is authed with the proof

#

you have to do it directly from the token holder method:
this.send or this.balance.sub

#

inside withdraw

scenic frigate
#

let me try real quick

sage radish
#

and the token manager approval has to be given to that from outside

scenic frigate
#

TokenHolder:

#

third party:

#

i still run into the same signature error unfortunately

#

did you manage to get it to work on your side? i tried to follow your instructions but no luck

#

what did i miss? its relatively difficult to dissect how the account updates should be composed from the dex example

#

if i do this instead it goes into a loop

exotic surge
#

have you tried with accountUpdate balance .sub ?

scenic frigate
#

i really dont want to shard the logic for sub/mint etc

sage radish
#

TokenHolder can't use this.token.send @scenic frigate

#

It's the wrong token

#

this.token is the token that the this contract manages

#

try this.send

scenic frigate
#

aha lol

#

yeah makes sense

#

ok and the rationale that the child cant call parent for approval also makes sense if i think about it in this way

#

TokenHolder

#

why does this.send produce a non zero balance change?

#
approveAccountUpdate {
        label: 'TokenHolder.withdraw()',
        publicKey: '..w46N',
        tokenId: '..6GUS',
        balanceChange: { magnitude: '10', sgn: 'Negative' },
        callDepth: 2,
        mayUseToken: { parentsOwnToken: true, inheritFromParent: false },
        authorizationKind: {
          isSigned: false,
          isProved: true,
          verificationKeyHash: '3392518251768960475377392625298437850623664973002200885669375116181514017494'
        },
        authorization: undefined
      }

where is the balance increment on the to address done and how to go about approving such updates? Do we really need to approveAndSend like you do in the dex example?

#

This is quite a lot of friction, it feels like i am doing something terribly wrong

sage radish
#

the to balance change should be a separate account update

#

isn't it?

scenic frigate
#

but its not in tokenHolder.self

#

which is what i get approved by Token.approveAccountUpdate

sage radish
#

gotcha

#

yeah easier to use balance.sub and split it up for now

scenic frigate
#

isnt there a way to do it with send?

#

sub & split just seems super wrong

#

like transfering from user -> contract is just token.transfer and contract -> user i have to sacrifice a goat

sage radish
#

the original idea was that it should be possible with the following parent child structure:
token manager -> token hodler -> to account
but that would need an additional argument (Layout) to approve which specifies the deeper tree structure, and that is kind of buggy rn

#

so better to go with shallow trees now

#
      token-manager
        /      \
token-holder   receiver
 -10 balance    +10 balance
scenic frigate
#

i use AnyChildren layout

sage radish
#

anychildren is insecure in that case, because it means the token manager can't inspect the receiver

#

so the receiver could do anything, like minting tokens

#

and the token manager won't know

#

so, to clarify, any children is insecure in the case I mentioned earlier where receiver is a grand child of token manager

#

bc it means that the token manager doesn't care about the receiver and won't summon its account update into its circuit

#

when token-holder is the direct child and doesn't have its own chidlren, you can use the default (NoChildren) which is secure

#

in approve

scenic frigate
#

i stopped checking for non zero balance change and i get Token_owner_not_caller:

 approveAccountUpdate {
        label: 'TokenHolder.withdraw()',
        publicKey: '..EGgP',
        tokenId: '..yCnC',
        balanceChange: { magnitude: '10', sgn: 'Negative' },
        callDepth: 2,
        mayUseToken: { parentsOwnToken: true, inheritFromParent: false },
        authorizationKind: {
          isSigned: false,
          isProved: true,
          verificationKeyHash: '3392518251768960475377392625298437850623664973002200885669375116181514017494'
        },
        authorization: undefined
      }
sage radish
#

can you post the whole tx?

scenic frigate
#

sure 1mom

#
[
      {
        publicKey: '..LS6b',
        fee: '0',
        nonce: '2',
        authorization: '..EzRQ'
      },
      {
        label: 'ThirdPartySmartContract.withdraw()',
        publicKey: '..dCVn',
        mayUseToken: { parentsOwnToken: false, inheritFromParent: false },
        authorizationKind: {
          isSigned: false,
          isProved: true,
          verificationKeyHash: '3392518251768960475377392625298437850623664973002200885669375116181514017494'
        },
        authorization: undefined
      },
      {
        label: 'Token.approveAccountUpdate()',
        publicKey: '..jtPC',
        callDepth: 1,
        mayUseToken: { parentsOwnToken: false, inheritFromParent: false },
        authorizationKind: {
          isSigned: false,
          isProved: true,
          verificationKeyHash: '3392518251768960475377392625298437850623664973002200885669375116181514017494'
        },
        authorization: undefined
      },
      {
        label: 'TokenHolder.withdraw()',
        publicKey: '..dCVn',
        tokenId: '..VS3V',
        balanceChange: { magnitude: '10', sgn: 'Negative' },
        callDepth: 2,
        mayUseToken: { parentsOwnToken: true, inheritFromParent: false },
        authorizationKind: {
          isSigned: false,
          isProved: true,
          verificationKeyHash: '3392518251768960475377392625298437850623664973002200885669375116181514017494'
        },
        authorization: undefined
      },
      {
        publicKey: '..LS6b',
        tokenId: '..VS3V',
        balanceChange: { magnitude: '10', sgn: 'Positive' },
        callDepth: 3,
        mayUseToken: { parentsOwnToken: true, inheritFromParent: false },
        authorizationKind: { isSigned: false, isProved: false, verificationKeyHash: '0' }
      }
    ]
sage radish
#

ah ok you have the manager -> holder -> receiver layout

#

in that case the receiver needs mayUseToken = InheritFromParent

#

not ParentsOwnToken

scenic frigate
#

ok here it looks like i get the positive balance update automatically from .send, which is good

#

i didnt split it yet

#

my token holder:

@method
  public withdraw(to: PublicKey, amount: UInt64) {
    this.send({ to, amount });
    this.self.body.mayUseToken = AccountUpdate.MayUseToken.ParentsOwnToken;
  }
#

i assume at call depth:2 i want ParentsOwnToken and at callDepth:3 i want InheritFromParent? how can i access the positive balance update?

sage radish
#
let receiver = this.send({ to, amount });
receiver.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent;
this.self.body.mayUseToken = AccountUpdate.MayUseToken.ParentsOwnToken;
scenic frigate
#

aha send returns an update

sage radish
#

I hope

scenic frigate
#

it doesnt

#

๐Ÿ˜„

sage radish
#

damn

#

then do it by hand

scenic frigate
#

where does that calldepth3 AU live?

#

if its not returned by send

#

and i assume self.body is calldepth:2

sage radish
#
let receiver = AccountUpdate.create(to, this.body.tokenId);
receiver.balance.subInPlace(...);
this.balance.addInPlance(...);
// etc
scenic frigate
#

so this.send is basically useless ๐Ÿ˜„

sage radish
#

ah sry I mixed up addInPlace and subInPlace

#

it should return the update.. will make that change

scenic frigate
#

yay ok at least we found that

#

ok thanks let me try manually

exotic surge
scenic frigate
#

i can't say im any smarter after this, will have to re read and consolidate my thoughts

#

TokenHolder.withdraw this works โœ…

sage radish
#

sweet

scenic frigate
#

tl;dr; on the whole tread is:

  • this.send instead of this.token.send in TokenHolder, since we're using the parent token, not our own token we're managing as a manger
  • this.send does not return an AU, so we do it manually, because be need to set InheritFromParent (btw @sage radish what does ParentsOwnToken vs InheritFromParent do?)
  • approval has to happen from outside of the token holder, the child of a token manager cant ask for an approval itself
#

if only this was written down somewhere before i started working on this ๐Ÿ˜„

#

also mayUseToken wasnt there back when i wrote my bridge contracts, so thats new to me completely

#

from my POV, both the 'to account' and the token holder are children of the token manager, why do they need a different mayUseToken? thats the last thing that doesnt make sense to me yet

#

I GET IT

sage radish
scenic frigate
#

bcs toAccountUpdate is a child of tokenholder ๐Ÿ˜„

#

so it inherits from parent = token holder

scenic frigate
#

olala, i wish i knew about this sooner!

scenic frigate
#

What would be the strategy to approve the depth2 + depth3 account updates and from where you'd suggest to do it? Under what rules should these be validated?

Ideally i'd like to validate them all together to ensure the overall balance change is 0

#

๐Ÿค”

sage radish
#

the idea would be to use a layout like StaticChildren(NoChildren) ... or something more flexible

scenic frigate
#

do i need to approve them both independently? that'd be hard since they are supposed to cancel each other out

sage radish
#

and then just access all the children (they'll be static size, missing ones replaced by dummies), and sum up their balances

#

no the approval of the top level child is enough

#

"approve" really means make it a child

scenic frigate
#

ok so when im approving the TokenHolder.self, how do i access self children?

#

tokenHolder.self.children.accountUpdates

sage radish
#
accountUpdate.children.accountUpdates

I think

#

ah

#

but the non standard layouts don't work yet

scenic frigate
#

Whats a standard and non standard layout? Any and No children are standard?

sage radish
#

that's why I was recommending the shallow layout before

scenic frigate
#

how can i implement a generic approval method that can go through an arbitrary number of children yet be static?:D

sage radish
scenic frigate
#

ooof

sage radish
#

in OCaml though

scenic frigate
#

ok and if i wanted to support lets say a fixed size of account updates, such as 3?

sage radish
#

you'd wait till we fixed the bug

scenic frigate
#
AccountUpdate.Layout.StaticChildren([AccountUpdate.Layout.NoChildren])
sage radish
#

yeah

scenic frigate
#

would this mean that i expect my account udpate to have one child that has no further children?

#

in my case, i have one child, bcs i go from call depth 2 -> 3

sage radish
#

no this, w/o the array:

AccountUpdate.Layout.StaticChildren(AccountUpdate.Layout.NoChildren)
#

exactly, the layout refers to the children of the approved (= child) update, and their children, ...

scenic frigate
#

ok so i need to check the update itself and its children

#

sum up the balance changes, and they should equal zero

#

thats a universal approval, but without a universal children layout

#

i'm starting to get a hang of this nice

#

i also began reading the MIP ๐Ÿ™‚ which helps!

#

thinkface

#

this seems to work! i tested by manualy deducting a larger balance than adding and it blew up

#

@sage radish is this the custom children layout prover bug you mentioned?

    thrown: Array [
      0,
      Array [
        248,
        MlBytes {
          "c": "Jsoo_runtime.Error.Exn",
          "l": 22,
          "t": 0,
        },
        237,
      ],
      [Error: ("Error: assert_equal: 0x11279CACA2D5F0BF291FCECA082448342ED4A4CB53DF1D31CCA8E9707EB4D154 != 0x0000000000000000000000000000000000000000000000000000000000000000")],
    ]
#

what does AnyChildren do in comparison to StaticChildren(...) when it comes to compile & prove? since in my screenshot above i want to iterate over children, how will the compile/prove know how many dummies to provide?

sage radish
#

anychildren means the children aren't hashed in the circuit to produce the children hash which is part of the public input. instead, the hash is just witnessed as an unconstrained field element. which means, the proof doesn't see the children essentially, the prover can pick them how they want. the assertions you do on the account updates won't enter the circuit I think because their done on constants not connected to the circuit

#

not useful for token managers ๐Ÿ˜„

scenic frigate
#

makes sense, so can we conclude that we cant implement a zero balance change policy until we can use StaticChildren?

#

bcs in my use case, i cant use AnyChildren since its a security hole, and i cant use NoChildren either since i have call depth 2->3

sage radish
#

yeah, the only way in your case is with the shallow layout I showed earlier

#
      token-manager
        /      \
token-holder   receiver
 -10 balance    +10 balance
scenic frigate
#

ok, that means i can use NoChildren but the receiver account update has to come from the token manager instead

#

ok all clear

sage radish
#

where the token holder doesn't know about the receiver and just subs its balance

scenic frigate
#

is there a ticket for the StaticChildren bug? so i can keep track

sage radish
scenic frigate
sage radish
#

actually it's on deck for me bcs I thought it blocks a few of you testers

#

it's that "failed to fill buffer" error

scenic frigate
#

nah i didnt get failed to fill i think

#

wait let me try ๐Ÿ™‚

#

this should mean that i allow one child with no further children, supporting my use case

sage radish
#

๐Ÿ‘

scenic frigate
#

this should check update + all children to have a sum of zero balance change

#

it works without proving

#

now lets prove..

#

as far as the token standard goes it will be really fun to define a 'universal' approval method ๐Ÿ˜„ for all layout combinations since AnyChildren isnt applicable

#

any suggestions apart from a lesson in combinatorics? ๐Ÿค”

#

this is my error when i run the approval code above^

    thrown: Array [
      0,
      Array [
        248,
        MlBytes {
          "c": "Jsoo_runtime.Error.Exn",
          "l": 22,
          "t": 0,
        },
        237,
      ],
      [Error: ("Error: assert_equal: 0x20F51AC4E6830E39869D613040CEDE6B25D99CDECA1508DDECA2A3D48A25B023 != 0x0000000000000000000000000000000000000000000000000000000000000000")],
    ]
#

can you see anything wrong with my code on the screenshot?

#

bcs when running without proving it works

sage radish
#

no I don't see issues

scenic frigate
#

is it the same bug then? i pushed to the same repo.. if you set proof = true in describeContract you can reproduce

sage radish
#

thanks, might take a look tomorrow