This page describes how the Multi-Party Compute (MPC) Policy Engine evaluates transaction policies written in the Cedar policy language, handles cross-chain scenarios, and resolves optional or omitted metadata fields.
Core Evaluation Principles
The Policy Engine uses the Cedar Policy Language to authorize cryptographic signing operations. The engine evaluates policies deterministically using three fundamental axioms:
- Default Deny: All operations are implicitly denied. For an operation to be permitted, it must match at least one active
permitpolicy. If no matchingpermitpolicy is found, the final decision isDENY. - Explicit Forbid: A
forbidpolicy acts as an absolute override. If an operation matches any activeforbidpolicy, the decision is immediately resolved toDENY, regardless of any matchingpermitpolicies that would otherwise allow the operation. - Missing Fields Just Mean "No Match" (Skip): If a rule looks for a field that is not present in the transaction payload (such as an optional field that was left out), Cedar does not crash. It treats that specific rule as a "no match" (evaluates to false) and moves on to the next rule.
Logical Evaluation Matrix
The following table demonstrates how the Policy Engine evaluates rule combinations.
For all scenarios, assume:
- The transaction caller (represented as the principal in Cedar policies) is Alice (
User::"alice"). In Multi-Party Compute, a principal represents the persona or identity requesting authorization for a cryptographic signing operation (such as a human signer, an automated service client, or a backend system account). - The transaction is transferring exactly 1 ETH (represented as 1,000,000,000 scaled nanocoin units in
context.transaction.spend_amount_scaled).
| Scenario | Active Policies | Result | Reason |
|---|---|---|---|
| Scenario 1: Empty state | No active policies. | DENY |
By default, all operations are blocked unless explicitly allowed by a matching permit policy. |
| Scenario 2: Single permit match |
permit( principal == User::"alice", action, resource ) when { context.transaction.spend_amount_scaled < 10000000000 }; Allows up to 10 ETH |
PERMIT |
The transaction parameters satisfy the policy condition. Since there are no conflicting forbid policies, the request is allowed. |
| Scenario 3: Single permit mismatch |
permit( principal == User::"alice", action, resource ) when { context.transaction.spend_amount_scaled < 500000000 }; Allows up to 0.5 ETH |
DENY |
The transfer amount (1 ETH) exceeds the policy limit. Since no matching permit rules are met, the default deny blocks the transaction. |
| Scenario 4: Multiple permit policies |
Policy A: permit( principal == User::"alice", action, resource ) when { context.transaction.spend_amount_scaled < 500000000 }; Allows up to 0.5 ETH Policy B: permit( principal == User::"alice", action, resource ) when { context.transaction.spend_amount_scaled < 5000000000 }; Allows up to 5 ETH |
PERMIT |
Although Policy A does not match, Policy B matches. You only need one matching permit policy to allow the transaction. |
| Scenario 5: Permit and forbid conflict |
Policy A (permit): permit( principal == User::"alice", action, resource ) when { context.transaction.spend_amount_scaled < 5000000000 }; Allows up to 5 ETH Policy B (forbid): forbid( principal == User::"alice", action, resource ) when { context.transaction.recipient == "0xBlockedAddress" }; Assumes destination is |
DENY |
The transfer is sent to 0xBlockedAddress. Even though Policy A permits the transaction amount, the destination matches the criteria for Policy B. The explicit forbid rule overrides Policy A and blocks the transaction. |
| Scenario 6: Unmatched forbid policy |
Same policies as in the preceding scenario |
PERMIT |
The transfer is sent to a safe address (e.g., 0xSafeAddress). Policy A matches successfully, and Policy B does not match because the destination is not 0xBlockedAddress. In the absence of a matching forbid rule, the permit rule is applied. |
Multi-chain and optional-field scenarios
While the preceding matrix demonstrates the core logical precedence of permitted and forbidden rules, the following scenarios illustrate how the Policy Engine evaluates rules in a multi-chain environment: specifically when executing against optional fields or different blockchain network schemas.
Note: Zero-Value Data Normalization To prevent unexpected evaluation errors and ensure policy predictability, the Policy Engine's parsing layer normalizes unpopulated string and byte fields in the evaluation context to their type-specific zero-values (such as an empty string
""or an empty byte array) instead of omitting them.This behavior lets you draft explicit policies targeting unpopulated fields without risking Cedar evaluation errors. However, accessing fields that are completely undefined in a blockchain's schema (such as EVM-only
gas_limiton a Bitcoin transaction, or Bitcoin-onlyoutputson an EVM transaction) will result in a Cedar evaluation error on that specific policy, causing it to evaluate tofalse(no match) and safely fail closed.
Scenario 7: Cross-chain policy evaluation and unmapped schema attributes
Context: A caller submits a Bitcoin transaction. However, the active policy set contains an Ethereum-specific policy:
permit( principal == User::"alice", action, resource ) when { context.transaction.schema == "ethereum.transaction.v2" && context.transaction.gas_limit < 100000 };Result:
DENYReason: Universal intent attributes (such as
spend_amount_scaled,recipient, andmax_fee_atomic) evaluate consistently across both Ethereum and Bitcoin transactions. When a policy references chain-specific attributes that don't exist in the submitted transaction's schema (such asgas_limiton a Bitcoin transaction), Cedar treats the unmatched attribute as an evaluation failure, resolving that specific policy tofalse(no match) without halting the engine. Since no other activepermitpolicy matches the Bitcoin transaction, the operation is denied by default.
Scenario 8: Missing optional field in a permit policy
Context: A caller submits a standard ETH transfer (which does not use smart contracts). The active policy set contains a rule checking contract interactions:
permit( principal == User::"alice", action, resource ) when { context.transaction.asset_contract_address == "0x1111111111111111111111111111111111111111" };Result:
DENYReason: For a standard ETH transfer, the optional
asset_contract_addressis unpopulated. The parser normalizes this to the zero-value empty string""in the evaluation context. Evaluating the comparison"" == "0x1111..."returnsfalse(no match) rather than raising an error. Since no other policy permits the transfer, the operation is denied by default.
Scenario 9: Missing optional field in a forbid policy
Context: A caller submits a standard ETH transfer of 10 ETH. The policy set consists of:
Policy A (permit):
permit( principal == User::"alice", action, resource ) when { context.transaction.spend_amount_scaled <= 100000000000 };Policy B (forbid):
forbid( principal == User::"alice", action, resource ) when { context.transaction.asset_contract_address == "0xBannedContract" };
Result:
PERMITReason: Policy A matches successfully because 10 ETH is under the limit. Policy B checks if the contract address matches a banned address. Due to zero-value normalization, the unpopulated contract address resolves to
"". Evaluating the comparison"" == "0xBannedContract"returnsfalse. Since the forbid policy does not match and the permit policy does, the transaction is permitted.
Scenario 10: Explicit checking of empty or missing fields
Context: A caller submits a standard ETH transfer (which is not sent to a smart contract). The policy set consists of:
Policy A (permit):
permit( principal == User::"alice", action, resource ) when { context.transaction.spend_amount_scaled <= 1000000000000 };Policy B (forbid):
forbid( principal == User::"alice", action, resource ) when { context.transaction.asset_contract_address == "" };
Result:
DENYReason: Due to parser normalization, the missing
asset_contract_addressis presented in the evaluation context as an empty string"". Evaluating Policy B resolves the condition totrue("" == ""). Theforbidpolicy matches, and the transaction is denied.
Pre-Evaluation Validation (Fail-Fast Gate)
The Policy Engine enforces strict data validation. Before a payload is passed to Cedar for policy evaluation, the underlying parser validates transaction structure, RLP encoding, and cryptographic integrity.
Scenario 11: Malformed and invalid transaction payloads
- Context: A client submits a malformed byte sequence, a payload with invalid structure, or a transaction missing mandatory cryptographic fields (e.g. an EVM transaction with an invalid RLP structure or missing gas parameters).
- Result:
gRPC InvalidArgument - Reason: The internal transaction parser detects the invalid input at the API gateway layer. The operation fails closed immediately, returning a
gRPC InvalidArgumenterror to the client, and evaluation by the Cedar Policy Engine is bypassed completely.