Evaluación de la política de transacciones

Antes de que la computación multipartita firme una transacción, la evalúa en función de las políticas de transacciones de tu instancia. En este documento, se describe qué políticas se evalúan, cómo se mueve una política a lo largo de su ciclo de vida, qué puede verificar una política y por qué se rechaza una transacción.

Cómo se evalúa una transacción

Una política de transacción es una sola declaración permit o forbid de Cedar. Cuando un firmante llama a CreateTransaction, la instancia decodifica la transacción sin firmar en Transaction.payload con el analizador para Transaction.schema y evalúa cada política aplicada en relación con la transacción antes de que se use cualquier recurso compartido de claves. Una política se aplica cuando su estado es ACTIVE o PENDING_DISABLED. Para obtener más información, consulta Ciclo de vida de las políticas.

La decisión sigue tres reglas:

  1. Rechazar de forma predeterminada. Una transacción se permite solo si coincide con al menos una política de permit. Sin políticas aplicadas, se rechazan todas las transacciones.
  2. La prohibición gana. Si coincide alguna política forbid, se rechaza la transacción, incluso si también coinciden las políticas permit.
  3. Una política que no se evalúa no coincide. Por ejemplo, una política que lee un atributo de contexto opcional que la transacción no tiene no se puede evaluar. La instancia registra el error, omite esa política y toma una decisión con las políticas aplicadas restantes. Esto también se aplica a las políticas de forbid, por lo que debes escribir políticas de forbid que no puedan fallar en un atributo faltante. Para ver un ejemplo, consulta la situación 8 y la situación 9.

Una transacción que contiene varias transferencias, como una transacción de Solana con varias instrucciones de transferencia, se evalúa una vez por cada transferencia. La transacción se permite solo si se permiten todas las transferencias. Si se rechaza alguna transferencia o no se puede decodificar, se rechaza toda la transacción.

Cada evaluación, permiso o denegación produce un registro de evaluación que se devuelve en la transacción en Transaction.policyEvaluationRecord.

La decisión controla si la instancia puede firmar la transacción sin esperar a más firmantes:

  • PERMIT: Si la solicitud CreateTransaction ya tiene suficientes autorizaciones de firmantes para cumplir con la política de claves del llavero, la transacción pasa a SIGNING de inmediato. De lo contrario, se crea en PENDING_AUTHORIZATION y espera a que haya más firmantes.
  • DENY: La instancia no firma la transacción automáticamente. La transacción se crea en PENDING_AUTHORIZATION con policyEvaluationRecord.decision establecido en DENY. Los firmantes pueden revisar el registro de evaluación y rechazar la transacción (RecordSignerDecision con DENY) o autorizarla de todos modos (AUTHORIZE). La transacción se firma solo si suficientes firmantes la autorizan para cumplir con la política de claves.

Una política de transacciones es una protección para la firma automática, no un bloqueo para los firmantes: los firmantes pueden autorizar una transacción que las políticas rechazan. El PolicyEvaluationRecord original permanece adjunto a la transacción para auditoría.

Ciclo de vida de la política

No se pueden cambiar las políticas de transacciones. TransactionPolicy no tiene un método de actualización, y su rule es inmutable. Para cambiar una regla, inhabilita la política con DisableTransactionPolicy y crea una nueva con CreateTransactionPolicy.

La activación o inhabilitación de una política requiere la aprobación de un quórum de autores de políticas (usuarios con un rol activo de POLICY_AUTHOR en User.roleStatuses). Cada autor de políticas firma un TransactionPolicySigningPayload que nombra la política (o, para CreateTransactionPolicy, la instancia), la acción (ACTIVATE o DISABLE) y rule.cedarDocument. La solicitud que propone un cambio cuenta como la primera aprobación. Otros autores de políticas aprueban con ApproveTransactionPolicy. La cantidad de aprobaciones necesarias se encuentra en activationInfo.requiredApprovals y deactivationInfo.requiredApprovals. Si la instancia necesita solo una aprobación, el cambio se aplica de inmediato.

En la siguiente tabla, se describe cada estado de la política y si se aplican las políticas en ese estado:

