Avaliação da política de transações

Antes de assinar uma transação, a computação multipartidária a avalia em relação às políticas de transação na sua instância. Neste documento, descrevemos quais políticas são avaliadas, como uma política passa pelo ciclo de vida, o que uma política pode verificar e por que uma transação é negada.

Como uma transação é avaliada

Uma política de transação é uma única instrução permit ou forbid do Cedar. Quando um Signer chama CreateTransaction, a instância decodifica a transação não assinada em Transaction.payload com o analisador para Transaction.schema e avalia cada política aplicada em relação à transação antes que qualquer compartilhamento de chave seja usado. Uma política é aplicada quando o estado dela é ACTIVE ou PENDING_DISABLED. Para mais informações, consulte Ciclo de vida da política.

A decisão segue três regras:

  1. Negar por padrão. Uma transação só é permitida se pelo menos uma política permit corresponder a ela. Sem políticas aplicadas, todas as transações são negadas.
  2. A proibição vence. Se alguma política forbid corresponder, a transação será negada, mesmo que as políticas permit também correspondam.
  3. Uma política que não é avaliada não corresponde. Por exemplo, uma política que lê um atributo de contexto opcional que a transação não tem não é avaliada. A instância registra o erro, ignora essa política e decide com as políticas aplicadas restantes. Isso também se aplica às políticas forbid. Portanto, escreva políticas forbid que não falhem em um atributo ausente. Veja um exemplo no Cenário 8 e no Cenário 9.

Uma transação que contém várias transferências, como uma transação Solana com várias instruções de transferência, é avaliada uma vez para cada transferência. A transação só será permitida se todas as transferências forem permitidas. Se alguma transferência for negada ou não puder ser decodificada, toda a transação será negada.

Cada avaliação, seja de permissão ou negação, produz um registro de avaliação que é retornado na transação em Transaction.policyEvaluationRecord.

A decisão controla se a instância pode assinar a transação sem esperar por mais signatários:

  • PERMIT: se a solicitação CreateTransaction já tiver autorizações de signatário suficientes para atender à política de chaves do KeyRing, a transação será movida para SIGNING imediatamente. Caso contrário, ela será criada em PENDING_AUTHORIZATION e aguardará mais signatários.
  • DENY: a instância não assina a transação automaticamente. A transação é criada em PENDING_AUTHORIZATION com policyEvaluationRecord.decision definido como DENY. Os signatários podem analisar o registro de avaliação e negar a transação (RecordSignerDecision com DENY) ou autorizá-la de qualquer maneira (AUTHORIZE). A transação só será assinada se houver signatários suficientes para atender à política de chaves.

Uma política de transação é uma proteção para a assinatura automática, não um bloqueio para os signatários: eles podem autorizar uma transação que as políticas negam. O PolicyEvaluationRecord original permanece anexado à transação para auditoria.

Ciclo de vida da política

As políticas de transação não podem ser alteradas. TransactionPolicy não tem um método de atualização, e o rule é imutável. Para mudar uma regra, desative a política com DisableTransactionPolicy e crie uma nova com CreateTransactionPolicy.

A ativação ou desativação de uma política precisa de aprovações de um quórum de autores de políticas (usuários com um papel POLICY_AUTHOR ativo em User.roleStatuses). Cada autor de política assina um TransactionPolicySigningPayload que nomeia a política (ou, para CreateTransactionPolicy, a instância), a ação (ACTIVATE ou DISABLE) e rule.cedarDocument. A solicitação que propõe uma mudança conta como a primeira aprovação. Outros autores de políticas aprovam com ApproveTransactionPolicy. O número de aprovações necessárias está em activationInfo.requiredApprovals e deactivationInfo.requiredApprovals. Se a instância precisar de apenas uma aprovação, a mudança entrará em vigor imediatamente.

A tabela a seguir descreve cada estado da política e se as políticas nesse estado são aplicadas:

Estado Significado Aplicada?
PENDING_ACTIVE A política foi proposta e está aguardando as aprovações do autor da política. Não
ACTIVE A política tem activationInfo.requiredApprovals aprovações. Sim
PENDING_DISABLED Um autor de política pediu para desativar a política, e a solicitação está aguardando aprovações. Sim, até que o pedido tenha deactivationInfo.requiredApprovals aprovações
DISABLED A política foi desativada ou retirada antes de ser ativada. As políticas desativadas são mantidas para auditoria. Não

