交易政策評估

多方運算會先根據執行個體中的交易政策評估交易,再簽署交易。本文說明系統會評估哪些政策、政策如何經歷生命週期、政策可檢查哪些項目,以及交易遭拒的原因。

交易評估方式

交易政策是單一 Cedar permit 或 forbid 陳述式。當簽署者呼叫 CreateTransaction 時,執行個體會使用 Transaction.schema 的剖析器解碼 Transaction.payload 中的未簽署交易,並在任何金鑰共用使用前,根據交易評估每項強制執行的政策。當政策狀態為 ACTIVE 或 PENDING_DISABLED 時,系統會強制執行政策。詳情請參閱「政策生命週期」。

這項決策遵循三項規則:

  1. 預設為拒絕。交易必須至少與一項 permit 政策相符,才能獲得許可。如果沒有強制執行的政策,所有交易都會遭到拒絕。
  2. 禁止勝出。如果符合任何 forbid 政策,即使也符合 permit 政策,交易仍會遭拒。
  3. 如果政策無法評估,就不會比對。舉例來說,如果政策讀取交易沒有的選用情境屬性,就無法評估。執行個體會記錄錯誤、略過該政策,並根據其餘強制執行的政策做出決定。這項原則也適用於 forbid 政策,因此請編寫不會因缺少屬性而失敗的 forbid 政策。如需範例,請參閱情境 8 和情境 9。

如果交易包含多筆轉帳 (例如 Solana 交易有多筆轉帳指令),系統會針對每筆轉帳評估一次。只有在每筆轉移都獲得許可的情況下,系統才會允許交易。如果任何轉移遭拒或無法解碼,整筆交易就會遭拒。

無論是允許或拒絕,每次評估都會產生評估記錄,並在 Transaction.policyEvaluationRecord 的交易中傳回。

這項決策會控制執行個體是否能簽署交易,而不必等待更多簽署者:

  • PERMIT:如果 CreateTransaction 要求已包含足夠的簽署者授權,符合 KeyRing 的金鑰政策,交易就會立即移至 SIGNING。否則,系統會在 PENDING_AUTHORIZATION 中建立合約,並等待更多簽署人。
  • DENY:執行個體不會自動簽署交易。交易會在 PENDING_AUTHORIZATION 中建立,且 policyEvaluationRecord.decision 設為 DENY。簽署者可以查看評估記錄並拒絕交易 (RecordSignerDecision 搭配 DENY),或授權交易 (AUTHORIZE)。只有在足夠的簽署者授權交易,符合金鑰政策時,系統才會簽署交易。

交易政策是自動簽署的防護機制,而非簽署者的封鎖機制:簽署者可以授權政策拒絕的交易。原始 PolicyEvaluationRecord 仍會附加至交易,以供稽核。

政策生命週期

交易政策無法變更。TransactionPolicy 沒有更新方法,且其 rule 不可變。如要變更規則,請使用 DisableTransactionPolicy 停用政策,並使用 CreateTransactionPolicy 建立新政策。

啟用或停用政策時,必須取得政策作者 (在 User.roleStatuses 中具備有效 POLICY_AUTHOR 角色的使用者) 的法定人數核准。每位政策作者都會簽署 TransactionPolicySigningPayload,其中會列出政策 (或 CreateTransactionPolicy 的執行個體)、動作 (ACTIVATE 或 DISABLE) 和 rule.cedarDocument。提出變更的要求會視為第一項核准。其他政策作者則使用 ApproveTransactionPolicy 核准。所需核准次數介於 activationInfo.requiredApprovals 和 deactivationInfo.requiredApprovals 之間。如果執行個體只需要一個核准,變更會立即生效。

下表說明各個政策狀態,以及是否會強制執行處於該狀態的政策:

狀態 意義 是否強制執行?
PENDING_ACTIVE 政策已提出,正在等待政策作者核准。 否
ACTIVE 這項政策有 activationInfo.requiredApprovals 項核准。 是
PENDING_DISABLED 政策作者要求停用政策,要求正在等待核准。 是,直到要求獲得 deactivationInfo.requiredApprovals 個核准
DISABLED 政策已停用,或在生效前撤銷。停用的政策會保留供稽核使用。 否