Estado Significado ¿Se aplica?
PENDING_ACTIVE Se propuso la política y se espera la aprobación de los autores de políticas. No
ACTIVE La política tiene activationInfo.requiredApprovals aprobaciones. Sí
PENDING_DISABLED Un autor de políticas solicitó inhabilitar la política, y la solicitud está esperando aprobaciones. Sí, hasta que la solicitud tenga deactivationInfo.requiredApprovals aprobaciones
DISABLED La política se inhabilitó o se retiró antes de que se activara. Las políticas inhabilitadas se conservan para la auditoría. No

Una política pasa por estos estados en una dirección:

  • De PENDING_ACTIVE a ACTIVE: La activación tiene suficientes aprobaciones.
  • PENDING_ACTIVE a DISABLED: Un autor de políticas retira la política con DisableTransactionPolicy antes de que se active.
  • ACTIVE a PENDING_DISABLED: Un autor de políticas llama a DisableTransactionPolicy.
  • PENDING_DISABLED a DISABLED: la solicitud de inhabilitación tiene suficientes aprobaciones.

Una política ingresa a cada estado pendiente como máximo una vez. No se puede volver a habilitar una política inhabilitada. En su lugar, crea una nueva.

Para saber quién puede llamar a estos métodos, consulta Roles de usuario.

Versión del conjunto de políticas

El conjunto de políticas es el conjunto de políticas que se aplican (ACTIVE o PENDING_DISABLED) en un momento determinado. Cada vez que se completa una activación o una inhabilitación, la instancia le asigna a esa transición el siguiente número en un contador para la instancia y lo registra en activationInfo.transitionSequence o deactivationInfo.transitionSequence. Ese número es la versión del conjunto de políticas que se produce con la transición.

En la siguiente tabla, se muestra cómo cambia la versión del conjunto de políticas en una secuencia de eventos:

Evento Secuencia de transición Políticas aplicadas
Se activa la política A. A: activationInfo.transitionSequence = 1 A
Se activa la política B. B: activationInfo.transitionSequence = 2 A, B
Se propone la política C (PENDING_ACTIVE) y, luego, se retira. Ninguno A, B
Se aprueba una solicitud de inhabilitación de la política A. A: deactivationInfo.transitionSequence = 3 B

Retirar una política PENDING_ACTIVE nunca cambia el conjunto de políticas aplicadas, por lo que no obtiene una secuencia de transición y no cambia la versión del conjunto de políticas. Mientras una transición espera aprobaciones, no se establece su transitionSequence.

Cada evaluación lee las políticas, las KeyLists y las claves a partir de la createTime de la transacción, y registra la secuencia de transición completada más alta en ese momento en policyEvaluationRecord.policySetVersion. Si dos evaluaciones tienen la misma versión del conjunto de políticas, usaron las mismas políticas.

La solicitud de Cedar

Una solicitud de Cedar tiene un principal, una acción, un recurso y un contexto. Por ejemplo:

permit (
  principal == User::"alice@example.com",
  action == Action::"buy_transaction",
  resource == Resource::"transaction_management"
)
when {
  context.principal_keyring == "institutional-treasury" &&
  context has fiat_amount && context.fiat_amount <= 10000
};

Cómo se asigna un usuario al principal de Cedar

La principal es el iniciador de la transacción: el usuario cuyo principal coincide con la identidad autenticada del emisor de CreateTransaction. El usuario debe tener un rol de SIGNER activo. En Cedar, la principal es una entidad User cuyo ID es el campo principal del usuario, por ejemplo, User::"alice@example.com".

La instancia no pasa ningún dato de entidad a Cedar. Una entidad User no tiene atributos ni elementos superiores, por lo que:

  • principal == User::"alice@example.com" coincide con las transacciones que inicia Alicia.
  • principal in Team::"treasury" nunca coincide. Para limitar quién puede firmar con una clave, usa la política de claves del llavero de claves. Para definir el alcance de una regla a un grupo de claves, consulta context.principal_keyring.
  • No se puede evaluar una regla que lee un atributo de principal, como principal.team.

No se puede cambiar el principal de un usuario, por lo que una regla que nombra a una principal sigue haciendo referencia a la misma identidad.

