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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions