IntersectMBO / plutus-apps

The Plutus application platform
Apache License 2.0
306 stars 214 forks source link

Figuring out 'txInfoSignatories' format #16

Closed hasufell closed 2 years ago

hasufell commented 3 years ago

I have a validator that is fairly straight forward:

validateSpend :: ValidatorType Burner
validateSpend (MyDatum addrHash) _myRedeemerValue ScriptContext { scriptContextTxInfo = TxInfo { txInfoSignatories = [addr] } } =
   addrHash == sha3_256 (getPubKeyHash addr)
validateSpend _ _  _ = traceError "Expecting exactly one signatory."

I compiled this plutus script, built a transaction manually and can lock funds successfully. I do this via wallet api, cardano-cli and other cli tools, because plutus isn't complete yet.

Now I want to lock funds for myself, so I can redeem them, but in order to do that I need to understand the exact format/encoding/hashing of txInfoSignatories. I've been searching for hours and tried many variations, using the wallet pubkey, account pubkey, address pubkey, hashing with Blake2b_224 etc. pp.

I find it hard to find in the documentation 1) what exact pubkey is this and 2) what exact hash/format is this and how can I create this hash from the command line given that I have full access to the wallet that signed the transaction.

All my attempts so far redeeming the funds ended with:

Command failed: transaction build  Error: The following scripts have execution failures:
the script for transaction input 0 (in the order of the TxIds) failed with The Plutus script evaluation failed: An error has occurred:  User error:
The provided Plutus code called 'error'.

Thanks.

luigy commented 3 years ago

@hasufell hmm how was MyDatum passed in the cli? I suspect the addrHash is probably actually a PubKey and is being compared against addr which is actually a PubKeyHash. see: cardano-cli address key-hash for getting the hash instead from a PubKey

hasufell commented 3 years ago

hmm how was MyDatum passed in the cli?

As a JSON string.

I suspect the addrHash is probably actually a PubKey and is being compared against addr which is actually a PubKeyHash. see: cardano-cli address key-hash for getting the hash instead from a PubKey

I tried all that too, I still get

Command failed: transaction build  Error: The following scripts have execution failures:
the script for transaction input 0 (in the order of the TxIds) failed with The Plutus script evaluation failed: An error has occurred:  User error:
The provided Plutus code called 'error'.

This suggests that it doesn't deserialize well, according to https://github.com/input-output-hk/plutus/issues/3938

So, IMO, something is wrong with the structure, not the data.

the-mercenaries-ltd commented 3 years ago

I'm having the same problem. [TacoBell from Discord]

Are we able to write any dynamic values to the command line so we can get something to debug when trying to submit the unlock transaction?

hasufell commented 3 years ago

I've been trying to print the datum in the playground where I input the pre-defined wallet pubkey hashes as a commitment, sth like:

newtype MyDatum = MyDatum BuiltinByteString
PlutusTx.makeLift ''MyDatum
PlutusTx.unstableMakeIsData ''MyDatum

lock :: AsContractError e => (PubKeyHash, Value) -> Contract w Schema e ()
lock (addr, lockedFunds) = do
    let hash = sha3_256 $ getPubKeyHash addr
    let tx = Constraints.mustPayToTheScript (MyDatum hash) lockedFunds
    logInfo @Prelude.String (Prelude.show (Aeson.encode ((MyDatum hash))))
     ...

So the input PubKeyHash from the playground is fabc30d46356151102cc57d427d338b8790b2244c1250159685400dd. The resulting aesonified datum will be: "09ea34dc60bad8c4d1e07043120e6a62f580fedcbdc0fb7ac4610a28429e066e".

I have no idea how to go from the pubkey hash to that datum. I converted to sha3_256 on the command-line and then ran cardano-cli transaction hash-script-data --script-data-value "\"$1\"", but that yielded a different hash.

luigy commented 3 years ago

@hasufell one way would be to go from MyDatum -> BuiltinData -> Cardano.Api.ScriptData before encoding into json which translates into the shape that the node expects. This also applies for Redeemer

-- note: unwrapping PubKeyHash or extra hashing is not required
newtype MyDatum = MyDatum PubKeyHash
let datum = MyDatum pkh
    scriptData = fromPlutusData $ builtinDataToData $ toBuiltinData datum
    scriptDataJson = scriptDataToJson ScriptDataJsonDetailedSchema scriptData