Uma política passa por esses estados em uma direção:

  • PENDING_ACTIVE para ACTIVE: a ativação tem aprovações suficientes.
  • PENDING_ACTIVE a DISABLED: um autor de política retira a política com DisableTransactionPolicy antes que ela seja ativada.
  • ACTIVE para PENDING_DISABLED: um autor de política chama DisableTransactionPolicy.
  • PENDING_DISABLED para DISABLED: a solicitação de desativação tem aprovações suficientes.

Uma política entra em cada estado pendente no máximo uma vez. Não é possível reativar uma política desativada. Em vez disso, crie uma nova política.

Para saber quem pode chamar esses métodos, consulte Funções do usuário.

Versão do conjunto de políticas

O conjunto de políticas é o conjunto de políticas aplicadas (ACTIVE ou PENDING_DISABLED) em um momento específico. Sempre que uma ativação ou desativação é concluída, a instância atribui a essa transição o próximo número em um contador para a instância e o registra em activationInfo.transitionSequence ou deactivationInfo.transitionSequence. Esse número é a versão do conjunto de políticas produzida pela transição.

A tabela a seguir mostra como a versão do conjunto de políticas muda em uma sequência de eventos:

Evento Sequência de transição Políticas aplicadas
A política A é ativada. A: activationInfo.transitionSequence = 1 A
A política B é ativada. B: activationInfo.transitionSequence = 2 A, B
A política C é proposta (PENDING_ACTIVE) e depois retirada. Nenhum A, B
Uma solicitação de desativação da política A é aprovada. A: deactivationInfo.transitionSequence = 3 B

A retirada de uma política PENDING_ACTIVE nunca altera o conjunto de políticas aplicadas. Portanto, ela não recebe uma sequência de transição e não altera a versão do conjunto de políticas. Enquanto uma transição aguarda aprovações, o transitionSequence dela não é definido.

Cada avaliação lê as políticas, as KeyLists e as chaves de acordo com o createTime da transação e registra a maior sequência de transição concluída naquele momento em policyEvaluationRecord.policySetVersion. Se duas avaliações tiverem a mesma versão do conjunto de políticas, elas usaram as mesmas políticas.

A solicitação do Cedar

Uma solicitação do Cedar tem um principal, uma ação, um recurso e um contexto. Exemplo:

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
};

Como um usuário é mapeado para o principal do Cedar

O principal é o iniciador da transação: o usuário cujo principal corresponde à identidade autenticada do autor da chamada de CreateTransaction. O usuário precisa ter um papel SIGNER ativo. No Cedar, o principal é uma entidade User com o ID do campo principal do usuário, por exemplo, User::"alice@example.com".

A instância não transmite dados de entidade para o Cedar. Uma entidade User não tem atributos nem pais, então:

  • principal == User::"alice@example.com" corresponde às transações que Alice inicia.
  • principal in Team::"treasury" nunca corresponde. Para limitar quem pode assinar com uma chave, use a política de chave do KeyRing. Para definir o escopo de uma regra para um grupo de chaves, marque context.principal_keyring.
  • Uma regra que lê um atributo de principal, como principal.team, não é avaliada.

O principal de um usuário não pode ser alterado. Portanto, uma regra que nomeia um principal continua se referindo à mesma identidade.

Ação e recurso

  • action: o valor de Transaction.action na solicitação CreateTransaction, formatado como uma entidade Cedar Action. CreateTransaction aceita as seguintes ações:

    Ação Usado para
    Action::"buy_transaction" Uma transação que adquire um recurso.
    Action::"sell_transaction" Uma transação que descarta um ativo.
    Action::"internal_transfer_transaction" Uma transferência de um recurso, como um pagamento ou uma transferência entre chaves.

    O iniciador escolhe a ação. A computação multipartidária não a infere do payload. Uma regra pode corresponder a uma ação (action == Action::"buy_transaction"), uma lista de ações (action in [Action::"buy_transaction", Action::"sell_transaction"]) ou deixar action sem restrições para corresponder a todas as ações.

  • resource: sempre Resource::"transaction_management" para avaliações de transações. Uma regra pode verificar resource == Resource::"transaction_management" ou deixar resource sem restrições.

Atributos de contexto

O contexto contém os atributos que as políticas verificam. Eles vêm de dois lugares:

  • O payload da transação e a chave de assinatura. A instância decodifica Transaction.payload e adiciona os IDs da chave de assinatura e do KeyRing. Esses atributos são principal_keyring, principal_key, blockchain, schema, secondary_key, amount_token_scaled, token_symbol, contract_address, chain_id, max_fee_atomic, payload e time_stamp. Para ver a tabela completa e o comportamento de cada blockchain, consulte Regras da política de transações de gravação.
  • O campo metadata da transação,que o iniciador define em CreateTransaction.

