Creating policy script key from existing CLI keyHash and slot

I am trying to build a multisig transaction application by following the Nami Multisig documentation here.

As you probably know that you will need a policyID and policy script to perform a transaction. And nami-js already have a function that generates new policyScript and policyID in here.

The problem is I don’t want to generate a new policyID, I would like to use the policy ID I have created through CLI. Here is my policy.script file

{
  "type": "all",
  "scripts":
  [
   {
     "type": "before",
     "slot": 16584779
   },
   {
     "type": "sig",
     "keyHash": "7242f6c68967db0b1aba097ef5a86c558d024902365b4a883b42154f"
   }
  ]
}

And the generated ID is

899418e2dd89a092f00db1484c693ec80040c5479b8058183ae7dea2

As I said, I would like to use my existing policy ID, so I modified to code to generate a policy script based on my keyHash and slot.

async createLockingPolicyScript(address, networkId, expirationTime) {
    
    const ttl = 16584779;
    const paymentKeyHash = this.S.Ed25519KeyHash.from_bytes(Buffer.from('7242f6c68967db0b1aba097ef5a86c558d024902365b4a883b42154f', 'hex'))

    const nativeScripts = this.S.NativeScripts.new();
    const script = this.S.ScriptPubkey.new(paymentKeyHash);
    const nativeScript = this.S.NativeScript.new_script_pubkey(script);
    const lockScript = this.S.NativeScript.new_timelock_expiry(
        this.S.TimelockExpiry.new(ttl)
    );
    nativeScripts.add(lockScript);
    nativeScripts.add(nativeScript);
    const finalScript = this.S.NativeScript.new_script_all(
        this.S.ScriptAll.new(nativeScripts)
    );
    const policyId = Buffer.from(
        this.S.ScriptHash.from_bytes(
            finalScript.hash().to_bytes()
        ).to_bytes(),
        "hex"
    ).toString("hex");
    
    return {
        id: policyId,
        script: Buffer.from(finalScript.to_bytes()).toString("hex"),
        paymentKeyHash: Buffer.from(paymentKeyHash.to_bytes(), "hex").toString("hex"),
        ttl
    };
}

It worked and it generated output as follows:-

policyId: 899418e2dd89a092f00db1484c693ec80040c5479b8058183ae7dea2
policyScript: 82018282051a00fd104b8200581c7242f6c68967db0b1aba097ef5a86c558d024902365b4a883b42154f
paymentKeyHash: 7242f6c68967db0b1aba097ef5a86c558d024902365b4a883b42154f
ttl: 16584779

But the problem is, when I’m trying to perform a transaction, I am receiving the following error:-

transaction submit error ShelleyTxValidationError ShelleyBasedEraBabbage (ApplyTxError [UtxowFailure (FromAlonzoUtxowFail (WrappedShelleyEraFailure (ScriptWitnessNotValidatingUTXOW (fromList [ScriptHash \\"899418e2dd89a092f00db1484c693ec80040c5479b8058183ae7dea2\\"]))))])

But why? Why do you want to have an address that you can only spend from until a certain slot is reached and then never again? If you sent something to that address after slot 16584779 it will be stuck there forever.

Which address did you generate from the script hash? I don’t see that anywhere in your report.

Is that also the address you are trying to spend from in that transaction?

Hi HeptaSean,

Hope you are doing well. If I remember correctly, you are the one who once helped me with a similar issue before.

To answer your questions:-

  1. I don’t actually wanna set an expiriy date, I just don’t know whether it’s possible to generate a policy ID without an expiry slot. I am planning to use this policy ID and script for minting NFTs and I am actually ok if the policy ID gets locked after a certain slot.

  2. Previously I have generated a policy.skey and policy.vkey and then generated the keyHash with the following command

cardano-cli address key-hash --payment-verification-key-file policy.vkey

Then I created a policy.script with the keyHash (sample given in the question) and ran the following command to generate a policy ID.

cardano-cli transaction policyid --script-file policy.script > policyID

Now, I am trying to develop a multi-sig minting app with cardano-serialization-lib which will use my CLI generated policy ID to sign the transaction. However, cardano-serialization-lib requires a policyID and policyScript to build and sign a transaction. So I followed your instruction here to generate the policyScript.

It all went right, but when I submitted the transaction, I was getting the error I mentioned in the question. I can show you the code of my frontend and backend service if you want to.

Regards

Of course. Just create a policy without it.

If you want to do multi-sig minting as in the Nami wallet example you linked, you cannot just reuse the same policy ID. Requiring the signature of the buyer and the minter is a different policy and therefore also a different policy ID.

You can reuse your key and its hash, so that you have to sign/witness the transaction with that key on the command line, but the policy ID will be different.

ScriptWitnessNotValidatingUTXOW sounds like you were giving the policy script not as a minting script, but as a UTxO validation script. (Which is why I thought you were trying to do multi-signature script addresses, not just minting.)

I think I am missing something. My goal is to create an app where users can mint my NFTs in a multi-sig transaction manner.

If I can’t use my existing policy key and hash and forced to generate a policy from the buyer’s wallet, then each buyer will receive their mint from a different policy ID, which isn’t how it’s supposed to be. All the NFTs of a project should be minted from a single policy id.

Can you shed some light on how I can do this?

Okay, had another look at https://github.com/dendorferpatrick/nami-wallet-examples/blob/master/Multi-Signature.md. Sorry! I was thinking way too complicated, because I thought the “multi-sig” means that it is a multi-sig policy, but no, it isn’t.

In fact, nearly every minting – certainly the easy examples – is multi-sig in the sense of that example. You always sign with the payment key of the address paying for the mint and with the key you have written into the policy. (You could write policies without keys, but that’s another topic.)

The difference here is that the address paying for the mint is the address of the user, not your own.

What exactly did you do before trying to submit the transaction?

You have to sign it with the policy key. And you have to assemble the transaction body and the two witnesses – the signature by the user and your signature with the policy key. And this assembled transaction has to be submitted.

It’s all in the Nami example. Where did you deviate from it?

That would look like:

{
  "type": "all",
  "scripts":
  [
   {
     "type": "sig",
     "keyHash": "7242f6c68967db0b1aba097ef5a86c558d024902365b4a883b42154f"
   }
  ]
}

But it is gonna be a different policyID.

Since the scripts array only has one element, you can shorten this to:

{
  "type": "sig",
  "keyHash": "7242f6c68967db0b1aba097ef5a86c558d024902365b4a883b42154f"
}

(Same effect, different policy ID.)

Cool!!!