政策會依序經歷這些狀態:

  • PENDING_ACTIVE到ACTIVE:啟用作業已獲得足夠的核准。
  • PENDING_ACTIVE 到 DISABLED:政策作者在政策生效前,使用 DisableTransactionPolicy 撤回政策。
  • ACTIVE 到 PENDING_DISABLED:政策作者呼叫 DisableTransactionPolicy。
  • PENDING_DISABLED到DISABLED:停用要求已獲得足夠的核准。

政策最多只會進入每個待處理狀態一次。停用的政策無法再次啟用,請改為建立新政策。

如要瞭解哪些使用者可以呼叫這些方法,請參閱「使用者角色」。

政策組合版本

「政策組合」是指在某個時間點強制執行的政策組合 (ACTIVE 或 PENDING_DISABLED)。每次啟用或停用完成時,執行個體會為該轉換提供執行個體計數器中的下一個數字,並記錄在 activationInfo.transitionSequence 或 deactivationInfo.transitionSequence 中。這個數字是轉換作業產生的政策集版本。

下表顯示政策組合版本在一連串事件中的變化:

事件 轉換序列 強制執行的政策
啟用政策 A。 A: activationInfo.transitionSequence = 1 A
啟用政策 B。 B: activationInfo.transitionSequence = 2 A、B
提出政策 C (PENDING_ACTIVE),然後撤回。 無 A、B
政策 A 的停用要求獲得核准。 答:deactivationInfo.transitionSequence = 3 B

撤銷 PENDING_ACTIVE 政策不會變更強制執行的政策組合,因此不會取得轉換序列,也不會變更政策組合版本。在轉換等待核准期間,其 transitionSequence 會取消設定。

每次評估都會讀取交易 createTime 的政策、KeyList 和金鑰,並在 policyEvaluationRecord.policySetVersion 中記錄當時完成的最高轉換序列。如果兩項評估作業的政策組合版本相同,則表示兩者使用相同的政策。

Cedar 要求

Cedar 要求包含主體、動作、資源和情境。例如:

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

使用者如何對應至 Cedar 主體

主體是交易的發起者:principal 與 CreateTransaction 呼叫者的已驗證身分相符的使用者。使用者必須具備有效的 SIGNER 角色。在 Cedar 中,主體是 User 實體,其 ID 是使用者的 principal 欄位,例如 User::"alice@example.com"。

執行個體不會將任何實體資料傳遞給 Cedar。User 實體沒有屬性,也沒有父項,因此:

  • principal == User::"alice@example.com" 會比對 Alice 發起的交易。
  • principal in Team::"treasury" 永遠不會相符。如要限制哪些人可以使用金鑰簽署,請使用 KeyRing 的金鑰政策。如要將規則範圍限定為一組金鑰,請勾選 context.principal_keyring。
  • 讀取 principal 屬性 (例如 principal.team) 的規則無法評估。

使用者的 principal 無法變更,因此指定主體的規則會持續參照相同身分。

動作和資源

  • action:CreateTransaction 要求中 Transaction.action 的值,格式為 Cedar Action 實體。CreateTransaction 接受下列動作:

    動作 適用於:
    Action::"buy_transaction" 取得資產的交易。
    Action::"sell_transaction" 處置資產的交易。
    Action::"internal_transfer_transaction" 資產轉移,例如付款或金鑰之間的轉移。

    發起人會選擇操作,多方運算不會從酬載推論。規則可以比對單一動作 (action == Action::"buy_transaction")、動作清單 (action in [Action::"buy_transaction", Action::"sell_transaction"]),或不限制 action,比對所有動作。

  • resource:交易評估一律為 Resource::"transaction_management"。規則可以檢查 resource == Resource::"transaction_management",或不限制 resource。

情境屬性

情境包含政策檢查的屬性。這些資料來自兩個地方:

  • 交易酬載和簽署金鑰。執行個體會解碼 Transaction.payload,並新增簽署金鑰及其 KeyRing 的 ID。這些屬性包括 principal_keyring、principal_key、blockchain、schema、secondary_key、amount_token_scaled、token_symbol、contract_address、chain_id、max_fee_atomic、payload 和 time_stamp。如需完整表格和各區塊鏈的行為,請參閱「寫入交易政策規則」。
  • 交易的 metadata 欄位,由發起人在 CreateTransaction 中設定。

