Skip to content

Responders to CollectEndorsementsView may expose signing oracle #1232

Closed

Description

@ale-linux

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.

TokenService().SigService().GetSigner(signingIdentity)

a node transfering a token may be tricked into approving the transfer of a different token.

Activity

  1. added this to the Q4/25 milestone on Oct 24, 2025
  2. self-assigned this
    on Nov 12, 2025
  3. modified the milestones: Q4/25, Q1/26 on Feb 9, 2026
  4. AkramBitar commented on Feb 12, 2026

    @AkramBitar
    Contributor

    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:

    // 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),
        )
    }
    

    @ale-linux could you please verify the above?

  5. adecaro commented on Feb 17, 2026

    @adecaro
    Contributor

    @AkramBitar , @HayimShaul , but I think what Bob found does not apply.

    In

    		signer, err := sigService.GetSigner(context.Context(), signerIdentity)

    signerIdentity comes from the transaction the EndorserView has been initialized with.

    and here

    		// check for the expected identity
    		if !signatureRequest.Signer.Equal(signerIdentity) {
    			return errors.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()
    	if err != nil {
    		return errors.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?

  6. AkramBitar commented on Feb 18, 2026

    @AkramBitar
    Contributor

    @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.

  7. added a commit that references this issue on Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions