sequenceDiagram
participant node0
participant node1
participant cosigner0
participant cosigner1 as cosigner1 (Leader)
participant cosigner2
Note over node0: Dies
node1->>cosigner1: Request Prevote signature
cosigner1->>cosigner1: Becomes leader
cosigner1->>cosigner1: SetNoncesAndSign
cosigner1->>cosigner2: SetNoncesAndSign
Note over cosigner0: Dead, no participation
Note over cosigner1: Dies before combining signatures
Note over node1: Dies
Note over node0, cosigner2: Recovery Phase
Note over node0, node1: Both restart
node0->>cosigner2: Request nil vote (timeout)
Note over cosigner2: Now Leader
cosigner2->>cosigner0: SetNoncesAndSign
cosigner2->>cosigner1: SetNoncesAndSign
cosigner2->>cosigner2: SetNoncesAndSign
cosigner0->>cosigner2: Signs with nil BlockID
cosigner1--xcosigner2: Error: already signed non-nil BlockID
cosigner2--xcosigner2: Error: already signed non-nil BlockID
node1->>cosigner2: Replay Prevote with existing BlockID
cosigner2--xnode1: Error: already signed vote with nil BlockID
Issue Description
Partial signatures are being used for double sign checks without verification of whether they've been successfully combined into a final signature.
Reproduction Scenario
We use two Tendermint nodes (node0, node1) with a 2/3 threshold Horcrux setup:
At this point:
Recovery Attempt and Failure
sequenceDiagram participant node0 participant node1 participant cosigner0 participant cosigner1 as cosigner1 (Leader) participant cosigner2 Note over node0: Dies node1->>cosigner1: Request Prevote signature cosigner1->>cosigner1: Becomes leader cosigner1->>cosigner1: SetNoncesAndSign cosigner1->>cosigner2: SetNoncesAndSign Note over cosigner0: Dead, no participation Note over cosigner1: Dies before combining signatures Note over node1: Dies Note over node0, cosigner2: Recovery Phase Note over node0, node1: Both restart node0->>cosigner2: Request nil vote (timeout) Note over cosigner2: Now Leader cosigner2->>cosigner0: SetNoncesAndSign cosigner2->>cosigner1: SetNoncesAndSign cosigner2->>cosigner2: SetNoncesAndSign cosigner0->>cosigner2: Signs with nil BlockID cosigner1--xcosigner2: Error: already signed non-nil BlockID cosigner2--xcosigner2: Error: already signed non-nil BlockID node1->>cosigner2: Replay Prevote with existing BlockID cosigner2--xnode1: Error: already signed vote with nil BlockIDRequired Mechanisms