下表說明中繼資料屬性:

Transaction.metadata 欄位 Cedar 屬性 Cedar 類型 值
fiatAmount context.fiat_amount Long 交易價值,以美元整數表示 (google.type.Money)。currencyCode 必須是 USD,units 必須大於或等於零,nanos 必須是 0。
validity context.validity Long 交易有效時間 (google.protobuf.Duration),以整數秒為單位。必須大於 0 秒,且不得有小數。
principalValue1 context.principal_value1 String 您定義的任何值,最多 256 個字元,例如交易平台或成本中心。
principalValue2 context.principal_value2 String 您定義的任何值,最多 256 個字元,例如內部支援單或交易對手 ID。

中繼資料適用下列規則:

  • 未設定的欄位不會出現在內容中。如果交易沒有 fiatAmount,則 context.fiat_amount 不存在,且讀取該交易的政策無法評估。請先檢查 has 屬性,例如 context has fiat_amount && context.fiat_amount <= 10000。
  • 中繼資料無效會導致要求失敗。如果欄位違反上表中的規則,例如 EUR 中的 fiatAmount 或非零 nanos、validity 為 0 秒,或 principalValue1 超過 256 個字元,CreateTransaction 會失敗並傳回 INVALID_ARGUMENT,且不會評估任何政策。
  • 執行個體不會根據交易檢查中繼資料。fiatAmount 是發起人認定的交易價值。其他簽署者在授權前,會先看到 Transaction.metadata 和 policyEvaluationRecord.evaluationContext 中的中繼資料。如要限制鏈上金額,而不受中繼資料影響,請查看 context.amount_token_scaled 和 context.contract_address。
  • 交易建立後,中繼資料即無法變更。

金鑰環

KeyRing可將金鑰分組。每筆交易的背景資訊都有 principal_keyring 和 principal_key,因此規則可以套用至 KeyRing 中的每個金鑰,包括之後建立的金鑰,而 KeyRing 可以是 KeyList 中的項目。如需更多資訊和規則範例,請參閱「KeyRings」。

KeyLists

KeyList 是區塊鏈位址的具名清單,可供政策參照,例如做為目的地許可清單。每個項目都是下列其中一項:

  • 手動項目 (manualEntry):執行個體未管理的位址,包含選填的 source 和 reason 欄位。
  • 執行個體中的金鑰 (key):與金鑰的位址相符。
  • 執行個體中的 KeyRing (keyRing):比對 KeyRing 中每個有效金鑰的位址,包括新增項目後建立的金鑰。

政策核准後,KeyList 可能會變更。使用 AddKeyListEntries 或 RemoveKeyListEntries 新增或移除項目時,參照 KeyList 的每項政策都會在下次評估時使用新項目,不需經過政策作者核准。當 PENDING_ACTIVE、ACTIVE 或 PENDING_DISABLED 政策參照 KeyList 時,無法使用 DeleteKeyList 刪除 KeyList。

Cedar 內容中的 KeyList

評估時,執行個體會將每個 KeyList 展開為 String 區塊鏈位址的平面 Set:手動項目,以及從每個金鑰和 KeyRing 項目衍生的位址。每個組合都會以 context.key_lists["<var>KEY_LIST_ID</var>"] 的形式傳遞至 Cedar,其中 KEY_LIST_ID 是 KeyList 資源名稱中的 {keyList} ID,例如 projects/my-project/locations/us-central1/instances/my-instance/keyLists/trusted-destinations 的 trusted-destinations。

規則會使用 Cedar contains 運算子,針對解碼的目的地位址 context.secondary_key 測試成員資格。舉例來說,如要僅允許轉移至 trusted-destinations KeyList 中的位址,請執行下列操作:

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

如要拒絕轉移至 blocked-addresses KeyList 中的任何地址:

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