A tabela a seguir descreve os atributos de metadados:

Campo Transaction.metadata Atributo do Cedar Tipo Cedar Valor
fiatAmount context.fiat_amount Long O valor da transação em dólares americanos inteiros (google.type.Money). currencyCode precisa ser USD, units precisa ser zero ou mais e nanos precisa ser 0.
validity context.validity Long Por quanto tempo a transação é válida (google.protobuf.Duration), em segundos inteiros. Precisa ser maior que 0 segundos, sem frações de segundos.
principalValue1 context.principal_value1 String Qualquer valor definido por você, com até 256 caracteres, como uma mesa de negociação ou um centro de custo.
principalValue2 context.principal_value2 String Qualquer valor que você definir, com até 256 caracteres, por exemplo, um tíquete interno ou ID de contraparte.

As seguintes regras se aplicam aos metadados:

  • Os campos não definidos não estão no contexto. Se uma transação não tiver fiatAmount, context.fiat_amount não vai existir, e uma política que a ler não será avaliada. Verifique primeiro o atributo com has, como em context has fiat_amount && context.fiat_amount <= 10000.
  • Metadados inválidos causam falha na solicitação. Se um campo violar as regras da tabela anterior, por exemplo, um fiatAmount em EUR ou com nanos diferente de zero, um validity de 0 segundos ou um principalValue1 com mais de 256 caracteres, CreateTransaction falhará com INVALID_ARGUMENT e nenhuma política será avaliada.
  • A instância não verifica os metadados em relação à transação. fiatAmount é o valor que o iniciador diz que a transação vale. Outros signatários veem os metadados em Transaction.metadata e policyEvaluationRecord.evaluationContext antes de autorizá-los. Para limitar os valores on-chain independentemente dos metadados, verifique context.amount_token_scaled e context.contract_address.
  • Os metadados não podem ser alterados após a criação da transação.

KeyRings

Um KeyRing agrupa chaves. O contexto de cada transação tem principal_keyring e principal_key. Assim, uma regra pode ser aplicada a todas as chaves em um KeyRing, incluindo as criadas posteriormente, e um KeyRing pode ser uma entrada em uma KeyList. Para mais informações e um exemplo de regra, consulte KeyRings.

KeyLists

Uma KeyList é uma lista nomeada de endereços de blockchain que as políticas podem consultar, por exemplo, como uma lista de permissões de destinos. Cada entrada é uma das seguintes:

  • Uma entrada manual (manualEntry): um endereço que a instância não gerencia, com campos opcionais source e reason.
  • Uma chave na instância (key): corresponde aos endereços da chave.
  • Um KeyRing na instância (keyRing): corresponde aos endereços de todas as chaves ativas no KeyRing, incluindo as criadas após a adição da entrada.

As KeyLists podem mudar depois que uma política é aprovada. Quando você adiciona ou remove uma entrada com AddKeyListEntries ou RemoveKeyListEntries, todas as políticas que se referem à KeyList usam as novas entradas da próxima avaliação, sem novas aprovações do autor da política. Não é possível excluir uma KeyList com DeleteKeyList enquanto uma política PENDING_ACTIVE, ACTIVE ou PENDING_DISABLED se referir a ela.

KeyLists no contexto do Cedar

No momento da avaliação, a instância expande cada KeyList em um Set simples de endereços de blockchain String: as entradas manuais, além dos endereços derivados de cada entrada de chave e KeyRing. Cada conjunto é transmitido para o Cedar como context.key_lists["<var>KEY_LIST_ID</var>"], em que KEY_LIST_ID é o ID de {keyList} no nome do recurso da KeyList, por exemplo, trusted-destinations para projects/my-project/locations/us-central1/instances/my-instance/keyLists/trusted-destinations.

Uma regra testa a associação com o operador Cedar contains em relação ao endereço de destino decodificado, context.secondary_key. Por exemplo, para permitir transferências apenas para endereços na KeyList trusted-destinations:

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

Para negar transferências para qualquer endereço na KeyList blocked-addresses:

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