Acción y recurso

  • action: Es el valor de Transaction.action en la solicitud CreateTransaction, con formato de entidad Action de Cedar. CreateTransaction acepta las siguientes acciones:

    Acción Se usa para
    Action::"buy_transaction" Es una transacción en la que se adquiere un activo.
    Action::"sell_transaction" Es una transacción que dispone de un recurso.
    Action::"internal_transfer_transaction" Es una transferencia de un activo, como un pago o un barrido entre claves.

    El iniciador elige la acción; la computación multipartita no la infiere de la carga útil. Una regla puede coincidir con una acción (action == Action::"buy_transaction"), una lista de acciones (action in [Action::"buy_transaction", Action::"sell_transaction"]) o dejar action sin restricciones para que coincida con cada acción.

  • resource: siempre Resource::"transaction_management" para las evaluaciones de transacciones. Una regla puede verificar resource == Resource::"transaction_management" o dejar resource sin restricciones.

Atributos de contexto

El contexto contiene los atributos que verifican las políticas. Provienen de dos lugares:

  • La carga útil de la transacción y la clave de firma. La instancia decodifica Transaction.payload y agrega los IDs de la clave de firma y su llavero de claves. Estos atributos son principal_keyring, principal_key, blockchain, schema, secondary_key, amount_token_scaled, token_symbol, contract_address, chain_id, max_fee_atomic, payload y time_stamp. Para ver la tabla completa y el comportamiento de cada cadena de bloques, consulta Escribe reglas de política de transacciones.
  • El campo metadata de la transacción, que el iniciador establece en CreateTransaction.

En la siguiente tabla, se describen los atributos de metadatos:

Campo Transaction.metadata Atributo de Cedar Tipo de cedro Valor
fiatAmount context.fiat_amount Long Es el valor de la transacción en dólares estadounidenses enteros (google.type.Money). currencyCode debe ser USD, units debe ser cero o más, y nanos debe ser 0.
validity context.validity Long Es el tiempo durante el que la transacción es válida (google.protobuf.Duration), en segundos enteros. Debe ser mayor que 0 segundos, sin fracciones de segundo.
principalValue1 context.principal_value1 String Cualquier valor que definas, de hasta 256 caracteres, por ejemplo, una mesa de operaciones o un centro de costos.
principalValue2 context.principal_value2 String Cualquier valor que definas, de hasta 256 caracteres, por ejemplo, un ticket interno o un ID de contraparte.

Se aplican las siguientes reglas a los metadatos:

  • Los campos no configurados no están en el contexto. Si una transacción no tiene fiatAmount, context.fiat_amount no existe y una política que la lee no se evalúa. Primero, verifica el atributo con has, como en context has fiat_amount && context.fiat_amount <= 10000.
  • Los metadatos no válidos hacen que la solicitud falle. Si un campo incumple las reglas de la tabla anterior, por ejemplo, un fiatAmount en EUR o con un nanos distinto de cero, un validity de 0 segundos o un principalValue1 de más de 256 caracteres, CreateTransaction falla con INVALID_ARGUMENT y no se evalúa ninguna política.
  • La instancia no verifica los metadatos con la transacción. fiatAmount es el valor que el iniciador dice que tiene la transacción. Otros firmantes ven los metadatos en Transaction.metadata y policyEvaluationRecord.evaluationContext antes de autorizarlos. Para limitar los importes en cadena de forma independiente de los metadatos, verifica context.amount_token_scaled y context.contract_address.
  • Los metadatos no se pueden cambiar después de que se crea la transacción.

KeyRings

Un KeyRing agrupa claves. El contexto de cada transacción tiene principal_keyring y principal_key, por lo que una regla se puede aplicar a cada clave de un KeyRing, incluidas las claves creadas más adelante, y un KeyRing puede ser una entrada en una KeyList. Para obtener más información y un ejemplo de regla, consulta KeyRings.

KeyLists

Una KeyList es una lista con nombre de direcciones de cadena de bloques a las que pueden hacer referencia las políticas, por ejemplo, como una lista de entidades permitidas de destinos. Cada entrada es una de las siguientes:

  • Una entrada manual (manualEntry): una dirección que la instancia no administra, con campos source y reason opcionales.
  • Una clave en la instancia (key): coincide con las direcciones de la clave.
  • Un KeyRing en la instancia (keyRing): coincide con las direcciones de cada clave activa en el KeyRing, incluidas las claves creadas después de que se agregó la entrada.

