IntersectMBO / IntersectMBO/evolution-sdk

Signing: key-hash governance voters are never added to required signers

Open
#479 0 comments 0 reactions 0 assignees View on GitHub
bug external-review
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.