LBS.writeFile "datum-file" $ Aeson.encode scriptDataJson

Then you could get the datum hash with cardano-cli transaction hash-script-data --script-data-file datum-file or use it to build a tx cardano-cli transaction build ... --tx-in-datum-file datum-file ...


One more thing to note is that including txInfoSignatories from cardano-cli received support since version 1.30.0 using the --required-signer payment.skey

Tested on testnet, plutus rev 58c093a49eb7a369865e361179d649264fc817a4

hasufell commented 3 years ago

@luigy I tried the code you suggested and it outputs:

{"constructor":0,"fields":[{"bytes":"8e3dac9f8357279fd7b4e851211eb23fb4edb4de77f78959f0899f48b6d8d1e2"}]}

However, using cardano-cli transaction hash-script-data --script-data-file datum-file on it still yields a different hash than I got above. Maybe that hash wasn't correct to begin with. I'm not sure how to debug that further.


Then I went on using --tx-in-datum-file instead of --tx-in-datum-value for the testnet use case and I get better errors with the input, such as:

SON schema error within the script data: {"constructor":0,"fields":[{"_nothing":[]}]}
JSON object does not match the schema.
Expected a single field named "int", "bytes", "string", "list" or "map".
Unexpected object field(s): {"_nothing":[]}

I tried to use both {"bytes":"$1"} and {"constructor":0,"fields":[{"bytes":"$1"}]} when constructing datum hash and redeemer values... and I still get

Command failed: transaction build  Error: The following scripts have execution failures:
the script for transaction input 0 (in the order of the TxIds) failed with The Plutus script evaluation failed: An error has occurred:  User error:
The provided Plutus code called 'error'.
luigy commented 3 years ago

{"constructor":0,"fields":[{"bytes":"8e3dac9f8357279fd7b4e851211eb23fb4edb4de77f78959f0899f48b6d8d1e2"}]}

@hasufell the length doesn't look right to me is it actually using PubKeyHash? what plutus rev is in use?

Right vkey <- readFileTextEnvelope (AsVerificationKey AsPaymentKey) "payment.vkey"
let vkeyhash = PubKeyHash $ toBuiltin $ Api.serialiseToRawBytes $ Api.verificationKeyHash vkey
    datum = MyDatum vkeyhash
    scriptData = fromPlutusData $ builtinDataToData $ toBuiltinData datum
    scriptDataJson = scriptDataToJson ScriptDataJsonDetailedSchema scriptData
LBS.writeFile "datum-file" $ Aeson.encode scriptDataJson
print vkeyhash
-- 1e95b74d0ee6e70126374fbacaef351cbad2fa5adeaa98f38c6ffa7f
-- {"constructor":0,"fields":[{"bytes":"1e95b74d0ee6e70126374fbacaef351cbad2fa5adeaa98f38c6ffa7f"}]}
hasufell commented 3 years ago

@hasufell the length doesn't look right to me is it actually using PubKeyHash? what plutus rev is in use?

plutus-starter-devcontainer/v1.0.9. The commitment that was inserted into the input field in plutus playground is the PubKeyHash that the plutus playground shows for Wallet 2:

Screenshot_2021-09-29_18-22-04

In fact... I'm not 100% sure what this PubKeyHash is really referring to anyway.


Also, I have to note that I run sha3_256 on the pubkeyhash :) (as described earlier)

Without it, I get fabc30d46356151102cc57d427d338b8790b2244c1250159685400dd

hasufell commented 3 years ago

So I still haven't gotten any further. To reiterate my questions:

  1. What's the format of txInfoSignatories (the [PubKeyHash])? And how would you get that from the command line? Sth like:
    cat root.prv | cardano-address key child 1852H/1815H/0H/0/0 > addr.prv
    cat addr.prv | cardano-address key public --with-chain-code > addr.xvk
    cardano-cli address key-hash --payment-verification-key-file addr.xvk
  2. How does one debug The provided Plutus code called 'error'?
  3. Given newtype MyDatum = MyDatum BuiltinByteString and newtype MyRedeemer = MyRedeemer { _nothing :: () }, what is the correct JSON you have to pass to cardano-cli transaction build?
luigy commented 3 years ago

plutus-starter-devcontainer/v1.0.9.