Las KeyLists pueden cambiar después de que se aprueba una política. Cuando agregas o quitas una entrada con AddKeyListEntries o RemoveKeyListEntries, cada política que hace referencia a la KeyList usa las entradas nuevas de la siguiente evaluación, sin aprobaciones nuevas del autor de la política. No se puede borrar un KeyList con DeleteKeyList mientras una política PENDING_ACTIVE, ACTIVE o PENDING_DISABLED haga referencia a él.

KeyLists en el contexto de Cedar

En el momento de la evaluación, la instancia expande cada KeyList en un Set plano de direcciones de cadena de bloques String: las entradas manuales y las direcciones derivadas de cada entrada de clave y KeyRing. Cada conjunto se pasa a Cedar como context.key_lists["<var>KEY_LIST_ID</var>"], donde KEY_LIST_ID es el ID de {keyList} en el nombre del recurso de KeyList, por ejemplo, trusted-destinations para projects/my-project/locations/us-central1/instances/my-instance/keyLists/trusted-destinations.

Una regla prueba la membresía con el operador contains de Cedar en la dirección de destino decodificada, context.secondary_key. Por ejemplo, para permitir transferencias solo a direcciones en la KeyList trusted-destinations, haz lo siguiente:

// Assuming you created a KeyList with ID "trusted-destinations":
permit (principal, action, resource)
when { context.key_lists["trusted-destinations"].contains(context.secondary_key) };

Para rechazar las transferencias a cualquier dirección de la KeyList blocked-addresses, haz lo siguiente:

// Assuming you created a KeyList with ID "blocked-addresses":
forbid (principal, action, resource)
when { context.key_lists["blocked-addresses"].contains(context.secondary_key) };

Ten en cuenta lo siguiente:

  • Usa la sintaxis de corchetes, no la de puntos, para el ID de KeyList. La sintaxis de puntos de Cedar (context.key_lists.trusted-destinations) no se analiza cuando el ID contiene un guion, que Cedar lee como resta, o comienza con un dígito, como puede hacerlo un UUID generado por el servidor. La sintaxis de corchetes, context.key_lists["<var>KEY_LIST_ID</var>"], funciona para todos los IDs.
  • key_lists siempre está presente, y una regla puede leer una KeyList en ella directamente. CreateTransactionPolicy verifica que exista cada KeyList a la que hace referencia la regla, y DeleteKeyList se niega a borrar una KeyList mientras una política haga referencia a ella. Usa context.key_lists has "<var>KEY_LIST_ID</var>" solo cuando una regla haga referencia a una KeyList que tal vez aún no exista.
  • Las direcciones se comparan como cadenas. secondary_key usa el formato que Escribe reglas de política de transacciones describe para cada cadena de bloques, por ejemplo, el formato de suma de comprobación EIP-55 con mayúsculas y minúsculas para Ethereum. Agrega entradas manuales en el mismo formato.
  • El secondary_key de una transferencia de tokens de Solana es la cuenta de tokens de destino, no la billetera del propietario. Para permitir una billetera, agrega su cuenta de token para cada acuñación que permitas.

Cada evaluación resuelve la membresía de KeyList a partir de la createTime de la transacción, al mismo tiempo que las políticas, y registra el resultado en policyEvaluationRecord.keyListMemberships: la KeyList, la dirección probada, si era miembro y la entrada que coincidió.

Debido a que una KeyList cambia lo que permite una política, limita quién puede modificarla. Los métodos KeyList necesitan roles/mpc.admin. Para obtener más información, consulta Permisos de IAM.

Motivos de rechazo

Cuando policyEvaluationRecord.decision es DENY, policyEvaluationRecord.denyReason indica el motivo. En la siguiente tabla, se describe cada motivo de rechazo:

Motivo del rechazo Cuándo Determina las políticas
FORBID_RULE_MATCHED Se aplicó al menos una política de forbid que coincidía. Son las políticas de forbid que coincidieron.
NO_PERMIT_RULE_MATCHED La carga útil se decodificó, no coincidió ninguna política forbid, no se produjo un error en la evaluación de ninguna política y no coincidió ninguna política permit. Ninguno
EVALUATION_ERROR No coincidió ninguna política permit ni forbid, y al menos una política no se pudo evaluar, por ejemplo, porque lee un atributo opcional que no está en el contexto. Ninguno evaluationErrors enumera los errores.
UNRECOGNIZED_PAYLOAD La instancia no pudo decodificar Transaction.payload ni proyectarlo en un contexto, por ejemplo, debido a bytes con formato incorrecto, una instrucción o una operación de contrato no admitidas, o una transacción de Bitcoin con varios destinatarios. No se evalúa ninguna política. Ninguno evaluationErrors indica el error del analizador.

