You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Responders to CollectEndorsementsView may expose signing oracle #1232
The CollectEndorsementsView is responsible for the retrieval of signatures from 3rd parties. This for example happens when the node requests signatures over issues, transfers and endorsement acks. The responders to these views retrieve the message to be signed from the network session and sign it. Lack of verification of what message is signed may cause the signer to sign an arbitrary message.
Note that by virtue of the fact that the signer is dynamically determined, e.g.
Attack Flow:
Step 1: Alice creates Transaction TX_transfer_A
- Transfers Token A (tx1-0) from Bob to Alice
- Bob receives this transaction and stores as s.tx
Step 2: Alice sends SignatureRequest with DIFFERENT identity
SignatureRequest {
TX: ,
Signer: Bob_Identity_For_Token_B ← Different identity!
}
Step 3: Bob's code extracts required signers from LOCAL s.tx
requiredSigners = extractRequiredSigners(s.tx.Request())
→ Returns [Bob_Identity_For_Token_A]
Step 4: Bob receives SignatureRequest with Bob_Identity_For_Token_B
Step 5: Identity check PASSES if both identities belong to Bob:
if !signatureRequest.Signer.Equal(signerIdentity) {
// This might pass if attacker manipulates the flow
}
Step 6: CRITICAL: GetSigner uses the RECEIVED identity
signer = GetSigner(context, signatureRequest.Signer)
// ^^^^^^^^^^^^^^^^^^^^^^^^
// Uses Bob_Identity_For_Token_B!
Step 7: Signs with WRONG identity but LOCAL transaction
sigma = signer.Sign(tokenRequestToSign)
// ^^^^^^^^^^^^^^^^^^
// This is from s.tx (Token A transfer)
Fix Summary
Vulnerability:
Responder signs local transaction without verifying it matches the signature request from the network, enabling blind signing attacks.
The Fix:
Add transaction content verification in token/services/ttx/endorse.go at line ~135:
// Get local transaction bytes
localTxBytes, err := s.tx.Bytes()
if err != nil {
return errors.Wrap(err, "failed to marshal local transaction")
}
// VERIFY: Transaction in signature request matches local transaction
if !bytes.Equal(signatureRequest.TX, localTxBytes) {
return errors.Errorf(
"transaction mismatch: refusing to sign. "+
"Requested TX hash: %x, Local TX hash: %x",
hash(signatureRequest.TX),
hash(localTxBytes),
)
}
signerIdentity comes from the transaction the EndorserView has been initialized with.
and here
// check for the expected identityif!signatureRequest.Signer.Equal(signerIdentity) {
returnerrors.Wrapf(
ErrSignerIdentityMismatch,
"signature request's signer does not match the expected signer, [%s] != [%s], required signatures [%d]",
signatureRequest.Signer,
signerIdentity,
len(requiredSigners),
)
}
we have the check over the identities (byte by byte).
In
tokenRequestToSign, err:=s.tx.TokenRequest.MarshalToSign()
iferr!=nil {
returnerrors.Wrap(err, "failed to marshal token request to sign")
}
This is what is signed.
So, I think, the Issue is outdated. The problem was already fixed, no?
@adecaro you are right, because the issue was left open we assumed that the issue still exist. We verified and indeed the solution found its way to the code. Thanks.
Activity
AkramBitar commentedon Feb 12, 2026
Based on Bob:
Attack Flow:
Step 1: Alice creates Transaction TX_transfer_A
- Transfers Token A (tx1-0) from Bob to Alice
- Bob receives this transaction and stores as s.tx
Step 2: Alice sends SignatureRequest with DIFFERENT identity
SignatureRequest {
TX: ,
Signer: Bob_Identity_For_Token_B ← Different identity!
}
Step 3: Bob's code extracts required signers from LOCAL s.tx
requiredSigners = extractRequiredSigners(s.tx.Request())
→ Returns [Bob_Identity_For_Token_A]
Step 4: Bob receives SignatureRequest with Bob_Identity_For_Token_B
Step 5: Identity check PASSES if both identities belong to Bob:
if !signatureRequest.Signer.Equal(signerIdentity) {
// This might pass if attacker manipulates the flow
}
Step 6: CRITICAL: GetSigner uses the RECEIVED identity
signer = GetSigner(context, signatureRequest.Signer)
// ^^^^^^^^^^^^^^^^^^^^^^^^
// Uses Bob_Identity_For_Token_B!
Step 7: Signs with WRONG identity but LOCAL transaction
sigma = signer.Sign(tokenRequestToSign)
// ^^^^^^^^^^^^^^^^^^
// This is from s.tx (Token A transfer)
Fix Summary
Vulnerability:
Responder signs local transaction without verifying it matches the signature request from the network, enabling blind signing attacks.
The Fix:
Add transaction content verification in token/services/ttx/endorse.go at line ~135:
@ale-linux could you please verify the above?
adecaro commentedon Feb 17, 2026
@AkramBitar , @HayimShaul , but I think what Bob found does not apply.
In
signerIdentity comes from the transaction the EndorserView has been initialized with.
and here
we have the check over the identities (byte by byte).
In
This is what is signed.
So, I think, the Issue is outdated. The problem was already fixed, no?
AkramBitar commentedon Feb 18, 2026
@adecaro you are right, because the issue was left open we assumed that the issue still exist. We verified and indeed the solution found its way to the code. Thanks.
FSC #1232
FSC #1232
FSC #1232
FSC #1232 (#1514)
FSC LFDT-Panurus#1232 (LFDT-Panurus#1514)