@hasufell oh I see that would certainly be missing the latest hashing fixes for PubKey* and others for working with a non emulated node and would recommend updating to something like https://github.com/input-output-hk/plutus/commit/58c093a49eb7a369865e361179d649264fc817a4 which I've verified against

  1. the output of the last command corresponds to PubKeyHash on the plutus side
  2. if you're seeing this from cardano-cli I'm also in the process of figuring out how to get better error messages reported
  3. this should do it https://github.com/input-output-hk/plutus/issues/3991#issuecomment-929584087 with the addition that the flag for redeemer is --tx-in-redeemer-file and the file can be generated similarly like shown for datum

hmm though I suspect you might also be missing --required-signer payment.skey on your cardano-cli transaction build command which is what the validateSpend is checking against in the txInfoSignatories and hence you were seeing the errors 2.

Make sure to update newtype MyDatum = MyDatum BuiltinByteString into newtype MyDatum = MyDatum PubKeyHash and the validateSpend accordingly

hasufell commented 3 years ago

@luigy

I tried using your commit (cardano-cli is built via 959cd83b27e96715ba28a56f85e1d503bf13d41f though I believe), switched everything to reading files (redeemer/datum), added --required-signer (using the 1852H/1815H/0H/0/0 derivation path) and use the following json encoding for datum/redeemer:

I'm not sure I can use newtype MyDatum = MyDatum PubKeyHash, because the pubkey hash is hashed again via sha3_256 on purpose.

That's why validateSpend is this:

validateSpend :: ValidatorType Burner
validateSpend (MyDatum addrHash) _myRedeemerValue ScriptContext { scriptContextTxInfo = txinfo } =
   traceIfFalse "owner has not signed" (addrHash `elem` fmap (sha3_256 . getPubKeyHash) (txInfoSignatories txinfo))

And all that works perfectly fine on playground and in the test suite.


To give more information on how I create the transactions via cli

To create the pubkey, I use:

cat root.prv |
    cardano-address key child "1852H/1815H/0H/0/0" > addr.prv
cat addr.prv |
    cardano-address key public --with-chain-code > addr.xvk
cardano-cli address key-hash --payment-verification-key-file addr.xvk

To create the skey and vkey for the transaction signatures and the required-signer signature, I use:

# for required signer, derivation path is 1852H/1815H/0H/0/0
cat root.prv |
    cardano-address key child "$derivation_path" > addr.prv
cardano-cli key convert-cardano-address-key --shelley-payment-key --signing-key-file "addr.prv" --out-file "key.skey"
cardano-cli key verification-key --signing-key-file "key.skey" --verification-key-file "key.vkey"

To create the datum hash, I use:

# this hakes the json file as input, containing e.g. '{"constructor":0,"fields":[{"bytes":"abcdef"}]}'
cardano-cli transaction hash-script-data --script-data-file "$1"

I create the lock transaction with something like this:

    cardano-cli transaction build \
        --alonzo-era \
        --tx-in "$tx_in" \
        --tx-out "$addr+$amount" \
        $([ -n "$datum_hash" ] && echo "--tx-out-datum-hash=$datum_hash") \
        --change-address "$change_addr" \
        $([ "$NETWORK" = "testnet" ] && echo --testnet-magic="${TESTNET_MAGIC}" || echo "--mainnet") \
        --protocol-params-file "pparams.json" \
        --witness-override 2 \
        --required-signer="$6" \
        --out-file "$tx_out"

The redeem transaction is built with something like this:

    cardano-cli transaction build \
        --alonzo-era \
        --tx-in "$tx_in" \
        --tx-in-script-file "$script_file" \
        --tx-in-datum-file "$datum_file" \
        --tx-in-redeemer-file "$redeemer_file" \
        --tx-in-collateral "$tx_in_collateral" \
        --change-address "$change_address" \
        --protocol-params-file "pparams.json" \
        $([ "$NETWORK" = "testnet" ] && echo --testnet-magic="${TESTNET_MAGIC}" || echo "--mainnet") \
        --witness-override 2 \
        --required-signer="$8" \
        --out-file "$tx_out"

Now, I can successfully lock. But redeeming still causes the infamous and hard to debug error:

Command failed: transaction build  Error: The following scripts have execution failures:
the script for transaction input 0 (in the order of the TxIds) failed with:
The Plutus script evaluation failed: An error has occurred:  User error:
The provided Plutus code called 'error'
luigy commented 3 years ago