Los motivos se deciden en el siguiente orden:

  1. Si la carga útil no se puede decodificar ni proyectar en un contexto, la decisión es DENY con UNRECOGNIZED_PAYLOAD.
  2. De lo contrario, se evalúan todas las políticas aplicadas. Si coincide alguna política forbid, la decisión es DENY con FORBID_RULE_MATCHED, incluso si también coincidieron las políticas permit o si no se pudieron evaluar otras políticas.
  3. De lo contrario, si coincide al menos una política permit, la decisión es PERMIT. Los errores de otras políticas se siguen registrando en evaluationErrors.
  4. De lo contrario, la decisión es DENY con EVALUATION_ERROR si alguna política no se pudo evaluar, o NO_PERMIT_RULE_MATCHED si todas las políticas se evaluaron sin coincidir.

Los errores se registran incluso cuando la decisión es PERMIT, por lo que debes verificar el registro de evaluación para ver las políticas que nunca coinciden porque siempre fallan.

Situaciones

En las situaciones del 1 al 9, Alice (User::"alice@example.com") crea una transferencia de Ethereum válida con action establecido en Action::"internal_transfer_transaction" y estos metadatos, a menos que la situación indique lo contrario:

"metadata": {
  "fiatAmount": {"currencyCode": "USD", "units": "5000"},
  "principalValue1": "treasury-desk"
}

La transacción no tiene validity ni principalValue2.

En la siguiente tabla, se resume cada situación:

Situación Políticas o carga útil aplicadas Decisión Motivo del rechazo
1 Ninguno DENY NO_PERMIT_RULE_MATCHED
2 Un permit que coincide PERMIT
3 Un permit que no coincide DENY NO_PERMIT_RULE_MATCHED
4 Dos permit, uno coincide PERMIT
5 Un permit y un forbid que coinciden DENY FORBID_RULE_MATCHED
6 Un permit que lee un atributo faltante DENY EVALUATION_ERROR
7 El mismo permit, con has DENY NO_PERMIT_RULE_MATCHED
8 Un forbid que lee un atributo faltante y un permit que coincide PERMIT
9 Un forbid que requiere un atributo DENY FORBID_RULE_MATCHED
10 Una carga útil que no se puede decodificar DENY UNRECOGNIZED_PAYLOAD

Situación 1: No se aplican políticas

La instancia no tiene políticas ACTIVE ni PENDING_DISABLED.

Decisión: DENY, NO_PERMIT_RULE_MATCHED. Sin una política de permit coincidente, se rechazan todas las transacciones. En una instancia nueva, no se firma ninguna transacción automáticamente hasta que los autores de políticas activen una política de permit. Los firmantes deben autorizar cada una.

Situación 2: Un permiso que coincide

permit (principal, action, resource)
when { context has fiat_amount && context.fiat_amount <= 10000 };

Decisión: PERMIT. El importe, 5,000, es como máximo 10,000. determiningPolicyIds incluye esta política.

Situación 3: Un permiso que no coincide

permit (principal, action, resource)
when { context has fiat_amount && context.fiat_amount <= 1000 };

Decisión: DENY, NO_PERMIT_RULE_MATCHED. El importe, 5,000, es superior a 1,000, y ninguna otra política permite la transacción.

Situación 4: Dos permisos, uno coincide

Política A:

permit (principal, action, resource)
when { context has fiat_amount && context.fiat_amount <= 1000 };

Política B:

permit (
  principal == User::"alice@example.com",
  action == Action::"internal_transfer_transaction",
  resource == Resource::"transaction_management"
)
when {
  context has fiat_amount && context.fiat_amount <= 25000 &&
  context has principal_value1 && context.principal_value1 == "treasury-desk"
};

Decisión: PERMIT. La política A no coincide, pero la política B sí. Basta con una política de permit que coincida. determiningPolicyIds solo enumera la política B.

Situación 5: Una prohibición anula un permiso

Política A:

permit (principal, action, resource)
when { context has fiat_amount && context.fiat_amount <= 10000 };

