{
  "tool_id": "art-594-tempo-mpp-voucher-receipt-verifier",
  "note": "golden_hash filled by `node golden-parity.test.mjs --update`. Vectors A/B/C's signing key is the well-known test private key 0x000...001, whose address (0x7e5f4552091a69125d5dfcb7b8c2659029395bdf) is a documented external constant used across Ethereum tooling (same key art-591's fixtures use) -- so a re-implementation can independently confirm the recovered address without trusting this kernel's own output. RFC 6979 deterministic-k signing means the (r,s) values are byte-for-byte reproducible from the same (digest, privkey) pair by any independent secp256k1 implementation. The Voucher(bytes32 channelId,uint96 cumulativeAmount) EIP-712 struct and \"TIP20 Channel Reserve\"/\"1\" domain match the primary TIP20ChannelReserve.sol source (VOUCHER_TYPEHASH, _NAME_HASH, _VERSION_HASH constants), verified live 2026-08-10.",
  "vectors": [
    {
      "name": "happy-path-voucher-valid-memo-valid-receipt-emitted",
      "policy_parameters": {
        "protocolVersion": "v2",
        "domainName": "TIP20 Channel Reserve",
        "domainVersion": "1",
        "domainChainId": "4217",
        "domainVerifyingContract": "0x1234567890123456789012345678901234567890",
        "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
        "cumulativeAmount": "1000",
        "r": "0xebf4fa27ba15333e85d176eb025f89fe05df0127b30aafbe0014054ea6182e8f",
        "s": "0x56b886bd4b7a4b6c7ce54e8a80b8758f650fe32a59d71a8d6fa738ca88d170a2",
        "yParity": 1,
        "channelPayer": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
        "channelSettled": "0",
        "channelDeposit": "10000",
        "channelCloseRequestedAt": "0",
        "memoValue": "0x0000000000000000000000000000000000000000000000000000000000000001",
        "memoExpectedReferenceHash": "0x0000000000000000000000000000000000000000000000000000000000000001",
        "challengeId": "chal-abc-123",
        "challengeRealm": "api.example.com",
        "challengeIntent": "session",
        "challengeAmount": "500",
        "challengeCurrency": "USDC",
        "challengeRecipient": "merchant-receiving-address-string",
        "challengeEscrowContract": "0x1234567890123456789012345678901234567890",
        "challengeChannelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
        "subjectExecutionHash": "sha256:deadbeef",
        "now": "2026-08-10T00:00:00Z"
      },
      "output_payload": {
        "voucher": {
          "verdict": "VOUCHER_VALID",
          "reasons": [],
          "digest": "0xcbf0ee887772173d20d9ae64d16e90598ae7f21931c29f87c01cb3aeb97d6e66",
          "recoveredSigner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
          "expectedSigner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
          "cumulativeAmount": "1000",
          "channelSettled": "0",
          "channelDeposit": "10000",
          "deltaAmount": "1000",
          "protocolVersion": "v2",
          "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
          "onChainSettleNote": null
        },
        "memo": {
          "verdict": "MEMO_VALID",
          "reasons": [],
          "value": "0x0000000000000000000000000000000000000000000000000000000000000001",
          "isZero": false,
          "referenceMatch": true,
          "decodesUtf8": null
        },
        "challenge": {
          "verdict": "CHALLENGE_WELLFORMED",
          "reasons": [],
          "wwwAuthenticate": {
            "id": "chal-abc-123",
            "realm": "api.example.com",
            "method": "tempo",
            "intent": "session",
            "request": {
              "amount": "500",
              "currency": "USDC",
              "recipient": "merchant-receiving-address-string",
              "methodDetails": {
                "escrowContract": "0x1234567890123456789012345678901234567890",
                "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
                "minVoucherDelta": null,
                "chainId": null
              }
            },
            "expires": null
          }
        },
        "receipt": {
          "verdict": "RECEIPT_EMITTED",
          "paymentReceipt": {
            "status": "success",
            "method": "tempo",
            "intent": "session",
            "timestamp": "2026-08-10T00:00:00Z",
            "reference": "0x1111111111111111111111111111111111111111111111111111111111111111",
            "challengeId": "chal-abc-123",
            "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
            "acceptedCumulative": "1000",
            "spent": "1000",
            "voucherDigest": "0xcbf0ee887772173d20d9ae64d16e90598ae7f21931c29f87c01cb3aeb97d6e66",
            "subjectExecutionHash": "sha256:deadbeef"
          }
        },
        "reasons": [],
        "scope_note": "Verifies a Tempo Machine Payments Protocol (MPP) session voucher offline (ecrecover, no RPC/database lookup), validates a TIP-20 32-byte memo structurally, and validates/renders a merchant-side HTTP 402 Payment challenge plus a Payment-Receipt-shaped binding to an optional subject execution_hash. Never holds a private key, never signs, never escrows third-party funds, never initiates a payment, never operates a live endpoint -- every field is caller-supplied and echoed, nothing is fetched or resolved independently. Sources: github.com/tempoxyz/mpp-specs (MIT/Apache-2) and github.com/tempoxyz/tempo tips/verify/src/TIP20ChannelReserve.sol (primary contract source for the Voucher typehash and ChannelState fields)."
      },
      "golden_hash": "6e5c721797badb8dde8a31f08ad2449561732b169572223b91df99a729be9f91"
    },
    {
      "name": "voucher-idempotent-retry-at-zero",
      "policy_parameters": {
        "protocolVersion": "v2",
        "domainName": "TIP20 Channel Reserve",
        "domainVersion": "1",
        "domainChainId": "4217",
        "domainVerifyingContract": "0x1234567890123456789012345678901234567890",
        "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
        "cumulativeAmount": "0",
        "r": "0x0f34387db7faf52ce6e396be4c5b04409621d0973974be2afd77e1a9f93c95b3",
        "s": "0x63e6215faae1a490f46fe9f4c2c2d3ebd9f90ecb7d46abd8f7233542d529f122",
        "yParity": 0,
        "channelPayer": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
        "channelSettled": "0",
        "channelDeposit": "10000",
        "channelCloseRequestedAt": "0"
      },
      "output_payload": {
        "voucher": {
          "verdict": "VOUCHER_IDEMPOTENT_RETRY",
          "reasons": [],
          "digest": "0x120f55f8a7882e910a610eee39804322550259090a4c5692100f216927c14752",
          "recoveredSigner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
          "expectedSigner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
          "cumulativeAmount": "0",
          "channelSettled": "0",
          "channelDeposit": "10000",
          "deltaAmount": "0",
          "protocolVersion": "v2",
          "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
          "onChainSettleNote": "Off-chain MPP session acceptance and on-chain TIP20ChannelReserve.settle() apply the SAME cumulativeAmount at two different layers with different strictness: settle() reverts AmountNotIncreasing when cumulativeAmount <= channel.settled (strict increase only), while the off-chain session server treats cumulativeAmount === settled as an idempotent retry (safe to accept, no double-charge). This node reports the off-chain verdict."
        },
        "memo": {
          "verdict": "MEMO_SKIPPED",
          "reasons": [],
          "value": null,
          "isZero": null,
          "referenceMatch": null,
          "decodesUtf8": null
        },
        "challenge": {
          "verdict": "CHALLENGE_INDETERMINATE",
          "reasons": [
            "challenge.id is required and must be non-empty -- this node never mints an authoritative id (that would require holding a server-side HMAC secret, out of scope by the red lines), the caller’s own server issues and binds it per draft-httpauth-payment-00",
            "challenge.realm is required",
            "challenge.intent must be \"session\" or \"charge\"",
            "challenge.amount is required",
            "challenge.currency is required",
            "challenge.recipient is required (a receiving address string -- never a private key; this node never holds or generates key material, and never becomes the merchant of record on the caller’s behalf)",
            "challenge.escrowContract is required and must be a 20-byte hex address (methodDetails.escrowContract per the Tempo session/charge method spec)"
          ],
          "wwwAuthenticate": null
        },
        "receipt": {
          "verdict": "RECEIPT_WITHHELD_CHALLENGE_INDETERMINATE",
          "paymentReceipt": null
        },
        "reasons": [
          "challenge.id is required and must be non-empty -- this node never mints an authoritative id (that would require holding a server-side HMAC secret, out of scope by the red lines), the caller’s own server issues and binds it per draft-httpauth-payment-00",
          "challenge.realm is required",
          "challenge.intent must be \"session\" or \"charge\"",
          "challenge.amount is required",
          "challenge.currency is required",
          "challenge.recipient is required (a receiving address string -- never a private key; this node never holds or generates key material, and never becomes the merchant of record on the caller’s behalf)",
          "challenge.escrowContract is required and must be a 20-byte hex address (methodDetails.escrowContract per the Tempo session/charge method spec)"
        ],
        "scope_note": "Verifies a Tempo Machine Payments Protocol (MPP) session voucher offline (ecrecover, no RPC/database lookup), validates a TIP-20 32-byte memo structurally, and validates/renders a merchant-side HTTP 402 Payment challenge plus a Payment-Receipt-shaped binding to an optional subject execution_hash. Never holds a private key, never signs, never escrows third-party funds, never initiates a payment, never operates a live endpoint -- every field is caller-supplied and echoed, nothing is fetched or resolved independently. Sources: github.com/tempoxyz/mpp-specs (MIT/Apache-2) and github.com/tempoxyz/tempo tips/verify/src/TIP20ChannelReserve.sol (primary contract source for the Voucher typehash and ChannelState fields)."
      },
      "golden_hash": "1b9f17ac140a5526f5b7933b52213e81a0c897527c6864c2a59f2671d460583e"
    },
    {
      "name": "all-empty-voucher-skipped-challenge-indeterminate",
      "policy_parameters": {},
      "output_payload": {
        "voucher": {
          "verdict": "VOUCHER_INDETERMINATE",
          "reasons": [
            "protocolVersion is required and must be \"v1\" (legacy \"Tempo Stream Channel\" domain, uint128 amount) or \"v2\" (the shipped \"TIP20 Channel Reserve\" reference contract, uint96 amount) -- never guessed",
            "domainName is required (EIP-712 domain field, never defaulted -- e.g. \"TIP20 Channel Reserve\" for v2)",
            "domainVersion is required (EIP-712 domain field, never defaulted)",
            "domainChainId is required and must be a non-negative integer (EIP-712 domain field, never defaulted)",
            "domainVerifyingContract is required and must be a 20-byte hex address (the deployed channel-reserve contract -- a guessed value defeats domain separation)",
            "channelId is required and must be a 32-byte hex value",
            "cumulativeAmount is required and must be a non-negative integer fitting uint96 (width depends on protocolVersion)",
            "channelSettled is required (the channel's on-file settled/highest-accepted cumulative amount, uint96 per ITIP20ChannelReserve.sol ChannelState -- supply \"0\" for a fresh channel)",
            "channelDeposit is required (the channel's funded deposit cap, uint96)",
            "channelCloseRequestedAt is required (uint32 timestamp; supply \"0\" for an open channel)",
            "either channelAuthorizedSigner or channelPayer is required, to know which address a voucher must be signed by",
            "r is required and must be a 32-byte hex value when signature is not supplied",
            "s is required and must be a 32-byte hex value when signature is not supplied"
          ],
          "digest": null,
          "recoveredSigner": null,
          "expectedSigner": null,
          "cumulativeAmount": null,
          "channelSettled": null,
          "channelDeposit": null,
          "deltaAmount": null,
          "protocolVersion": null,
          "channelId": null,
          "onChainSettleNote": null
        },
        "memo": {
          "verdict": "MEMO_SKIPPED",
          "reasons": [],
          "value": null,
          "isZero": null,
          "referenceMatch": null,
          "decodesUtf8": null
        },
        "challenge": {
          "verdict": "CHALLENGE_INDETERMINATE",
          "reasons": [
            "challenge.id is required and must be non-empty -- this node never mints an authoritative id (that would require holding a server-side HMAC secret, out of scope by the red lines), the caller’s own server issues and binds it per draft-httpauth-payment-00",
            "challenge.realm is required",
            "challenge.intent must be \"session\" or \"charge\"",
            "challenge.amount is required",
            "challenge.currency is required",
            "challenge.recipient is required (a receiving address string -- never a private key; this node never holds or generates key material, and never becomes the merchant of record on the caller’s behalf)",
            "challenge.escrowContract is required and must be a 20-byte hex address (methodDetails.escrowContract per the Tempo session/charge method spec)"
          ],
          "wwwAuthenticate": null
        },
        "receipt": {
          "verdict": "RECEIPT_WITHHELD_CHALLENGE_INDETERMINATE",
          "paymentReceipt": null
        },
        "reasons": [
          "protocolVersion is required and must be \"v1\" (legacy \"Tempo Stream Channel\" domain, uint128 amount) or \"v2\" (the shipped \"TIP20 Channel Reserve\" reference contract, uint96 amount) -- never guessed",
          "domainName is required (EIP-712 domain field, never defaulted -- e.g. \"TIP20 Channel Reserve\" for v2)",
          "domainVersion is required (EIP-712 domain field, never defaulted)",
          "domainChainId is required and must be a non-negative integer (EIP-712 domain field, never defaulted)",
          "domainVerifyingContract is required and must be a 20-byte hex address (the deployed channel-reserve contract -- a guessed value defeats domain separation)",
          "channelId is required and must be a 32-byte hex value",
          "cumulativeAmount is required and must be a non-negative integer fitting uint96 (width depends on protocolVersion)",
          "channelSettled is required (the channel's on-file settled/highest-accepted cumulative amount, uint96 per ITIP20ChannelReserve.sol ChannelState -- supply \"0\" for a fresh channel)",
          "channelDeposit is required (the channel's funded deposit cap, uint96)",
          "channelCloseRequestedAt is required (uint32 timestamp; supply \"0\" for an open channel)",
          "either channelAuthorizedSigner or channelPayer is required, to know which address a voucher must be signed by",
          "r is required and must be a 32-byte hex value when signature is not supplied",
          "s is required and must be a 32-byte hex value when signature is not supplied",
          "challenge.id is required and must be non-empty -- this node never mints an authoritative id (that would require holding a server-side HMAC secret, out of scope by the red lines), the caller’s own server issues and binds it per draft-httpauth-payment-00",
          "challenge.realm is required",
          "challenge.intent must be \"session\" or \"charge\"",
          "challenge.amount is required",
          "challenge.currency is required",
          "challenge.recipient is required (a receiving address string -- never a private key; this node never holds or generates key material, and never becomes the merchant of record on the caller’s behalf)",
          "challenge.escrowContract is required and must be a 20-byte hex address (methodDetails.escrowContract per the Tempo session/charge method spec)"
        ],
        "scope_note": "Verifies a Tempo Machine Payments Protocol (MPP) session voucher offline (ecrecover, no RPC/database lookup), validates a TIP-20 32-byte memo structurally, and validates/renders a merchant-side HTTP 402 Payment challenge plus a Payment-Receipt-shaped binding to an optional subject execution_hash. Never holds a private key, never signs, never escrows third-party funds, never initiates a payment, never operates a live endpoint -- every field is caller-supplied and echoed, nothing is fetched or resolved independently. Sources: github.com/tempoxyz/mpp-specs (MIT/Apache-2) and github.com/tempoxyz/tempo tips/verify/src/TIP20ChannelReserve.sol (primary contract source for the Voucher typehash and ChannelState fields)."
      },
      "golden_hash": "83f0adf9f4345c239f3ff559e461f4a6d4b99b4938b43edcfe71d3aa2bfdb86b"
    },
    {
      "name": "voucher-missing-required-fields-indeterminate",
      "policy_parameters": {
        "protocolVersion": "v2",
        "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
        "cumulativeAmount": "5"
      },
      "output_payload": {
        "voucher": {
          "verdict": "VOUCHER_INDETERMINATE",
          "reasons": [
            "domainName is required (EIP-712 domain field, never defaulted -- e.g. \"TIP20 Channel Reserve\" for v2)",
            "domainVersion is required (EIP-712 domain field, never defaulted)",
            "domainChainId is required and must be a non-negative integer (EIP-712 domain field, never defaulted)",
            "domainVerifyingContract is required and must be a 20-byte hex address (the deployed channel-reserve contract -- a guessed value defeats domain separation)",
            "channelSettled is required (the channel's on-file settled/highest-accepted cumulative amount, uint96 per ITIP20ChannelReserve.sol ChannelState -- supply \"0\" for a fresh channel)",
            "channelDeposit is required (the channel's funded deposit cap, uint96)",
            "channelCloseRequestedAt is required (uint32 timestamp; supply \"0\" for an open channel)",
            "either channelAuthorizedSigner or channelPayer is required, to know which address a voucher must be signed by",
            "r is required and must be a 32-byte hex value when signature is not supplied",
            "s is required and must be a 32-byte hex value when signature is not supplied"
          ],
          "digest": null,
          "recoveredSigner": null,
          "expectedSigner": null,
          "cumulativeAmount": "5",
          "channelSettled": null,
          "channelDeposit": null,
          "deltaAmount": null,
          "protocolVersion": "v2",
          "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
          "onChainSettleNote": null
        },
        "memo": {
          "verdict": "MEMO_SKIPPED",
          "reasons": [],
          "value": null,
          "isZero": null,
          "referenceMatch": null,
          "decodesUtf8": null
        },
        "challenge": {
          "verdict": "CHALLENGE_INDETERMINATE",
          "reasons": [
            "challenge.id is required and must be non-empty -- this node never mints an authoritative id (that would require holding a server-side HMAC secret, out of scope by the red lines), the caller’s own server issues and binds it per draft-httpauth-payment-00",
            "challenge.realm is required",
            "challenge.intent must be \"session\" or \"charge\"",
            "challenge.amount is required",
            "challenge.currency is required",
            "challenge.recipient is required (a receiving address string -- never a private key; this node never holds or generates key material, and never becomes the merchant of record on the caller’s behalf)",
            "challenge.escrowContract is required and must be a 20-byte hex address (methodDetails.escrowContract per the Tempo session/charge method spec)"
          ],
          "wwwAuthenticate": null
        },
        "receipt": {
          "verdict": "RECEIPT_WITHHELD_CHALLENGE_INDETERMINATE",
          "paymentReceipt": null
        },
        "reasons": [
          "domainName is required (EIP-712 domain field, never defaulted -- e.g. \"TIP20 Channel Reserve\" for v2)",
          "domainVersion is required (EIP-712 domain field, never defaulted)",
          "domainChainId is required and must be a non-negative integer (EIP-712 domain field, never defaulted)",
          "domainVerifyingContract is required and must be a 20-byte hex address (the deployed channel-reserve contract -- a guessed value defeats domain separation)",
          "channelSettled is required (the channel's on-file settled/highest-accepted cumulative amount, uint96 per ITIP20ChannelReserve.sol ChannelState -- supply \"0\" for a fresh channel)",
          "channelDeposit is required (the channel's funded deposit cap, uint96)",
          "channelCloseRequestedAt is required (uint32 timestamp; supply \"0\" for an open channel)",
          "either channelAuthorizedSigner or channelPayer is required, to know which address a voucher must be signed by",
          "r is required and must be a 32-byte hex value when signature is not supplied",
          "s is required and must be a 32-byte hex value when signature is not supplied",
          "challenge.id is required and must be non-empty -- this node never mints an authoritative id (that would require holding a server-side HMAC secret, out of scope by the red lines), the caller’s own server issues and binds it per draft-httpauth-payment-00",
          "challenge.realm is required",
          "challenge.intent must be \"session\" or \"charge\"",
          "challenge.amount is required",
          "challenge.currency is required",
          "challenge.recipient is required (a receiving address string -- never a private key; this node never holds or generates key material, and never becomes the merchant of record on the caller’s behalf)",
          "challenge.escrowContract is required and must be a 20-byte hex address (methodDetails.escrowContract per the Tempo session/charge method spec)"
        ],
        "scope_note": "Verifies a Tempo Machine Payments Protocol (MPP) session voucher offline (ecrecover, no RPC/database lookup), validates a TIP-20 32-byte memo structurally, and validates/renders a merchant-side HTTP 402 Payment challenge plus a Payment-Receipt-shaped binding to an optional subject execution_hash. Never holds a private key, never signs, never escrows third-party funds, never initiates a payment, never operates a live endpoint -- every field is caller-supplied and echoed, nothing is fetched or resolved independently. Sources: github.com/tempoxyz/mpp-specs (MIT/Apache-2) and github.com/tempoxyz/tempo tips/verify/src/TIP20ChannelReserve.sol (primary contract source for the Voucher typehash and ChannelState fields)."
      },
      "golden_hash": "76faafce2f8397b19311451fa732b3ec94ab6c6da53bb79631530beb7e6b6d6d"
    },
    {
      "name": "memo-malformed-wrong-length",
      "policy_parameters": {
        "memoValue": "0xdeadbeef"
      },
      "output_payload": {
        "voucher": {
          "verdict": "VOUCHER_INDETERMINATE",
          "reasons": [
            "protocolVersion is required and must be \"v1\" (legacy \"Tempo Stream Channel\" domain, uint128 amount) or \"v2\" (the shipped \"TIP20 Channel Reserve\" reference contract, uint96 amount) -- never guessed",
            "domainName is required (EIP-712 domain field, never defaulted -- e.g. \"TIP20 Channel Reserve\" for v2)",
            "domainVersion is required (EIP-712 domain field, never defaulted)",
            "domainChainId is required and must be a non-negative integer (EIP-712 domain field, never defaulted)",
            "domainVerifyingContract is required and must be a 20-byte hex address (the deployed channel-reserve contract -- a guessed value defeats domain separation)",
            "channelId is required and must be a 32-byte hex value",
            "cumulativeAmount is required and must be a non-negative integer fitting uint96 (width depends on protocolVersion)",
            "channelSettled is required (the channel's on-file settled/highest-accepted cumulative amount, uint96 per ITIP20ChannelReserve.sol ChannelState -- supply \"0\" for a fresh channel)",
            "channelDeposit is required (the channel's funded deposit cap, uint96)",
            "channelCloseRequestedAt is required (uint32 timestamp; supply \"0\" for an open channel)",
            "either channelAuthorizedSigner or channelPayer is required, to know which address a voucher must be signed by",
            "r is required and must be a 32-byte hex value when signature is not supplied",
            "s is required and must be a 32-byte hex value when signature is not supplied"
          ],
          "digest": null,
          "recoveredSigner": null,
          "expectedSigner": null,
          "cumulativeAmount": null,
          "channelSettled": null,
          "channelDeposit": null,
          "deltaAmount": null,
          "protocolVersion": null,
          "channelId": null,
          "onChainSettleNote": null
        },
        "memo": {
          "verdict": "MEMO_MALFORMED",
          "reasons": [
            "memo.value must be a 32-byte (bytes32) hex value per TIP-20’s fixed memo field"
          ],
          "value": "0xdeadbeef",
          "isZero": null,
          "referenceMatch": null,
          "decodesUtf8": null
        },
        "challenge": {
          "verdict": "CHALLENGE_INDETERMINATE",
          "reasons": [
            "challenge.id is required and must be non-empty -- this node never mints an authoritative id (that would require holding a server-side HMAC secret, out of scope by the red lines), the caller’s own server issues and binds it per draft-httpauth-payment-00",
            "challenge.realm is required",
            "challenge.intent must be \"session\" or \"charge\"",
            "challenge.amount is required",
            "challenge.currency is required",
            "challenge.recipient is required (a receiving address string -- never a private key; this node never holds or generates key material, and never becomes the merchant of record on the caller’s behalf)",
            "challenge.escrowContract is required and must be a 20-byte hex address (methodDetails.escrowContract per the Tempo session/charge method spec)"
          ],
          "wwwAuthenticate": null
        },
        "receipt": {
          "verdict": "RECEIPT_WITHHELD_CHALLENGE_INDETERMINATE",
          "paymentReceipt": null
        },
        "reasons": [
          "protocolVersion is required and must be \"v1\" (legacy \"Tempo Stream Channel\" domain, uint128 amount) or \"v2\" (the shipped \"TIP20 Channel Reserve\" reference contract, uint96 amount) -- never guessed",
          "domainName is required (EIP-712 domain field, never defaulted -- e.g. \"TIP20 Channel Reserve\" for v2)",
          "domainVersion is required (EIP-712 domain field, never defaulted)",
          "domainChainId is required and must be a non-negative integer (EIP-712 domain field, never defaulted)",
          "domainVerifyingContract is required and must be a 20-byte hex address (the deployed channel-reserve contract -- a guessed value defeats domain separation)",
          "channelId is required and must be a 32-byte hex value",
          "cumulativeAmount is required and must be a non-negative integer fitting uint96 (width depends on protocolVersion)",
          "channelSettled is required (the channel's on-file settled/highest-accepted cumulative amount, uint96 per ITIP20ChannelReserve.sol ChannelState -- supply \"0\" for a fresh channel)",
          "channelDeposit is required (the channel's funded deposit cap, uint96)",
          "channelCloseRequestedAt is required (uint32 timestamp; supply \"0\" for an open channel)",
          "either channelAuthorizedSigner or channelPayer is required, to know which address a voucher must be signed by",
          "r is required and must be a 32-byte hex value when signature is not supplied",
          "s is required and must be a 32-byte hex value when signature is not supplied",
          "memo.value must be a 32-byte (bytes32) hex value per TIP-20’s fixed memo field",
          "challenge.id is required and must be non-empty -- this node never mints an authoritative id (that would require holding a server-side HMAC secret, out of scope by the red lines), the caller’s own server issues and binds it per draft-httpauth-payment-00",
          "challenge.realm is required",
          "challenge.intent must be \"session\" or \"charge\"",
          "challenge.amount is required",
          "challenge.currency is required",
          "challenge.recipient is required (a receiving address string -- never a private key; this node never holds or generates key material, and never becomes the merchant of record on the caller’s behalf)",
          "challenge.escrowContract is required and must be a 20-byte hex address (methodDetails.escrowContract per the Tempo session/charge method spec)"
        ],
        "scope_note": "Verifies a Tempo Machine Payments Protocol (MPP) session voucher offline (ecrecover, no RPC/database lookup), validates a TIP-20 32-byte memo structurally, and validates/renders a merchant-side HTTP 402 Payment challenge plus a Payment-Receipt-shaped binding to an optional subject execution_hash. Never holds a private key, never signs, never escrows third-party funds, never initiates a payment, never operates a live endpoint -- every field is caller-supplied and echoed, nothing is fetched or resolved independently. Sources: github.com/tempoxyz/mpp-specs (MIT/Apache-2) and github.com/tempoxyz/tempo tips/verify/src/TIP20ChannelReserve.sol (primary contract source for the Voucher typehash and ChannelState fields)."
      },
      "golden_hash": "801847e4761088a43d036979c76dcfe8ddfa77eb4363802422983953bab1f358"
    },
    {
      "name": "voucher-exceeds-deposit-cap",
      "policy_parameters": {
        "protocolVersion": "v2",
        "domainName": "TIP20 Channel Reserve",
        "domainVersion": "1",
        "domainChainId": "4217",
        "domainVerifyingContract": "0x1234567890123456789012345678901234567890",
        "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
        "cumulativeAmount": "99999",
        "r": "0x949f532f3dcfe3edff4cab43ce3e37fe605357e2e4fdb26b229ff36d3b907705",
        "s": "0x4ee5e46e5d1e443efb4f1fa5edd55359e2a8da00d31b6c5826363e9bdc3e20c5",
        "yParity": 1,
        "channelPayer": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
        "channelSettled": "0",
        "channelDeposit": "1000",
        "channelCloseRequestedAt": "0"
      },
      "output_payload": {
        "voucher": {
          "verdict": "VOUCHER_EXCEEDS_DEPOSIT",
          "reasons": [
            "cumulativeAmount exceeds channelDeposit -- the channel is not funded to cover this voucher"
          ],
          "digest": "0x2f83423c52ac822f61110e1315b05b948a52c76bd1373ef48509555bfe47db38",
          "recoveredSigner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
          "expectedSigner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
          "cumulativeAmount": "99999",
          "channelSettled": "0",
          "channelDeposit": "1000",
          "deltaAmount": "99999",
          "protocolVersion": "v2",
          "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
          "onChainSettleNote": null
        },
        "memo": {
          "verdict": "MEMO_SKIPPED",
          "reasons": [],
          "value": null,
          "isZero": null,
          "referenceMatch": null,
          "decodesUtf8": null
        },
        "challenge": {
          "verdict": "CHALLENGE_INDETERMINATE",
          "reasons": [
            "challenge.id is required and must be non-empty -- this node never mints an authoritative id (that would require holding a server-side HMAC secret, out of scope by the red lines), the caller’s own server issues and binds it per draft-httpauth-payment-00",
            "challenge.realm is required",
            "challenge.intent must be \"session\" or \"charge\"",
            "challenge.amount is required",
            "challenge.currency is required",
            "challenge.recipient is required (a receiving address string -- never a private key; this node never holds or generates key material, and never becomes the merchant of record on the caller’s behalf)",
            "challenge.escrowContract is required and must be a 20-byte hex address (methodDetails.escrowContract per the Tempo session/charge method spec)"
          ],
          "wwwAuthenticate": null
        },
        "receipt": {
          "verdict": "RECEIPT_WITHHELD_CHALLENGE_INDETERMINATE",
          "paymentReceipt": null
        },
        "reasons": [
          "cumulativeAmount exceeds channelDeposit -- the channel is not funded to cover this voucher",
          "challenge.id is required and must be non-empty -- this node never mints an authoritative id (that would require holding a server-side HMAC secret, out of scope by the red lines), the caller’s own server issues and binds it per draft-httpauth-payment-00",
          "challenge.realm is required",
          "challenge.intent must be \"session\" or \"charge\"",
          "challenge.amount is required",
          "challenge.currency is required",
          "challenge.recipient is required (a receiving address string -- never a private key; this node never holds or generates key material, and never becomes the merchant of record on the caller’s behalf)",
          "challenge.escrowContract is required and must be a 20-byte hex address (methodDetails.escrowContract per the Tempo session/charge method spec)"
        ],
        "scope_note": "Verifies a Tempo Machine Payments Protocol (MPP) session voucher offline (ecrecover, no RPC/database lookup), validates a TIP-20 32-byte memo structurally, and validates/renders a merchant-side HTTP 402 Payment challenge plus a Payment-Receipt-shaped binding to an optional subject execution_hash. Never holds a private key, never signs, never escrows third-party funds, never initiates a payment, never operates a live endpoint -- every field is caller-supplied and echoed, nothing is fetched or resolved independently. Sources: github.com/tempoxyz/mpp-specs (MIT/Apache-2) and github.com/tempoxyz/tempo tips/verify/src/TIP20ChannelReserve.sol (primary contract source for the Voucher typehash and ChannelState fields)."
      },
      "golden_hash": "9f649f962d12a318a6f05ba0730d828b8c0012ddf48e456171d83cc732c1078e"
    },
    {
      "name": "memo-utf8-encoding-check-valid-ascii",
      "policy_parameters": {
        "protocolVersion": "v2",
        "domainName": "TIP20 Channel Reserve",
        "domainVersion": "1",
        "domainChainId": "4217",
        "domainVerifyingContract": "0x1234567890123456789012345678901234567890",
        "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
        "cumulativeAmount": "1000",
        "r": "0xebf4fa27ba15333e85d176eb025f89fe05df0127b30aafbe0014054ea6182e8f",
        "s": "0x56b886bd4b7a4b6c7ce54e8a80b8758f650fe32a59d71a8d6fa738ca88d170a2",
        "yParity": 1,
        "channelPayer": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
        "channelSettled": "0",
        "channelDeposit": "10000",
        "channelCloseRequestedAt": "0",
        "memoValue": "0x4141414141414141414141414141414141414141414141414141414141414141",
        "memoExpectedReferenceHash": null,
        "challengeId": "chal-abc-123",
        "challengeRealm": "api.example.com",
        "challengeIntent": "session",
        "challengeAmount": "500",
        "challengeCurrency": "USDC",
        "challengeRecipient": "merchant-receiving-address-string",
        "challengeEscrowContract": "0x1234567890123456789012345678901234567890",
        "challengeChannelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
        "subjectExecutionHash": "sha256:deadbeef",
        "now": "2026-08-10T00:00:00Z",
        "memoExpectedEncoding": "utf8"
      },
      "output_payload": {
        "voucher": {
          "verdict": "VOUCHER_VALID",
          "reasons": [],
          "digest": "0xcbf0ee887772173d20d9ae64d16e90598ae7f21931c29f87c01cb3aeb97d6e66",
          "recoveredSigner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
          "expectedSigner": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
          "cumulativeAmount": "1000",
          "channelSettled": "0",
          "channelDeposit": "10000",
          "deltaAmount": "1000",
          "protocolVersion": "v2",
          "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
          "onChainSettleNote": null
        },
        "memo": {
          "verdict": "MEMO_VALID",
          "reasons": [],
          "value": "0x4141414141414141414141414141414141414141414141414141414141414141",
          "isZero": false,
          "referenceMatch": null,
          "decodesUtf8": true
        },
        "challenge": {
          "verdict": "CHALLENGE_WELLFORMED",
          "reasons": [],
          "wwwAuthenticate": {
            "id": "chal-abc-123",
            "realm": "api.example.com",
            "method": "tempo",
            "intent": "session",
            "request": {
              "amount": "500",
              "currency": "USDC",
              "recipient": "merchant-receiving-address-string",
              "methodDetails": {
                "escrowContract": "0x1234567890123456789012345678901234567890",
                "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
                "minVoucherDelta": null,
                "chainId": null
              }
            },
            "expires": null
          }
        },
        "receipt": {
          "verdict": "RECEIPT_EMITTED",
          "paymentReceipt": {
            "status": "success",
            "method": "tempo",
            "intent": "session",
            "timestamp": "2026-08-10T00:00:00Z",
            "reference": "0x1111111111111111111111111111111111111111111111111111111111111111",
            "challengeId": "chal-abc-123",
            "channelId": "0x1111111111111111111111111111111111111111111111111111111111111111",
            "acceptedCumulative": "1000",
            "spent": "1000",
            "voucherDigest": "0xcbf0ee887772173d20d9ae64d16e90598ae7f21931c29f87c01cb3aeb97d6e66",
            "subjectExecutionHash": "sha256:deadbeef"
          }
        },
        "reasons": [],
        "scope_note": "Verifies a Tempo Machine Payments Protocol (MPP) session voucher offline (ecrecover, no RPC/database lookup), validates a TIP-20 32-byte memo structurally, and validates/renders a merchant-side HTTP 402 Payment challenge plus a Payment-Receipt-shaped binding to an optional subject execution_hash. Never holds a private key, never signs, never escrows third-party funds, never initiates a payment, never operates a live endpoint -- every field is caller-supplied and echoed, nothing is fetched or resolved independently. Sources: github.com/tempoxyz/mpp-specs (MIT/Apache-2) and github.com/tempoxyz/tempo tips/verify/src/TIP20ChannelReserve.sol (primary contract source for the Voucher typehash and ChannelState fields)."
      },
      "golden_hash": "87356e8783a20ecec2fa456d2c224d67abfb9f90dcc7ccd89be336aef0a72c42"
    }
  ]
}