請注意以下幾點:

  • KeyList ID 請使用括號語法,而非點語法。如果 ID 含有連字號 (Cedar 會解讀為減號),或開頭為數字 (伺服器產生的 UUID 可能會出現這種情況),Cedar 的點語法 (context.key_lists.trusted-destinations) 就無法剖析。括號語法 context.key_lists["<var>KEY_LIST_ID</var>"] 適用於所有 ID。
  • key_lists 一律存在,規則可以直接讀取其中的 KeyList。CreateTransactionPolicy 會驗證規則參照的每個 KeyList 是否存在,且 DeleteKeyList 會拒絕刪除政策參照的 KeyList。只有在規則參照可能尚未存在的 KeyList 時,才使用 context.key_lists has "<var>KEY_LIST_ID</var>"。
  • 系統會以字串形式比較地址。secondary_key 使用的格式,取決於寫入交易政策規則針對各區塊鏈描述的格式,例如以太坊的混合大小寫 EIP-55 核對和格式。請以相同格式手動新增項目。
  • Solana 代幣轉移的 secondary_key 是目的地代幣帳戶,而非擁有者的錢包。如要允許錢包,請為每個允許的鑄幣新增代幣帳戶。

每次評估都會解析交易 createTime 的 KeyList 成員資格 (與政策相同),並將結果記錄在 policyEvaluationRecord.keyListMemberships 中:KeyList、測試的位址、是否為成員,以及相符的項目。

KeyList 會變更政策允許的內容,因此請限制可變更 KeyList 的人員。KeyList 方法需要 roles/mpc.admin。詳情請參閱「IAM 權限」。

拒絕原因

當 policyEvaluationRecord.decision 為 DENY 時,policyEvaluationRecord.denyReason 會說明原因。下表說明各項拒絕原因:

拒絕原因 時間 決定政策
FORBID_RULE_MATCHED 至少有一項強制執行的forbid政策相符。 符合條件的 forbid 項政策。
NO_PERMIT_RULE_MATCHED 酬載已解碼,沒有相符的 forbid 政策,沒有政策評估失敗,也沒有相符的 permit 政策。 無
EVALUATION_ERROR 沒有符合的 permit 或 forbid 政策,且至少有一項政策無法評估,例如讀取了不在內容中的選用屬性。 無,evaluationErrors會列出錯誤。
UNRECOGNIZED_PAYLOAD 執行個體無法解碼 Transaction.payload 或將其投影至內容,例如位元組格式錯誤、指令或合約作業不受支援,或比特幣交易有多個收款人。未評估任何政策。 無,evaluationErrors 會列出剖析器錯誤。

原因的決定順序如下:

  1. 如果無法解碼酬載或將其投影至情境,則決策為 DENY,並顯示 UNRECOGNIZED_PAYLOAD。
  2. 否則,系統會評估所有強制執行的政策。如果符合任何 forbid 政策,則判定結果為 DENY,並顯示 FORBID_RULE_MATCHED,即使 permit 政策也符合或無法評估其他政策。
  3. 否則,如果至少有一項 permit 政策相符,則決策為 PERMIT。其他政策的錯誤仍會記錄在 evaluationErrors 中。
  4. 否則,如果任何政策評估失敗,則決策為 DENY,並顯示 EVALUATION_ERROR;如果每項政策評估後均不相符,則決策為 NO_PERMIT_RULE_MATCHED。

即使判定結果為 PERMIT,系統也會記錄錯誤,因此請檢查評估記錄,找出因一律失敗而從未比對成功的政策。

情境

在情境 1 到 9 中,除非情境另有說明,否則 Alice (User::"alice@example.com") 會建立有效的以太坊轉帳,並將 action 設為 Action::"internal_transfer_transaction" 和這項中繼資料:

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

交易沒有validity和principalValue2。

下表彙整各個情境:

情境 強制執行的政策或酬載 決定 拒絕原因
1 無 DENY NO_PERMIT_RULE_MATCHED
2 一個符合條件的permit PERMIT
3 一個不相符的permit DENY NO_PERMIT_RULE_MATCHED
4 兩個 permit,一個相符 PERMIT
5 permit 和 forbid 都相符 DENY FORBID_RULE_MATCHED
6 讀取缺少屬性的 permit DENY EVALUATION_ERROR
7 相同的permit,搭配has DENY NO_PERMIT_RULE_MATCHED
8 讀取缺少屬性的 forbid,以及相符的 permit PERMIT
9 需要屬性的 forbid DENY FORBID_RULE_MATCHED
10 無法解碼的酬載 DENY UNRECOGNIZED_PAYLOAD

情境 1:未強制執行政策

執行個體沒有 ACTIVE 或 PENDING_DISABLED 政策。

決策:DENY、NO_PERMIT_RULE_MATCHED。如果沒有相符的 permit 政策,所有交易都會遭到拒絕。在新的執行個體中,除非政策作者啟用 permit 政策,否則系統不會自動簽署任何交易,簽署者必須授權每筆交易。

情境 2:有一張相符的許可證

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

決策:PERMIT。金額 5,000 不得超過 10,000。 determiningPolicyIds列出這項政策。

情境 3:有一張許可證不符

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

決策:DENY、NO_PERMIT_RULE_MATCHED。金額 5,000 超過 1,000,且沒有其他政策允許這筆交易。

情境 4:兩個許可證,一個相符

政策 A:

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

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

決策:PERMIT。政策 A 不符合,但政策 B 符合。只要有一項相符的 permit 政策即可。determiningPolicyIds 只會列出政策 B。

情境 5:禁止覆寫允許

政策 A:

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

政策 B:

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

決策:DENY、FORBID_RULE_MATCHED。兩項政策都符合,forbid 優先。determiningPolicyIds 只會列出政策 B。

情境 6:許可讀取缺少的屬性

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

決策:DENY、EVALUATION_ERROR。交易沒有 validity,因此 context.validity 不存在,政策無法評估。沒有其他政策允許這項交易。evaluationErrors 會列出錯誤。

情境 7:相同許可證,

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

決策:DENY、NO_PERMIT_RULE_MATCHED。context has validity 為 false,因此政策會評估為 false,而不是失敗。交易仍遭拒,但原因顯示政策已評估且不相符。

情境 8:禁止讀取缺少的屬性

政策 A:

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

政策 B:

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

決策:PERMIT。交易沒有 principalValue2,因此政策 B 無法評估,系統會略過。政策 A 符合條件。determiningPolicyIds 列出政策 A,evaluationErrors 則列出政策 B 的錯誤。

情境 9:需要屬性的禁止

政策 A 是情境 8 的permit政策。政策 B:

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

決策:在這些情境中,PERMIT 交易,其 fiatAmount。如果 Alice 略過 fiatAmount,則決策為 DENY,FORBID_RULE_MATCHED:context has fiat_amount 為 false,因此 forbid 政策相符。這項政策只使用 has,因此評估作業不會失敗。

如要在單一 forbid 政策中,結合必要屬性和其值的檢查,請先檢查屬性:

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

情境 10:無法解碼的酬載

Alice 建立一筆交易,但剖析器無法解碼 Transaction.schema 的 payload,例如截斷的 RLP 位元組、有多個收款人的比特幣 PSBT,或含有不支援指令的 Solana 交易。

決策:DENY、UNRECOGNIZED_PAYLOAD。不會評估任何政策。evaluationErrors 會列出剖析器錯誤,determiningPolicyIds 則為空白。

評估記錄

每次評估都會產生無法變更的 PolicyEvaluationRecord。系統會在 Transaction.policyEvaluationRecord 中傳回這項資訊,其中包含下列欄位:

欄位 值
decision PERMIT 或 DENY。
denyReason 其中一個拒絕原因。如果決策為 PERMIT,則不設定。
determiningPolicyIds PERMIT:相符的 permit 政策 ID。FORBID_RULE_MATCHED:相符的 forbid 政策 ID。否則為空白。
evaluationErrors 評估失敗的政策錯誤,或 denyReason 為 UNRECOGNIZED_PAYLOAD 時的剖析器錯誤。
evaluationContext 評估政策時依據的 Cedar 情境,格式為 google.protobuf.Struct。64 位元整數會儲存為十進位字串,以維持精確度。
policySetVersion 評估作業使用的政策集版本。
keyListMemberships 評估期間解析的 KeyList 成員資格。
intentDigest 經評估的交易酬載摘要,以小寫十六進位編碼表示。雜湊值取決於酬載的結構定義:ETHEREUM_TX_V2 的交易 Keccak-256、BITCOIN_TX_V2 的第一個輸入 sighash、SOLANA_TX_V1 的未簽署訊息 SHA-256,以及 EIP712_TYPED_DATA_V1 的 EIP-712 簽署摘要 keccak256(0x1901 ++ domainSeparator ++ hashStruct(message))。
evaluationTime 評估作業的執行時間。