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:
- Negar por padrão. Uma transação só é permitida se pelo menos uma política
permitcorresponder a ela. Sem políticas aplicadas, todas as transações são negadas. - A proibição vence. Se alguma política
forbidcorresponder, a transação será negada, mesmo que as políticaspermittambém correspondam. - 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íticasforbidque 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çãoCreateTransactionjá tiver autorizações de signatário suficientes para atender à política de chaves do KeyRing, a transação será movida paraSIGNINGimediatamente. Caso contrário, ela será criada emPENDING_AUTHORIZATIONe aguardará mais signatários.DENY: a instância não assina a transação automaticamente. A transação é criada emPENDING_AUTHORIZATIONcompolicyEvaluationRecord.decisiondefinido comoDENY. Os signatários podem analisar o registro de avaliação e negar a transação (RecordSignerDecisioncomDENY) 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_ACTIVEparaACTIVE: a ativação tem aprovações suficientes.PENDING_ACTIVEaDISABLED: um autor de política retira a política comDisableTransactionPolicyantes que ela seja ativada.ACTIVEparaPENDING_DISABLED: um autor de política chamaDisableTransactionPolicy.PENDING_DISABLEDparaDISABLED: 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, marquecontext.principal_keyring.- Uma regra que lê um atributo de
principal, comoprincipal.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 deTransaction.actionna solicitaçãoCreateTransaction, formatado como uma entidade CedarAction.CreateTransactionaceita 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 deixaractionsem restrições para corresponder a todas as ações.resource: sempreResource::"transaction_management"para avaliações de transações. Uma regra pode verificarresource == Resource::"transaction_management"ou deixarresourcesem 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.payloade adiciona os IDs da chave de assinatura e do KeyRing. Esses atributos sãoprincipal_keyring,principal_key,blockchain,schema,secondary_key,amount_token_scaled,token_symbol,contract_address,chain_id,max_fee_atomic,payloadetime_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
metadatada transação,que o iniciador define emCreateTransaction.
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_amountnão vai existir, e uma política que a ler não será avaliada. Verifique primeiro o atributo comhas, como emcontext 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
fiatAmountemEURou comnanosdiferente de zero, umvalidityde 0 segundos ou umprincipalValue1com mais de 256 caracteres,CreateTransactionfalhará comINVALID_ARGUMENTe 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 emTransaction.metadataepolicyEvaluationRecord.evaluationContextantes de autorizá-los. Para limitar os valores on-chain independentemente dos metadados, verifiquecontext.amount_token_scaledecontext.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 opcionaissourceereason. - 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_listsestá sempre presente, e uma regra pode ler uma KeyList diretamente nele.CreateTransactionPolicyverifica se todas as KeyLists mencionadas pela regra existem, eDeleteKeyListse recusa a excluir uma KeyList enquanto uma política se refere a ela. Usecontext.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_keyusa 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_keyde 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:
- Se o payload não puder ser decodificado ou projetado em um contexto, a decisão
será
DENYcomUNRECOGNIZED_PAYLOAD. - Caso contrário, todas as políticas aplicadas serão avaliadas. Se alguma política
forbidcorresponder, a decisão seráDENYcomFORBID_RULE_MATCHED, mesmo que as políticaspermittambém correspondam ou que outras políticas não sejam avaliadas. - Caso contrário, se pelo menos uma política
permitcorresponder, a decisão seráPERMIT. Os erros de outras políticas ainda são registrados emevaluationErrors. - Caso contrário, a decisão será
DENYcomEVALUATION_ERRORse alguma política não tiver sido avaliada ouNO_PERMIT_RULE_MATCHEDse 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. |