Lembre-se:

  • Use a sintaxe de colchetes, não a de ponto, para o ID da KeyList. A sintaxe de ponto do Cedar (context.key_lists.trusted-destinations) não consegue analisar quando o ID contém um hífen, que o Cedar lê como subtração, ou começa com um dígito, como um UUID gerado pelo servidor. A sintaxe de colchetes, context.key_lists["<var>KEY_LIST_ID</var>"], funciona para todos os ID.
  • key_lists está sempre presente, e uma regra pode ler uma KeyList diretamente nele. CreateTransactionPolicy verifica se todas as KeyLists mencionadas pela regra existem, e DeleteKeyList se recusa a excluir uma KeyList enquanto uma política se refere a ela. Use context.key_lists has "<var>KEY_LIST_ID</var>" somente quando uma regra se referir a uma KeyList que talvez ainda não exista.
  • Os endereços são comparados como strings. secondary_key usa o formato que as regras de política de transação de gravação descrevem para cada blockchain, por exemplo, o formato de checksum EIP-55 com letras maiúsculas e minúsculas para o Ethereum. Adicione entradas manuais no mesmo formato.
  • O secondary_key de uma transferência de token Solana é a conta de token de destino, não a carteira do proprietário. Para permitir uma carteira, adicione a conta de token dela para cada mint que você permitir.

Cada avaliação resolve a associação da KeyList de acordo com o createTime da transação, ao mesmo tempo que as políticas, e registra o resultado em policyEvaluationRecord.keyListMemberships: a KeyList, o endereço testado, se era um membro e a entrada correspondente.

Como uma KeyList muda o que uma política permite, limite quem pode alterá-la. Os métodos KeyList precisam de roles/mpc.admin. Para mais informações, consulte Permissões do IAM.

Motivos da recusa

Quando policyEvaluationRecord.decision é DENY, policyEvaluationRecord.denyReason diz o motivo. A tabela a seguir descreve cada motivo de negação:

Motivo da recusa Quando Como determinar políticas
FORBID_RULE_MATCHED Pelo menos uma política de forbid aplicada correspondeu. As políticas de forbid que corresponderam.
NO_PERMIT_RULE_MATCHED O payload foi decodificado, nenhuma política forbid foi correspondida, nenhuma política falhou na avaliação e nenhuma política permit foi correspondida. Nenhum
EVALUATION_ERROR Nenhuma política permit ou forbid correspondeu, e pelo menos uma política não foi avaliada, por exemplo, porque lê um atributo opcional que não está no contexto. Nenhuma. evaluationErrors lista os erros.
UNRECOGNIZED_PAYLOAD A instância não conseguiu decodificar Transaction.payload ou projetá-lo em um contexto, por exemplo, devido a bytes malformados, uma instrução ou operação de contrato sem suporte ou uma transação de Bitcoin com vários destinatários. Nenhuma política é avaliada. Nenhuma. evaluationErrors lista o erro do analisador.

Os motivos são decididos na seguinte ordem:

  1. Se o payload não puder ser decodificado ou projetado em um contexto, a decisão será DENY com UNRECOGNIZED_PAYLOAD.
  2. Caso contrário, todas as políticas aplicadas serão avaliadas. Se alguma política forbid corresponder, a decisão será DENY com FORBID_RULE_MATCHED, mesmo que as políticas permit também correspondam ou que outras políticas não sejam avaliadas.
  3. Caso contrário, se pelo menos uma política permit corresponder, a decisão será PERMIT. Os erros de outras políticas ainda são registrados em evaluationErrors.
  4. Caso contrário, a decisão será DENY com EVALUATION_ERROR se alguma política não tiver sido avaliada ou NO_PERMIT_RULE_MATCHED se todas as políticas tiverem sido avaliadas sem correspondência.

Os erros são registrados mesmo quando a decisão é PERMIT. Portanto, verifique o registro de avaliação em busca de políticas que nunca correspondem porque sempre falham.

Cenários

Nos cenários de 1 a 9, Alice (User::"alice@example.com") cria uma transferência válida do Ethereum com action definido como Action::"internal_transfer_transaction" e estes metadados, a menos que o cenário diga o contrário:

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

A transação não tem validity nem principalValue2.

A tabela a seguir resume cada cenário:

Cenário Políticas de restrição ou payload Decisão Motivo da recusa
1 Nenhum DENY NO_PERMIT_RULE_MATCHED
2 Um permit que corresponde PERMIT
3 Um permit que não corresponde DENY NO_PERMIT_RULE_MATCHED
4 Dois permit, um corresponde PERMIT
5 Um permit e um forbid que correspondem DENY FORBID_RULE_MATCHED
6 Um permit que lê um atributo ausente DENY EVALUATION_ERROR
7 O mesmo permit, com has DENY NO_PERMIT_RULE_MATCHED
8 Um forbid que lê um atributo ausente e um permit que corresponde PERMIT
9 Um forbid que requer um atributo DENY FORBID_RULE_MATCHED
10 Um payload que não pode ser decodificado DENY UNRECOGNIZED_PAYLOAD

Cenário 1: nenhuma política aplicada