@hasufell nice, thanks for the detailed response. Will try to take a look at this soon and can even pair up if you would like to build an example for future reference. Feel free to ping me on the Community discord if you're there

luigy commented 2 years ago

@hasufell this is what the lock I tried looks like:

funds=98000000 # 98 ADA
cardano-cli transaction build \
    --alonzo-era \
    --tx-in "$txid#$txix" \
    --tx-out $(cat $scriptaddr)+$funds \
    --tx-out-datum-hash $datumhash \
    --change-address $useraddr \
    --required-signer $requiredsigner \
    $magic \
    --out-file $txdraft

and the unlock:

cardano-cli transaction build \
  --alonzo-era \
  --protocol-params-file $protocolparams \
  --tx-in $script_txid#$script_txix \
  --tx-in-script-file $script \
  --tx-in-datum-file $datumfile \
  --tx-in-redeemer-file $redeemerfile \
  --tx-in-collateral $collateral_txid#$collateral_txix \
  --required-signer $requiredsigner \
  --change-address $useraddr \
  $magic \
  --out-file $txunlockraw
hasufell commented 2 years ago

@luigy still doesn't work for me. With latest cardano-node etc I get:

Command failed: transaction build  Error: The following scripts have execution failures:
the script for transaction input 0 (in the order of the TxIds) failed with:
The Plutus script evaluation failed: An error has occurred:  User error:
The provided Plutus code called 'error'.
Caused by: [ (force (builtin headList)) (con list (data) []) ]

The full code is here: https://git.io/JPD3v


Feel free to ping me on the Community discord if you're there

I tried. But the server has some weird captcha verification, which is broken.

hasufell commented 2 years ago

Ok, I was able to track down the issue further.

As described above, we use a redeemer dummy type, because whether redeeming works or not is supposed to be discovered by the txInfoSignatories, not the redeemer datum:

-- | Redeemer holds no data, since the address is taken from the context.
newtype MyRedeemer = MyRedeemer { _nothing :: () }
PlutusTx.makeLift ''MyRedeemer
PlutusTx.unstableMakeIsData ''MyRedeemer

I followed down the code of cardano-api in the node, which apparently does the json encoding/decoding: https://github.com/input-output-hk/cardano-node/blob/d95ef21062a50acf8f35f1f5d94f8a8a70f7510b/cardano-api/src/Cardano/Api/ScriptData.hs#L431

In a repl, I was then able to generate the the json from the node code directly:

> Data.Aeson.encode $ scriptDataToJson ScriptDataJsonDetailedSchema $ (fromPlutusData $ builtinDataToData $ toBuiltinData (MyRedeemer ()))
"{\"constructor\":0,\"fields\":[{\"constructor\":0,\"fields\":[]}]}"

In my code, I tried the json values "", "{\"constructor\":0,\"fields\":[]}" and some other combinations, but none of them match the above. After fixing that, I could submit the redeem transaction and got a different error:

the script for transaction input 0 (in the order of the TxIds) failed with:
The Plutus script evaluation failed: An error has occurred:  User error:
The provided Plutus code called 'error'.
Script debugging logs: owner has not signed
PT5

This is much better. Now the script actually tells me that it couldn't find the signature in the txInfoSignatories. After further tests, these apparently evaluate to an empty list, although I'm passing --required-signer to cardano-cli transaction build.

So that's at least some progress.

luigy commented 2 years ago

@hasufell nice, well done. With the new error you might be bumping into a bug that was fixed by https://github.com/input-output-hk/cardano-node/pull/3319 in which PaymentExtendedKeys were being dropped from requiredSignatories list. I skimmed through how your keys are being converted and it might be the case https://github.com/hasufell/proof-of-burn-cardano/blob/26bfa911a371d37e69812321bc787df933e03d92/docker/cardano-cli/wallet.sh#L343 https://github.com/input-output-hk/cardano-node/blob/d95ef21062a50acf8f35f1f5d94f8a8a70f7510b/cardano-cli/src/Cardano/CLI/Shelley/Run/Key.hs#L533

AngelCastilloB commented 2 years ago

Ok, I was able to track down the issue further.

