Summary
computeRequiredKeyHashesSync derives the signing keys from inputs, withdrawals, and certificates, but never inspects votingProcedures (body key 19) or proposalProcedures (key 20). The tx builder's Vote operation likewise only tracks redeemers for script voters and never adds a key-hash voter to requiredSigners. So a governance vote cast by a key-hash DRep, constitutional-committee member, or stake pool is not signed by the wallet, and the node rejects the under-signed transaction. Fail closed: no fund loss and no unauthorized action (a vkey witness covers the whole body, so nothing is signed that the signer didn't already authorize). The SDK can build a vote it cannot sign.
Affected
packages/evolution/src/sdk/client/internal/Signing.ts
- computeRequiredKeyHashesSync (L47-184): checks requiredSigners, nativeScripts, inputs, collateralInputs, withdrawals, certificates; no votingProcedures / proposalProcedures branch
packages/evolution/src/sdk/builders/operations/Vote.ts
- createVoteProgram: only tracks redeemers for script voters (isScriptVoter); key-hash voters are never added to state.requiredSigners
Fix
In computeRequiredKeyHashesSync, when tx.body.votingProcedures is present, iterate the voters and add the wallet credential on a match, mirroring the existing certificate logic:
- DRepVoter with KeyHash drep -> match against stakeKhHex (same as RegDrepCert handling)
- ConstitutionalCommitteeVoter with KeyHash credential -> match against the wallet's committee credential (same as AuthCommitteeHotCert handling)
- StakePoolVoter -> match poolKeyHash against paymentKhHex (same as PoolRegistration/PoolRetirement handling)
Apply the equivalent for proposalProcedures.
Regression test
- given: a transaction whose only wallet-relevant field is a votingProcedures entry for a key-hash DRep voter equal to the wallet's stake credential
- before fix: signTx returns an empty witness set (voter key not added)
- after fix: signTx adds the stake-key vkey witness
Must FAIL on main today and PASS after the fix.
Reference
Reported informally (governance signing gap). The same report also claimed this enables "unauthorized voting"; that part does not hold, since a vkey witness signs the entire transaction body, so a vote riding along in a signed transaction is the ordinary review-before-signing consideration, not a separate escalation.
Summary
computeRequiredKeyHashesSyncderives the signing keys from inputs, withdrawals, and certificates, but never inspectsvotingProcedures(body key 19) orproposalProcedures(key 20). The tx builder's Vote operation likewise only tracks redeemers for script voters and never adds a key-hash voter torequiredSigners. So a governance vote cast by a key-hash DRep, constitutional-committee member, or stake pool is not signed by the wallet, and the node rejects the under-signed transaction. Fail closed: no fund loss and no unauthorized action (a vkey witness covers the whole body, so nothing is signed that the signer didn't already authorize). The SDK can build a vote it cannot sign.Affected
packages/evolution/src/sdk/client/internal/Signing.ts
packages/evolution/src/sdk/builders/operations/Vote.ts
Fix
In
computeRequiredKeyHashesSync, whentx.body.votingProceduresis present, iterate the voters and add the wallet credential on a match, mirroring the existing certificate logic:Apply the equivalent for proposalProcedures.
Regression test
Must FAIL on main today and PASS after the fix.
Reference
Reported informally (governance signing gap). The same report also claimed this enables "unauthorized voting"; that part does not hold, since a vkey witness signs the entire transaction body, so a vote riding along in a signed transaction is the ordinary review-before-signing consideration, not a separate escalation.