A instância não tem políticas ACTIVE ou PENDING_DISABLED.

Decisão: DENY, NO_PERMIT_RULE_MATCHED. Sem uma política permit correspondente, todas as transações são negadas. Em uma nova instância, nenhuma transação é assinada automaticamente até que os autores de políticas ativem uma política permit. Os signatários precisam autorizar cada uma delas.

Cenário 2: uma permissão correspondente

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

Decisão: PERMIT. O valor, 5.000, é no máximo 10.000. determiningPolicyIds lista essa política.

Cenário 3: uma permissão que não corresponde

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

Decisão: DENY, NO_PERMIT_RULE_MATCHED. O valor, 5.000, é maior que 1.000, e nenhuma outra política permite a transação.

Cenário 4: duas permissões, uma correspondência

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"
};

Decisão: PERMIT. A política A não corresponde, mas a política B sim. Uma política de correspondência permit é suficiente. determiningPolicyIds lista apenas a Política B.

Cenário 5: uma proibição substitui uma permissão

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" };

Decisão: DENY, FORBID_RULE_MATCHED. As duas políticas correspondem, e forbid vence. determiningPolicyIds lista apenas a Política B.

Cenário 6: uma permissão lê um atributo ausente

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

Decisão: DENY, EVALUATION_ERROR. A transação não tem validity, então context.validity não existe e a política não é avaliada. Nenhuma outra política permite a transação. evaluationErrors lista o erro.

Cenário 7: a mesma permissão, com has

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

Decisão: DENY, NO_PERMIT_RULE_MATCHED. context has validity é falso, então a política é avaliada como falsa em vez de falhar. A transação ainda é negada, mas o motivo mostra que a política foi avaliada e não correspondeu.

Cenário 8: uma proibição lê um atributo ausente

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" };

Decisão: PERMIT. A transação não tem principalValue2, então a Política B não é avaliada e é ignorada. A política A corresponde. determiningPolicyIds lista a Política A, e evaluationErrors lista o erro da Política B.

Cenário 9: uma proibição que exige um atributo

A política A é a política permit do cenário 8. Política B:

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

Decisão: PERMIT para a transação nesses cenários, que tem um fiatAmount. Se Alice omitir fiatAmount, a decisão será DENY, FORBID_RULE_MATCHED: context has fiat_amount é falso, então a política forbid corresponde. Essa política não pode falhar na avaliação, porque usa apenas has.

Para combinar um atributo obrigatório com uma verificação do valor dele em uma política forbid, primeiro verifique o atributo:

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

Cenário 10: um payload que não pode ser decodificado

Alice cria uma transação cujo payload não pode ser decodificado pelo analisador para Transaction.schema, por exemplo, bytes RLP truncados, um PSBT de Bitcoin com vários destinatários ou uma transação Solana com uma instrução sem suporte.

Decisão: DENY, UNRECOGNIZED_PAYLOAD. Nenhuma política é avaliada. evaluationErrors lista o erro do analisador, e determiningPolicyIds fica vazio.

Registro de avaliação

Cada avaliação produz um PolicyEvaluationRecord que não pode ser alterado. Ele é retornado em Transaction.policyEvaluationRecord e tem os seguintes campos:

Campo Valor
decision PERMIT ou DENY.
denyReason Um dos motivos de negação. Não definido quando a decisão é PERMIT.
determiningPolicyIds Em PERMIT, os IDs das políticas de permit que corresponderam. Em FORBID_RULE_MATCHED, os IDs das políticas de forbid que corresponderam. Caso contrário, vazio.
evaluationErrors Os erros de políticas que não foram avaliadas ou o erro do analisador quando denyReason é UNRECOGNIZED_PAYLOAD.
evaluationContext O contexto do Cedar em que as políticas foram avaliadas, como um google.protobuf.Struct. Os números inteiros de 64 bits são armazenados como strings decimais para manter a precisão.
policySetVersion A versão do conjunto de políticas usada na avaliação.
keyListMemberships As assinaturas de KeyList resolvidas durante a avaliação.
intentDigest O resumo codificado em hexadecimal em letras minúsculas do payload da transação que foi avaliado. O hash depende do esquema do payload: Keccak-256 da transação para ETHEREUM_TX_V2, o sighash da primeira entrada para BITCOIN_TX_V2, SHA-256 da mensagem não assinada para SOLANA_TX_V1 e o resumo de assinatura EIP-712, keccak256(0x1901 ++ domainSeparator ++ hashStruct(message)), para EIP712_TYPED_DATA_V1.
evaluationTime Quando a avaliação foi executada.