IntersectMBO / IntersectMBO/evolution-sdk
Signing: key-hash governance voters are never added to required signers
- Dominant language
- TypeScript
- Stars
- 22
- Forks
- 30
- Avg merge
- 5h 29m
- Merged PRs (30d)
- 12
Description
## 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.
Contributor guide
Research direction
Start in packages/evolution/src/sdk/client/internal/Signing.ts at computeRequiredKeyHashesSync and compare its certificate handling with the voting and proposal procedure fields. Then inspect packages/evolution/src/sdk/builders/operations/Vote.ts and run the signing regression scenario described in the issue. Done means a key-hash DRep vote causes signTx to include the wallet's stake-key vkey witness, while the test fails before and passes after the fix.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- blockchain
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100