Skip to content

.authCommitteeHot() / .resignCommitteeCold() require a Plutus redeemer for native-script (multisig) CC cold credentials #448

Description

@emmanuel-musau

Summary

Following up on #447 / #432 (native-script DRep voters), the same defect still exists for the third governance
entity, constitutional committee members. TxBuilder.authCommitteeHot() and TxBuilder.resignCommitteeCold() reject a native-script (multisig) cold credential, demanding a Plutus redeemer even when the native script is attached via .attachScript(). Native scripts are satisfied by vkey witnesses, not redeemers, so a multisig CC member currently cannot authorize a hot credential or resign through the SDK. (@evolution-sdk/evolution@0.5.11)

On-chain, this is valid: ' cardano-cli` builds both certificates with just the native script and the threshold vkey signatures, no redeemer:

cardano-cli conway governance committee create-hot-key-authorization-certificate \
  --cold-script-file multisig.json \
  --hot-verification-key-file hot.vkey \
  --out-file auth.cert
cardano-cli conway transaction build ... \
  --certificate-file auth.cert --certificate-script-file multisig.json ...

Root cause

In packages/evolution/src/sdk/builders/operations/Governance.ts, both CC programs fail eagerly at operation time, before the attached script can be classified as native vs Plutus:

// createAuthCommitteeHotProgram
const isScriptControlled = params.coldCredential._tag === "ScriptHash"

if (isScriptControlled && !params.redeemer) {
  return yield* Effect.fail(
    new TransactionBuilderError({
      message: "Redeemer required for script-controlled cold credential authorization"
    })
  )
}

(createResignCommitteeColdProgram has the identical check for resignation.)

Reproduction

import {
  Client, preprod, NativeScripts, ScriptHash, KeyHash, Address,
} from "@evolution-sdk/evolution";

// 2-of-3 native (multisig) script as the CC cold credential
const script = NativeScripts.makeScriptNOfK(
  2n,
  keyHashes.map((kh) => NativeScripts.makeScriptPubKey(kh).script), // 3 x 28-byte key
hashes
);
const coldCredential = new ScriptHash.ScriptHash({
  hash: ScriptHash.fromScript(script).hash,
});
const hotCredential = new KeyHash.KeyHash({ hash: hotKeyHash }); // 28-byte key hash

const client = Client.make(preprod).withKoios({ baseUrl:
"https://preprod.koios.rest/api/v1" });
const utxos = await client.getUtxos(Address.fromBech32(fundedAddr));

await client.newTx()
  .authCommitteeHot({ coldCredential, hotCredential })
  .attachScript({ script })                 // native script attached, no redeemer
  .collectFrom({ inputs: utxos })
  .build({ availableUtxos: utxos, changeAddress: Address.fromBech32(fundedAddr) });

Actual: build fails with TxBuilderError: "Redeemer required for script-controlled cold credential authorization". The equivalent
.resignCommitteeCold({ coldCredential, anchor: null }) fails the same way ("... cold credential resignation"). A key-hash cold credential builds fine, isolating the failure to script credentials.

Expected: the certificate builds with the native script in the witness set and the fee sized for the threshold number of vkey witnesses; no
redeemer, no scriptDataHash. A redeemer should be required only for Plutus-script cold credentials.

Suggested fix

Same recipe as #447 :

  1. Remove the two eager redeemer checks from createAuthCommitteeHotProgram and createResignCommitteeColdProgram.
  2. Extend validateCertRedeemers in internal/txBuilder.ts to resolve the cold credential script hash for AuthCommitteeHotCert and
    ResignCommitteeColdCert (alongside the existing DRep cert cases), so the native-vs-Plutus distinction is made at build time once the script is attached or supplied via reference input.

The repro in the issue uses the public client.newTx() API to match #425's style; I verified the identical failure through makeTxBuilder directly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions