linera-io/linera-protocol · error · ExecutionError

UnauthenticatedClaimOwner

UnauthenticatedClaimOwner

Error message

ExecutionError::UnauthenticatedClaimOwner

What it means

Claim withdraws tokens from an owner's account (possibly on another chain) and forwards them to a recipient. The claimed source owner must be authenticated: either the transaction signer equals source, or the calling application's ID equals source (applications may claim their own funds). This error means neither held, so the claim was rejected before any funds moved or any Withdraw message was created.

Source

Thrown at linera-execution/src/system.rs:769

        ensure!(
            amount > Amount::ZERO,
            ExecutionError::IncorrectTransferAmount
        );
        self.debit(&source, amount).await?;
        self.credit_or_send_message(source, recipient, amount).await
    }

    /// Claims `amount` from `source`'s account on `target_id` and transfers it to `recipient`.
    pub async fn claim(
        &mut self,
        authenticated_owner: Option<AccountOwner>,
        authenticated_application_id: Option<ApplicationId>,
        source: AccountOwner,
        target_id: ChainId,
        recipient: Account,
        amount: Amount,
    ) -> Result<Option<OutgoingMessage>, ExecutionError> {
        ensure!(
            authenticated_owner == Some(source)
                || authenticated_application_id.map(AccountOwner::from) == Some(source),
            ExecutionError::UnauthenticatedClaimOwner
        );
        ensure!(amount > Amount::ZERO, ExecutionError::IncorrectClaimAmount);

        let current_chain_id = self.context().extra().chain_id();
        if target_id == current_chain_id {
            // Handle same-chain claim locally by processing the withdraw operation directly
            self.debit(&source, amount).await?;
            self.credit_or_send_message(source, recipient, amount).await
        } else {
            // Handle cross-chain claim with Withdraw message
            let message = SystemMessage::Withdraw {
                amount,
                owner: source,
                recipient,
            };

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Sign the claim operation with the key of the owner whose funds are being claimed.
  2. If an application performs the claim, set source to the application's own account (its ApplicationId mapped to AccountOwner) rather than a user account.
  3. Verify the owner field in the Claim matches the authenticated signer of the block before submitting.

Example fix

// before: claim submitted with neither signer nor app matching the claimed owner
let op = SystemOperation::Claim { owner, target_id, recipient, amount };
client.submit(op).await?; // UnauthenticatedClaimOwner

// after: sign the block with the claimed owner's key first
let block = client.prepare_block(op).sign(&owner_key).send().await?;
Defensive patterns

Strategy: validation

Validate before calling

// The claimed owner must be authenticated before submitting a Claim
ensure!(
    authenticated_owner == Some(source)
        || application_account == Some(source),
    "claim must be signed by (or originate from) the claimed owner"
);

Type guard

fn is_unauthenticated_claim(e: &ExecutionError) -> bool {
    matches!(e, ExecutionError::UnauthenticatedClaimOwner)
}

Try / catch

match result {
    Err(ExecutionError::UnauthenticatedClaimOwner) => {
        // Signer/app does not match the claimed owner: re-sign as that
        // owner or claim from the application's own account.
    }
    Err(e) => return Err(e.into()),
    Ok(value) => { /* ... */ }
}

Prevention

When it happens

Trigger: Submitting SystemOperation::Claim { owner, target_id, recipient, amount } where owner is neither the block's authenticated signer nor the calling application; an application calling runtime.claim(...) over a user's remote account without that user's signature on the operation.

Common situations: Reclaiming funds from another chain with the wrong wallet or key; applications attempting to sweep user balances without user authorization; mixing up the owner and recipient parameters when constructing the Claim operation.

Understand the failure class

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/b1c8edda363e3b53. Report an issue: GitHub.