多方運算會先根據執行個體中的交易政策評估交易,再簽署交易。本文說明系統會評估哪些政策、政策如何經歷生命週期、政策可檢查哪些項目,以及交易遭拒的原因。
交易評估方式
交易政策是單一 Cedar
permit 或 forbid 陳述式。當簽署者呼叫 CreateTransaction 時,執行個體會使用 Transaction.schema 的剖析器解碼 Transaction.payload 中的未簽署交易,並在任何金鑰共用使用前,根據交易評估每項強制執行的政策。當政策狀態為 ACTIVE 或 PENDING_DISABLED 時,系統會強制執行政策。詳情請參閱「政策生命週期」。
這項決策遵循三項規則:
- 預設為拒絕。交易必須至少與一項
permit政策相符,才能獲得許可。如果沒有強制執行的政策,所有交易都會遭到拒絕。 - 禁止勝出。如果符合任何
forbid政策,即使也符合permit政策,交易仍會遭拒。 - 如果政策無法評估,就不會比對。舉例來說,如果政策讀取交易沒有的選用情境屬性,就無法評估。執行個體會記錄錯誤、略過該政策,並根據其餘強制執行的政策做出決定。這項原則也適用於
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的值,格式為 CedarAction實體。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 會列出剖析器錯誤。 |
原因的決定順序如下:
- 如果無法解碼酬載或將其投影至情境,則決策為
DENY,並顯示UNRECOGNIZED_PAYLOAD。 - 否則,系統會評估所有強制執行的政策。如果符合任何
forbid政策,則判定結果為DENY,並顯示FORBID_RULE_MATCHED,即使permit政策也符合或無法評估其他政策。 - 否則,如果至少有一項
permit政策相符,則決策為PERMIT。其他政策的錯誤仍會記錄在evaluationErrors中。 - 否則,如果任何政策評估失敗,則決策為
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 |
評估作業的執行時間。 |