As described above, we use a redeemer dummy type, because whether redeeming works or not is supposed to be discovered by the txInfoSignatories, not the redeemer datum:

-- | Redeemer holds no data, since the address is taken from the context.
newtype MyRedeemer = MyRedeemer { _nothing :: () }
PlutusTx.makeLift ''MyRedeemer
PlutusTx.unstableMakeIsData ''MyRedeemer

I followed down the code of cardano-api in the node, which apparently does the json encoding/decoding: https://github.com/input-output-hk/cardano-node/blob/d95ef21062a50acf8f35f1f5d94f8a8a70f7510b/cardano-api/src/Cardano/Api/ScriptData.hs#L431

In a repl, I was then able to generate the the json from the node code directly:

> Data.Aeson.encode $ scriptDataToJson ScriptDataJsonDetailedSchema $ (fromPlutusData $ builtinDataToData $ toBuiltinData (MyRedeemer ()))
"{\"constructor\":0,\"fields\":[{\"constructor\":0,\"fields\":[]}]}"

In my code, I tried the json values "", "{\"constructor\":0,\"fields\":[]}" and some other combinations, but none of them match the above. After fixing that, I could submit the redeem transaction and got a different error:

the script for transaction input 0 (in the order of the TxIds) failed with:
The Plutus script evaluation failed: An error has occurred:  User error:
The provided Plutus code called 'error'.
Script debugging logs: owner has not signed
PT5

This is much better. Now the script actually tells me that it couldn't find the signature in the txInfoSignatories. After further tests, these apparently evaluate to an empty list, although I'm passing --required-signer to cardano-cli transaction build.

So that's at least some progress.

I was also stuck with the same issue (wrong redeemer format), we are also not using the redeemer and were passing [] as a dummy redeemer value. For the longest time, we were looking into the Datums because we were not using the redeemer so we didn't think the problem was there, if not were for your finding we would still be looking.

This was the error we were getting:

The Plutus script evaluation failed: An error has occurred:  User error:
The provided Plutus code called 'error'.
Caused by: [ (builtin unConstrData) (con data #80) ]
hasufell commented 2 years ago

@hasufell nice, well done. With the new error you might be bumping into a bug that was fixed by input-output-hk/cardano-node#3319 in which PaymentExtendedKeys were being dropped from requiredSignatories list. I skimmed through how your keys are being converted and it might be the case https://github.com/hasufell/proof-of-burn-cardano/blob/26bfa911a371d37e69812321bc787df933e03d92/docker/cardano-cli/wallet.sh#L343 https://github.com/input-output-hk/cardano-node/blob/d95ef21062a50acf8f35f1f5d94f8a8a70f7510b/cardano-cli/src/Cardano/CLI/Shelley/Run/Key.hs#L533

I rebuilt with that commit and it indeed fixed it and I could submit the redeem transaction to testnet and it apparently validated correctly and I got my funds back.

So, to reiterate (I'll probably write an article on it too):

  1. feed your data (either Datum or Redeemer) to the function Data.Aeson.encode . scriptDataToJson ScriptDataJsonDetailedSchema . fromPlutusData . builtinDataToData . toBuiltinData to figure out the final encoding
  2. make sure to use cardano-cli transaction build --tx-in-redeemer-file $FILE1 --tx-in-datum-file $FILE2 with files that contain the json content from the step above when constructing transactions (details: there are two json schemas for decoding... the extended one ScriptDataJsonDetailedSchema is used when reading files as opposed to direct json strings and has better errors in my experience)
  3. make sure to run at least with cardano-cli commit 9dd31b3f8f17fba30882e98bb02810a7a504ba38, so txInfoSignatories are not dropped
  4. if the script fails without a PT<int> error, it didn't even manage to evaluate, but failed during decoding
  5. generating wallet pubkey (e.g. for use in datum or redeemer) can be done like so: cardano-cli address key-hash --payment-verification-key "$(echo "<your recovery phrase>" | cardano-address key from-recovery-phrase Shelley | cardano-address key child "1852H/1815H/0H/0/0" | cardano-address key public --with-chain-code)" (make sure to include it via cardano-cli transaction build --required-signer=key.skey, which requires the signing key of the same derivation path... I also had to set --witness-override 2, not sure if that's still needed)

Thanks for all your help.


@AngelCastilloB glad to hear

luigy commented 2 years ago

Nice! :tada: