diff --git a/clear-signing.mediawiki b/clear-signing.mediawiki new file mode 100644 index 0000000..b408587 --- /dev/null +++ b/clear-signing.mediawiki @@ -0,0 +1,812 @@ +
+ELIP: TBD
+Layer: Wallet
+Title: Clear Signing for Liquid
+Author: TBD
+Comments-Summary: No comments yet.
+Comments-URI: TBD
+Status: Draft
+Type: Standards Track
+Created: 2026-06-09
+License: BSD-3-Clause
+
+ +==Abstract== + +This document defines a structured metadata profile for wallets to interpret an Elements transaction for the user. +It helps users understand the consequences of signing a particular transaction and lowers the risk of blind signing +as much as possible. + +It does not specify how this metadata is verified or made trusted. + +==Copyright== + +This document is licensed under the 3-clause BSD license. + +==Motivation== + +To expand the Liquid ecosystem, infrastructure and user-facing applications should be able to explain +to a non-technical user what is happening and which outcomes to expect. Similar to banking systems, +users should be given as much security as possible by default. + +Therefore, ecosystem participants should make as much effort as possible in explaining actions to the user. + +This profile presents a way to interpret a general-purpose action of signing a transaction on the Liquid network to the user. + +==Specification== + +The keywords "MUST", "MUST NOT", "REQUIRED", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be interpreted as described in [https://www.rfc-editor.org/rfc/rfc2119 RFC 2119]. + +==Scope== + +This document is scoped to regular Liquid transfers, asset issuance/re-issuance, asset burn, and Simplicity covenant spends. + +Actions that are supported by the consensus but out of scope: +# Non-Simplicity Taproot script-path spends. +# Bitcoin Script and Miniscript covenant interpretation outside the regular cases above + +This document does not specify the final shape of wallet views. +It specifies what information should be available to the wallet and what information should be included +in the metadata documents used for clear signing. + +==General== + +This document defines a set of metadata documents that provide enough information for the wallet to explain user the +transaction that is being signed. + + + +==Account and Chain Binding== + +Every metadata document MUST be bound to the active Liquid or Elements chain. + +
+chain_id = "bip122:" + first_32_lowercase_hex_characters_of_genesis_block_hash
+
+ +Liquid mainnet uses: + +
+bip122:1466275836220db2944ca059a3a10ef6
+
+ +Liquid testnet uses: + +
+bip122:a771da8e52ee6ad581ed1e9a99825e5b
+
+ +Liquid asset identifiers MUST use the [https://github.com/ElementsProject/ELIPs/blob/main/elip-0144.mediawiki ELIP-0144] asset ID form: + +
+asset_id = chain_id + "/" + asset_namespace + ":" + asset_reference
+asset_namespace = "elip144"
+asset_reference = 64 lowercase hex characters
+
+ +The Liquid mainnet policy asset ID is: + +
+bip122:1466275836220db2944ca059a3a10ef6/elip144:6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d
+
+ +A wallet MUST verify that every field containing chain information refers to the wallet’s currently active chain. + +==Metadata Document Model== + +Each imported metadata object is a standalone document. +A user imports an account metadata document, an asset metadata document, an action metadata document, or a covenant metadata document. + +A clear-signing request MAY reference standalone documents by hash, by local identifier, by registry identifier, or by embedding the document directly. This document does not specify the transport mechanism for metadata documents. + +Each metadata document MUST contain: + +{| +! Field +! Type +! Required +! Description +|- +| $schema +| String +| Yes +| The JSON Schema URI for this document type. +|- +| kind +| String +| Yes +| The metadata document kind. +|- +| version +| Integer +| Yes +| The version of the document kind. +|- +| context +| Object +| Yes +| The constraints that MUST match before the document is used. +|- +| metadata +| Object +| Yes +| Trusted constants values that are usable after context matching. +|- +| display +| Object +| Yes +| User-facing labels, intent strings, detail labels, and formatting rules. +|} + +A wallet MUST verify the context before applying metadata or display. + +A wallet MUST NOT use metadata as the source of truth for the final transaction shape. A wallet MUST compute final transaction shape from the transaction, PSET, wallet state, and wallet policy. + +A metadata document MAY contain a documentId field. If present, documentId MUST be a hash of the canonical document bytes or a registry identifier bound to that hash. + +==Clear-Signing Review Layers== + +A wallet SHOULD review a clear-signing request in layers: + + +# Summary layer. +# Net balance layer. +# Account details layer. +# Asset details layer. +# Action details layer. +# Amount details layer. +# Simplicity covenant details layer. +# Raw fallback layer. + +The summary layer SHOULD be short and user-facing. + +The net balance layer MUST be wallet-computed and grouped per asset. + +The account details layer MUST show the full account identifier based on [https://github.com/ElementsProject/ELIPs/blob/main/elip-0144.mediawiki ELIP-0144]. + +The asset details layer MUST show the full asset ID. + +The action details layer MUST show the action being performed and the source of the action match. + +The amount details layer MUST show the base-unit amount and the formatted display amount when asset precision is available. + +The Simplicity covenant details layer MUST show the covenant metadata document and the matched action. + +The raw fallback layer SHOULD show the raw account, asset, action, and covenant binding values when the wallet cannot safely shorten or explain them. + +==Account Metadata Document== + +An account metadata document binds an [https://github.com/ElementsProject/ELIPs/blob/main/elip-0144.mediawiki ELIP-0144] account ID to a user-facing alias. + +The detailed account view MUST show the full account ID. The detailed account view MUST NOT shorten the account ID itself. + +If possible, a wallet SHOULD use an alias. If there is no alias, the wallet SHOULD make its best effort to point the user to the full account form so the user can verify it. + +A wallet MAY abbreviate an account in the summary layer only if the full account ID is available in the detailed view. + +===Account Metadata Fields=== + +{| +! Field +! Type +! Required +! Description +|- +| kind +| String +| Yes +| MUST be liquid.accountMetadata. +|- +| context.chainId +| String +| Yes +| The active Liquid or Elements chain ID. +|- +| context.account +| String +| Yes +| The full account ID. +|- +| metadata.alias +| String +| No +| The account alias. +|- +| metadata.description +| String +| No +| A human-readable description of the account. +|- +| metadata.owner +| String +| No +| A human-readable owner label. +|- +| metadata.infoUrl +| String +| No +| A URL with more information. +|- +| display.summaryLabel +| String +| No +| A short label for summary views. +|- +| display.detailFields +| Array +| No +| A list of detailed fields to display. +|} + +===Account Metadata Example=== + +
+{
+  "$schema": "https://example.invalid/liquid-clear-signing/account-metadata-v0.schema.json",
+  "kind": "liquid.accountMetadata",
+  "version": 0,
+  "context": {
+    "chainId": "bip122:1466275836220db2944ca059a3a10ef6",
+    "account": "bip122:1466275836220db2944ca059a3a10ef6:b781-7bc7-db64-c3de-3937-7eb7-c9ab-f799"
+  },
+  "metadata": {
+    "alias": "Alice",
+    "description": "Receiver account"
+  },
+  "display": {
+    "summaryLabel": "Alice",
+    "detailFields": [
+      { "label": "Account", "path": "$.context.account" },
+      { "label": "Alias", "path": "$.metadata.alias" }
+    ]
+  }
+}
+
+ +==Asset Metadata Document== + +An asset metadata document binds an [https://github.com/ElementsProject/ELIPs/blob/main/elip-0144.mediawiki ELIP-0144] asset ID to asset metadata. + +The detailed asset view MUST show the full asset ID. If an asset alias, ticker, or name is unavailable, the wallet MUST fall back to displaying the full asset ID. + +The wallet MUST validate that the asset metadata document refers to the active chain. + +The asset metadata document SHOULD be compatible with the existing Liquid asset contract shape used by the Liquid asset registry and [https://github.com/ElementsProject/ELIPs/blob/main/elip-0100.mediawiki ELIP-0100], including entity.domain, issuer_pubkey, name, precision, ticker, and version when those fields are available. + +===Asset Metadata Fields=== + +{| +! Field +! Type +! Required +! Description +|- +| kind +| String +| Yes +| MUST be liquid.assetMetadata. +|- +| context.chainId +| String +| Yes +| The active Liquid or Elements chain ID. +|- +| context.assetId +| String +| Yes +| The full ELIP-0144 asset ID. +|- +| metadata.name +| String +| No +| The asset display name. +|- +| metadata.ticker +| String +| No +| The ticker shown in the summary layer. +|- +| metadata.precision +| Integer +| No +| The decimal precision used to format base-unit amounts. +|- +| metadata.entity.domain +| String +| No +| The issuer or entity domain. +|- +| metadata.issuer_pubkey +| String +| No +| The issuer public key from the asset contract. +|- +| metadata.version +| Integer +| No +| The asset contract version. +|- +| display.summaryLabel +| String +| No +| A short label for summary views. +|- +| display.detailFields +| Array +| No +| A list of detailed fields to display. +|} + +===Asset Amount Formatting=== + +Amounts MUST be computed in base units first. + +If metadata.precision is available and valid, the wallet MAY display the amount as a decimal value. If precision is unavailable, the wallet MUST display the base-unit amount and SHOULD show that decimal precision is unavailable. + +Asset metadata MUST NOT override the asset ID of a transaction input or output. + +===Asset Metadata Example=== + +
+{
+  "$schema": "https://example.invalid/liquid-clear-signing/asset-metadata-v0.schema.json",
+  "kind": "liquid.assetMetadata",
+  "version": 0,
+  "context": {
+    "chainId": "bip122:1466275836220db2944ca059a3a10ef6",
+    "assetId": "bip122:1466275836220db2944ca059a3a10ef6/elip144:ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"
+  },
+  "metadata": {
+    "entity": {
+      "domain": "tether.to"
+    },
+    "name": "Tether USDt",
+    "precision": 8,
+    "ticker": "USDT",
+    "version": 0
+  },
+  "display": {
+    "summaryLabel": "USDT",
+    "detailFields": [
+      { "label": "Asset ID", "path": "$.context.assetId" },
+      { "label": "Ticker", "path": "$.metadata.ticker" },
+      { "label": "Name", "path": "$.metadata.name" },
+      { "label": "Precision", "path": "$.metadata.precision" }
+    ]
+  }
+}
+
+ +==Amounts and Net Balance Changes== + +A wallet MUST compute net balance changes per asset. + +A wallet MUST compute the amount lost by the wallet, the amount gained by the wallet, and the amount that stays in the wallet as change, per asset, when the required transaction data and wallet state are available. + +A wallet MUST show fees as part of the net balance review for the fee asset. + +A wallet MUST NOT use a metadata document as the source of truth for amount or asset movement. + +A wallet MUST reject or clearly downgrade the review if it cannot compute a safe review of wallet inputs, wallet outputs, fees, and confidentiality status. + +===Net Balance Change Fields=== + +A clear-signing request review SHOULD expose the following wallet-computed structure to the display layer: + +{| +! Field +! Type +! Required +! Description +|- +| assetId +| String +| Yes +| The ELIP-0144 asset ID. +|- +| inputAmount +| String +| Yes +| Wallet-owned input amount in base units. +|- +| outputAmount +| String +| Yes +| Wallet-owned output amount in base units. +|- +| changeAmount +| String +| No +| Wallet-owned change amount in base units when the wallet can identify change. +|- +| externalReceiveAmount +| String +| No +| Amount received by external accounts when known. +|- +| feeAmount +| String +| No +| Fee amount for this asset when this asset pays fees. +|- +| netAmount +| String +| Yes +| outputAmount - inputAmount, in base units, from the wallet perspective. +|- +| direction +| String +| Yes +| loss, gain, or neutral. +|- +| confidence +| String +| Yes +| verified, partial, or unknown. +|} + +===Net Balance Change Example=== + +
+{
+  "assetId": "bip122:1466275836220db2944ca059a3a10ef6/elip144:ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2",
+  "inputAmount": "1025000000",
+  "outputAmount": "0",
+  "externalReceiveAmount": "1025000000",
+  "netAmount": "-1025000000",
+  "direction": "loss",
+  "confidence": "verified"
+}
+
+ +==Action Metadata Document== + +An action metadata document describes the action being performed. + +In cases like issuance, burn, transfer, and multisig, the action can be a special case recognized by the wallet. + +In some cases, a wallet MAY rely on Bitcoin Script or transaction pattern matching to understand what is happening. In some cases, this cannot be possible because of the complexity of multisig schemes and because some schemes are indistinguishable from each other from the pure transaction point of view. + +An action metadata document MUST NOT override wallet-computed transaction shape. + +===Built-In Regular Actions=== + +This profile recognizes the following regular actions: + +{| +! Action +! Description +|- +| transfer +| A transfer of an existing asset. +|- +| issuance +| A issuance of a new Liquid asset. +|- +| re-issuance +| A re-issuance of a Liquid asset. +|- +| burn +| A burn of an existing Liquid asset. +|- +| unknown +| A non-Simplicity action that the wallet cannot classify. +|} + +===Action Metadata Fields=== + +{| +! Field +! Type +! Required +! Description +|- +| kind +| String +| Yes +| MUST be liquid.actionMetadata. +|- +| context.chainId +| String +| Yes +| The active Liquid or Elements chain ID. +|- +| context.actionType +| String +| Yes +| A built-in regular action identifier. +|- +| metadata.name +| String +| No +| The action display name. +|- +| metadata.description +| String +| No +| The action description. +|- +| display.intent +| String +| Yes +| A short action intent. +|- +| display.interpolatedIntent +| String +| No +| A short summary template. +|- +| display.fields +| Array +| No +| Display fields required for the action. +|} + +===Action Metadata Example=== + +
+{
+  "$schema": "https://example.invalid/liquid-clear-signing/action-metadata-v0.schema.json",
+  "kind": "liquid.actionMetadata",
+  "version": 0,
+  "context": {
+    "chainId": "bip122:1466275836220db2944ca059a3a10ef6",
+    "actionType": "transfer"
+  },
+  "metadata": {
+    "name": "Transfer",
+    "description": "A regular transfer of a Liquid asset."
+  },
+  "display": {
+    "intent": "Send",
+    "interpolatedIntent": "You send {to} {amount} {asset}",
+    "fields": [
+      { "path": "to", "label": "To", "format": "account" },
+      { "path": "amount", "label": "Amount", "format": "assetAmount" },
+      { "path": "asset", "label": "Asset", "format": "asset" }
+    ]
+  }
+}
+
+ +==Simplicity Covenant Metadata Document== + +A Simplicity covenant metadata document binds a Simplicity program, identified by its commitment Merkle root (CMR), to human-readable covenant metadata. + +A Simplicity tapleaf on an Elements chain uses leaf version 0xbe and a leaf script consisting of the 32-byte CMR of the Simplicity program. + +===Covenant Identity=== + +The covenant identity used by this document is the CMR. + +A wallet MUST compute the CMR itself from the Simplicity program bytes. A wallet MUST NOT trust a CMR claimed by an application, a transport layer, or a metadata registry. + +When the wallet has access to the taproot control block for a Simplicity input, +the wallet MUST verify that the output being spent commits to a tapleaf with leaf version 0xbe +and a leaf script equal to the computed CMR. + +A hardware signer MUST perform this verification on-device when the control block is available and MUST display the covenant as unverified when it is not. + +===Covenant Metadata Fields=== + +{| +! Field +! Type +! Required +! Description +|- +| kind +| String +| Yes +| MUST be liquid.covenantMetadata. +|- +| context.chainId +| String +| Yes +| The active Liquid or Elements chain ID. +|- +| context.cmr +| String +| Yes +| The covenant CMR as 64 lowercase hex characters. +|- +| context.contractId +| String +| No +| A 64 lowercase hex registry identifier for the covenant, when a registry binding exists. +|- +| metadata.name +| String +| No +| The covenant display name. +|- +| metadata.description +| String +| No +| A human-readable description of the covenant behavior. +|- +| metadata.compilerVersion +| String +| No +| The SimplicityHL compiler version that produces the program from the source. +|- +| metadata.sourceHash +| String +| No +| The SHA-256 of the flattened covenant source, as 64 lowercase hex characters. +|- +| metadata.manifestHash +| String +| No +| The SHA-256 of the canonical transaction manifest bytes describing this covenant's actions, as 64 lowercase hex characters. +|- +| metadata.actions +| Array +| No +| The covenant actions. Each action has a path (an array of Left and Right strings identifying the witness branch), a name, an optional description, and optional outputAnnotations (an array of { "outputIndex": n, "label": s } objects). +|- +| display.summaryLabel +| String +| No +| A short label for summary views. +|- +| display.intent +| String +| No +| A short covenant intent for the summary layer. +|- +| display.detailFields +| Array +| No +| A list of detailed fields to display. +|} + +===Covenant Metadata Example=== + +
+{
+  "$schema": "https://example.invalid/liquid-clear-signing/covenant-metadata-v0.schema.json",
+  "kind": "liquid.covenantMetadata",
+  "version": 0,
+  "context": {
+    "chainId": "bip122:1466275836220db2944ca059a3a10ef6",
+    "cmr": "f3cd4537d7ebb201732203195b30b549b8dc0c2c6257b3a0d53bedb08ea02874",
+    "contractId": "1111111111111111111111111111111111111111111111111111111111111111"
+  },
+  "metadata": {
+    "name": "Cash-settled option",
+    "description": "A covenant locking collateral until exercise, settlement, expiry, or cancellation.",
+    "compilerVersion": "0.5.0",
+    "sourceHash": "6a5e0d54c9f87e9c05bfa1cb3c33627628898e263861d980d9a5b1e6d1e08b25",
+    "actions": [
+      {
+        "path": ["Left", "Left"],
+        "name": "Create option",
+        "outputAnnotations": [
+          { "outputIndex": 2, "label": "Collateral locked to covenant" }
+        ]
+      },
+      {
+        "path": ["Left", "Right", "Left"],
+        "name": "Exercise option",
+        "description": "Burns option tokens and pays the settlement asset into the covenant."
+      }
+    ]
+  },
+  "display": {
+    "summaryLabel": "Cash-settled option",
+    "intent": "Interact with a cash-settled option covenant"
+  }
+}
+
+ +===Covenant Action Matching=== + +A wallet MAY match the covenant action from the revealed Simplicity witness branch when the witness is available. +A wallet that cannot prove the action from the witness MUST mark the action as unproven when displaying it. + +===Intent Verification and Transaction Manifests=== + +Clear signing exists to make the action the user believes they are approving match the transaction they actually sign. Displaying labels is not sufficient: the wallet SHOULD verify the transaction shape against a declared recipe of the intended action before presenting that action as the summary. + +A [https://github.com/ElementsProject/ELIPs/pull/41 transaction manifest] describes a protocol's actions as recipes: ordered input descriptors (covenant UTXO types or wallet inputs, with asset and amount constraints), ordered output descriptors (destinations, assets, amount formulas), and per-input witnesses. Because a Simplicity covenant address is fully determined by its source and compile parameters (P2TR(NUMS, tapleaf(0xbe, CMR))), a manifest binds human-readable action descriptions to on-chain scripts in a machine-checkable way. + +When a manifest is available for the covenant being spent, a wallet SHOULD: + +# Recompute the covenant scripts from the manifest's sources and compile parameters (or verify supplied program bytes against them by CMR), and verify the spent outputs and created outputs match the action's input and output descriptors, including required_index, asset and amount constraints. +# Display the action's description as the intent only after the recipe matches. A partial match MUST NOT be presented as a verified intent. + +A covenant metadata document MAY bind to a manifest through metadata.manifestHash (SHA-256 of the canonical manifest bytes), allowing metadata document trust (registration or attestation) to extend to the manifest content. + +===Raw Transaction Review=== + +A wallet MUST let the user verify the raw transaction independently of every interpretation layer. At minimum the raw review MUST expose, for every output: the full scriptPubKey (or the address derived from it), the full asset ID in hex, the amount in base units, and any OP_RETURN payload. For every Simplicity input the raw review MUST expose the device-computed CMR and the hash of the program bytes. Interpreted views (aliases, tickers, action labels, output annotations) supplement the raw data; they MUST NOT replace it or make it unreachable. + +===Verification Paths=== + +A wallet SHOULD implement as many independent verification paths as possible and display which of them succeeded. A wallet MUST NOT display data as trusted when no path succeeded. + +A wallet MUST display trust as provenance rather than a bare verdict: the user is shown WHO vouched for the data ("registered by you", "whitelisted by you", "attested by