plutus_mint: thread additional_signers into tx body's required_signers

On Babbage+, Plutus's TxInfo.signatories is populated only from the
tx body's required_signers field — vkey witnesses alone don't surface
in the script context. Without this, any Agora script that calls
pauthorizedBy/txSignedBy fails. Stake-bootstrap on preprod erred
because the owner pkh wasn't in signatories despite the wallet
signing the tx.

MCP layer always passes [wallet_pkh]; callers can append cosigner
pkhs for multi-sig flows.
This commit is contained in:
Kayos 2026-05-07 10:33:55 -07:00
parent 6708d448d8
commit a2a1f72a74
2 changed files with 21 additions and 0 deletions

View file

@ -95,6 +95,16 @@ pub struct PlutusMintArgs<'a> {
/// Optional inline datum on the recipient output. Required for
/// any send to a Plutus script address.
pub dest_inline_datum_cbor: Option<&'a [u8]>,
/// PKH hashes to add to the tx body's `required_signers` field.
/// On Babbage+, Plutus's `TxInfo.signatories` is populated ONLY
/// from `required_signers` — VKey witnesses alone are not enough
/// for any `pauthorizedBy` / `txSignedBy` check inside a script.
/// MCP layer always passes this wallet's payment-key pkh; pass
/// extra entries for cosigners. Empty slice = scripts that don't
/// check signatories (e.g. Agora's GST policy).
/// Caught 2026-05-07 on Agora's stake-policy bootstrap on preprod
/// — script erred because owner pkh was absent from signatories.
pub additional_signers: &'a [Hash<28>],
}
fn parse_address(bech32: &str) -> Result<PallasAddress, WalletError> {
@ -586,6 +596,10 @@ fn prepare_plutus_mint(
.fee(fee)
.network_id(network_id);
for pkh in args.additional_signers {
staging = staging.disclosed_signer(*pkh);
}
// Plutus V1/V2/V3 each need their cost-model wired via
// language_view so pallas computes script_data_hash on the tx
// body. Without it, chain rejects with PPViewHashesDontMatch.
@ -785,6 +799,7 @@ mod tests {
dest_lovelace: 3_000_000,
dest_extra_assets: &[],
dest_inline_datum_cbor: Some(&UNIT_DATUM_CBOR),
additional_signers: &[],
};
let err = build_signed_plutus_mint(
&payment,
@ -816,6 +831,7 @@ mod tests {
dest_lovelace: 3_000_000,
dest_extra_assets: &[],
dest_inline_datum_cbor: Some(&UNIT_DATUM_CBOR),
additional_signers: &[],
};
let err = build_signed_plutus_mint(
&payment,
@ -857,6 +873,7 @@ mod tests {
dest_lovelace: 3_000_000,
dest_extra_assets: &[],
dest_inline_datum_cbor: Some(&UNIT_DATUM_CBOR),
additional_signers: &[],
};
let err = build_signed_plutus_mint(
&payment,
@ -895,6 +912,7 @@ mod tests {
dest_lovelace: 3_000_000,
dest_extra_assets: &[],
dest_inline_datum_cbor: Some(&UNIT_DATUM_CBOR),
additional_signers: &[],
};
// V3 cost model is required for staging language_view; test
// ProtocolParams::default() should provide one for preprod.

View file

@ -1654,6 +1654,8 @@ impl WalletService {
}
};
let wallet_pkh = self.inner.payment_key.public_key_hash();
let signers = [wallet_pkh];
let core_args = CorePlutusMintArgs {
required_inputs: &required,
policy_cbor: &policy_cbor,
@ -1665,6 +1667,7 @@ impl WalletService {
dest_lovelace,
dest_extra_assets: &core_extras,
dest_inline_datum_cbor: datum_bytes.as_deref(),
additional_signers: &signers,
};
let unsigned = build_unsigned_plutus_mint(