Política B:

forbid (principal, action, resource)
when { context has principal_value1 && context.principal_value1 == "treasury-desk" };

Decisión: DENY, FORBID_RULE_MATCHED. Ambas políticas coinciden y prevalece forbid. determiningPolicyIds solo enumera la política B.

Situación 6: Un permiso lee un atributo faltante

permit (principal, action, resource)
when { context.validity <= 3600 };

Decisión: DENY, EVALUATION_ERROR. La transacción no tiene validity, por lo que context.validity no existe y la política no se evalúa. Ninguna otra política permite la transacción. evaluationErrors muestra el error.

Situación 7: El mismo permiso, con has

permit (principal, action, resource)
when { context has validity && context.validity <= 3600 };

Decisión: DENY, NO_PERMIT_RULE_MATCHED. context has validity es falso, por lo que la política se evalúa como falsa en lugar de fallar. La transacción se sigue rechazando, pero el motivo muestra que la política se evaluó y no coincidió.

Situación 8: Una prohibición lee un atributo faltante

Política A:

permit (principal, action, resource)
when { context has fiat_amount && context.fiat_amount <= 10000 };

Política B:

forbid (principal, action, resource)
when { context.principal_value2 == "sanctioned-counterparty" };

Decisión: PERMIT. La transacción no tiene principalValue2, por lo que la política B no se evalúa y se omite. La política A coincide. determiningPolicyIds enumera la política A y evaluationErrors enumera el error de la política B.

Situación 9: Una prohibición que requiere un atributo

La política A es la política permit de la situación 8. Política B:

forbid (principal, action, resource)
unless { context has fiat_amount };

Decisión: PERMIT para la transacción en estos casos, que tiene un fiatAmount. Si Alicia omite fiatAmount, la decisión es DENY, FORBID_RULE_MATCHED: context has fiat_amount es falso, por lo que la política forbid coincide. Esta política no puede fallar en la evaluación, ya que solo usa has.

Para combinar un atributo obligatorio con una verificación de su valor en una política de forbid, primero verifica el atributo:

forbid (principal, action, resource)
unless { context has fiat_amount && context.fiat_amount <= 10000 };

Situación 10: Una carga útil que no se puede decodificar

Alice crea una transacción cuyo payload no puede decodificar el analizador para Transaction.schema, por ejemplo, bytes de RLP truncados, una PSBT de Bitcoin con varios destinatarios o una transacción de Solana con una instrucción no admitida.

Decisión: DENY, UNRECOGNIZED_PAYLOAD. No se evalúa ninguna política. evaluationErrors enumera el error del analizador y determiningPolicyIds está vacío.

Registro de evaluación

Cada evaluación produce un PolicyEvaluationRecord que no se puede cambiar. Se devuelve en Transaction.policyEvaluationRecord y tiene los siguientes campos:

Campo Valor
decision PERMIT o DENY.
denyReason Es uno de los motivos de rechazo. No se establece cuando la decisión es PERMIT.
determiningPolicyIds En PERMIT, los IDs de las políticas de permit que coincidieron. En FORBID_RULE_MATCHED, los IDs de las políticas de forbid que coincidieron. De lo contrario, estará vacío.
evaluationErrors Son los errores de las políticas que no se pudieron evaluar o el error del analizador cuando denyReason es UNRECOGNIZED_PAYLOAD.
evaluationContext El contexto de Cedar con el que se evaluaron las políticas, como un google.protobuf.Struct. Los números enteros de 64 bits se almacenan como cadenas decimales para mantener su precisión.
policySetVersion La versión del conjunto de políticas que usó la evaluación.
keyListMemberships Las membresías de KeyList se resolvieron durante la evaluación.
intentDigest Es el resumen codificado en formato hexadecimal en minúsculas de la carga útil de la transacción que se evaluó. El hash depende del esquema de la carga útil: Keccak-256 de la transacción para ETHEREUM_TX_V2, el sighash de la primera entrada para BITCOIN_TX_V2, SHA-256 del mensaje sin firmar para SOLANA_TX_V1 y el resumen de firma EIP-712, keccak256(0x1901 ++ domainSeparator ++ hashStruct(message)), para EIP712_TYPED_DATA_V1.
evaluationTime Cuándo se ejecutó la evaluación.