diff --git a/wai/.gitattributes b/wai/.gitattributes index 46c43ac3d..5db763d23 100644 --- a/wai/.gitattributes +++ b/wai/.gitattributes @@ -7,6 +7,10 @@ REGISTRY.md -text *.wih1 -text *.wia1 -text *.wis1 -text +# The live-ads corpus's supply files (extensions/live-ads.md §8.7, §11): sellers files and +# authorised-sellers files a verifier finds by their SHA-256, some with CR or CR LF line ends +# and one not UTF-8, on purpose: never converted. +live-conformance/ads/supply.* -text # The splat corpus (extensions/splat-exact.md §11): glTF assets, their buffers and their # commitments, regenerated and compared byte for byte, so never converted. live-conformance/splat/** -text diff --git a/wai/changes/2026-11-04-ad-decision-consent-signal-supply-chain.md b/wai/changes/2026-11-04-ad-decision-consent-signal-supply-chain.md new file mode 100644 index 000000000..8a79fe594 --- /dev/null +++ b/wai/changes/2026-11-04-ad-decision-consent-signal-supply-chain.md @@ -0,0 +1,79 @@ +### An ad decision's consent signal and supply chain, bound into what the decider signs + +- **The consent signal** ([live-ads](extensions/live-ads.md) §8.6): a break-form `wai.video.ssai` receipt of version 2 + states the consent signal its decision was made under: the Global Privacy Platform string the player gave with its + request for the asset list, byte for byte, the sections that apply, and a child-directed flag: three of the members + OpenRTB 2.6 gives an ad request's `Regs`. Its rules hold the string to the URL-safe characters the GPP string's text + lists, with its header and every section present, and the sections to the string. The section says what each party + does with the signal: the player gives it as it holds it and never writes or alters a string; the decider states it + byte for byte, passes it unchanged in every request it makes to fill the break, and marks every such request as + directed to children when the policy or the request does; no party reads consent from an empty string or age from a + flag of 0, and a decider or a verifier does not use either to identify a viewer or link one viewer's receipts; and a + verifier does not decode the string's sections, so a receipt that verifies shows what the decider stated it was given + and passed on, not that the viewer gave it or that any party honoured it. +- **A GPP string links what carries it** (live-ads §12, [stitched-presentation](extensions/stitched-presentation.md) + §11): a consent tool makes the string for a viewer, not for one request, and it can stay the same across the viewer's + sessions and rooms, so a receipt, or a stitched record of one session, whose string is not empty can be linked through + it to every other that carries the same string, in any room, by whoever holds them. The string is in the signed bytes, + so it cannot be stripped from a receipt that stays verifiable. The rule against using the string to link binds the + decider and the verifier only. The cross-room property of a receipt's session holds of receipts that carry no + string. +- **The supply chain of each impression** (§8.7): the receipt also states, in `supply_chains`, one entry for each + impression: the `SupplyChain` object (OpenRTB 2.6 §3.2.25 and §3.2.26) of the request whose answer gave the decider + that impression's candidate, or `null`. A pod's candidates can answer several requests, each with its own chain. A + chain gives each node's advertising system domain, seller id, payment flag and optional members, with the SHA-256 of + each system's sellers file (*Sellers.json* 1.0) and of the inventory owner's authorised-sellers file (*ads.txt + Specification* 1.1, or *app-ads.txt* 1.0 for an app). A verifier that holds those files checks each node against + them: listed under its seller id in its system's sellers file, and authorised in the owner's file, `DIRECT` for the + first node of a complete chain and `DIRECT` or `RESELLER` for the others; a part whose file it does not hold is + stated unchecked. WAI carries the published object and its references, and defines no supply-chain scheme of its + own. +- **How the files read** (§8.7): an authorised-sellers file by the stated reading of *ads.txt* 1.1 §3.2 to §3.5 (line + ends, comments, variable records, extension fields, three or four fields). A sellers file is UTF-8 with no byte order + mark and a JSON text within limits RFC 8259 leaves to a reader: its arrays and objects nest at most 64 deep, the + members read (`sellers`, `seller_id`, `seller_type`) are not given twice, and the member names and strings read + escape no lone surrogate. The rest of the file is read by the grammar alone, its numbers never converted, so a number + of any size reads. +- **Signed.** A receipt of version 2 signs under `wai:video-ssai-break-receipt` 0x02, with the consent signal and the + chains, in the impressions' order, between its impressions and its signer (§8.4), so a receipt whose string, + sections, flag or chains changed after signing, or whose chains were moved to other impressions, fails its signature. + A receipt of version 1 keeps its bytes and its identity, and reads as before. +- **A policy of version 3** (§3.1): the presentation fields of version 2, then an `audience` whose bit 0, **child**, + marks the room as directed to children. Under it every receipt is of version 2 (`request_terms_absent`), and + child-directed when the policy says so (`child_directed_unset`) (§9.3 step 13). Step 14 checks each chain against + the supply files the verifier holds (`supply_file_malformed`, `supply_seller_unlisted`, `supply_seller_unauthorised`), + and a receipt's own rules gain `consent_invalid` and `supply_chain_invalid` (§8.5 rule 6). + [ad-presentation](extensions/ad-presentation.md) §8 says that a policy of version 3 carries its presentation fields. +- **The stitched pod-input record** ([stitched-presentation](extensions/stitched-presentation.md) §4.7): `WSI1` of + version 2 carries the same consent signal after its ads, held to live-ads §8.6's rules, the signer in the decider's + place; a shared record, which every viewer is shown, carries no viewer's string. A verification states each accepted + record's signal. `wai_stitched_presentation sign-pod` writes a record of version 2 from a draft's `consent`. +- **The corpora.** [`live-conformance/ads/`](live-conformance/ads/) gains a room under a policy of version 3 whose + receipts state a GPP string with two ads sold through two complete chains checked against two sellers files and an + app-ads.txt, and no string with a child-directed request through an incomplete chain left unchecked; a room directed + to children; a chain two impressions share, authorised by an ads.txt only when each of §8.7's reading rules is + applied; a sellers file that reads only by the grammar outside what §8.7 reads; four receipts whose string, flag, + chain or order of chains was changed after signing, each refused `bad_signature`; and a control for each rule of + §8.2's carriage of version 2, of §8.6 and of §8.7, and for each way a supply file fails to read or to list or + authorise a node. The supply files are files of the corpus, `supply.*`, read as bytes and kept from line-ending + conversion; one is not UTF-8. + [`live-conformance/stitched/`](live-conformance/stitched/) gains `consent`: two records of version 2, one whose GPP + string was changed after signing (`bad_signature`), a shared record carrying a viewer's string and a record stating + a section with no string (each `pod_input_malformed`). Every earlier vector keeps its bytes. `live_ads_verify.py` + and `stitched_presentation_verify.py` read the request terms, the sellers files and the authorised-sellers files + themselves. +- **Breaking:** `api:rust:wai::live_ads::corpus::generate` and `api:rust:wai::live_ads::corpus::verify` give and take + each file of the corpus as bytes, since a supply file need not be UTF-8. +- **Breaking:** new members of public structs a literal could build: `api:rust:wai::wai_video_ssai::SsaiBreakReceipt::terms` + (`None` for a receipt of version 1; `SsaiBreakReceipt::seal` keeps its signature and seals one, and + `seal_with_terms` seals a receipt of version 2), `api:rust:wai::live_ads::AdPolicy::audience`, + `api:rust:wai::live_ads::Input::supply_files`, `api:rust:wai::live_ads::ReceiptFacts::terms`, + `api:rust:wai::stitched_presentation::PodInput::consent` and + `api:rust:wai::stitched_presentation::RecordFacts::consent`. +- **Breaking:** the variants an exhaustive match needs: `api:rust:wai::wai_video_ssai::BreakReceiptFault::ConsentInvalid` + and `api:rust:wai::wai_video_ssai::BreakReceiptFault::SupplyChainInvalid`; the reasons live-ads §10 adds, + `api:rust:wai::live_ads::Reason::ConsentInvalid`, `api:rust:wai::live_ads::Reason::SupplyChainInvalid`, + `api:rust:wai::live_ads::Reason::RequestTermsAbsent`, `api:rust:wai::live_ads::Reason::ChildDirectedUnset`, + `api:rust:wai::live_ads::Reason::SupplyFileMalformed`, `api:rust:wai::live_ads::Reason::SupplySellerUnlisted` and + `api:rust:wai::live_ads::Reason::SupplySellerUnauthorised`; and `api:rust:wai::live_ads::Reason::ALL`, which holds + them, an array of 27. diff --git a/wai/extensions/ad-presentation.md b/wai/extensions/ad-presentation.md index 69d9c1046..772407a53 100644 --- a/wai/extensions/ad-presentation.md +++ b/wai/extensions/ad-presentation.md @@ -356,6 +356,9 @@ or 0 with **claim** set, or not 0 with **claim** clear; or, with **attest** set, than 0 or an `issuer` that is not empty. A policy of version 2 with `presentation` 0 is verified as a policy of version 1. +A policy of version 3 (live-ads §3.1) carries the same four fields, under the same rules, after +`creative_base` and before its `audience`; with `presentation` 0 it requires no claim. + ## 9. Verification ### 9.1 Inputs diff --git a/wai/extensions/live-ads.md b/wai/extensions/live-ads.md index 1a3609b94..b6bbe60a2 100644 --- a/wai/extensions/live-ads.md +++ b/wai/extensions/live-ads.md @@ -30,6 +30,26 @@ > example MPDs G23, G29-1 and G29-2, at the schema's revision of 2026-06-13; the clause text of > its §5.16 was not available to this revision (§6.1). A section number of one of these texts > is that revision's. +> +> A receipt of version 2 also binds the **request terms** its decision was made under: the +> consent signal the player gave, a Global Privacy Platform string with the sections that apply +> and a child-directed flag (§8.6), and the supply chain each of its impressions was sold through, +> each seller named by its advertising system's domain and its seller id, with references to each +> system's sellers file and to the inventory owner's authorised-sellers file (§8.7). WAI carries +> the signal and the chain as the texts below define them, binds them into what the decider +> signs, and defines no scheme of its own beside them. A policy of version 3 requires every +> receipt to state its consent signal, child-directed in a room the policy marks as directed to +> children (§3.1, §9.3 step 13). +> +> Texts read for §8.6 and §8.7, each at its source: *Global Privacy Protocol String*, version +> 1.0, its revision of 3 November 2023: its sections on what the string serves and holds, who +> makes it, its format, header, delimiters and section encoding, and how a service reached by a +> URL is given it. *OpenRTB API Specification* Version 2.6: §3.2.2 (`Source`), §3.2.3 (`Regs`), +> §3.2.25 (`SupplyChain`) and §3.2.26 (`SupplyChainNode`). The *OpenRTB SupplyChain object*, in +> full. *Sellers.json*, version 1.0, July 2019, in full. *ads.txt Specification* Version 1.1, +> August 2022: §3 to §5. *Authorized Sellers for Apps (app-ads.txt)*, version 1.0, March 2019: +> its abstract, its introduction, and its sections on publishing the file and on translating an +> app's developer URL into the file's path. ## 1. Scope @@ -39,12 +59,18 @@ interstitial date ranges (§5) and their DASH alternative-presentation events ( list, the decision that fills it, and a viewer joining a break already running (§7); the break form of the `wai.video.ssai` receipt (§8); and verification end to end, from the room's events to the spend (§9). +The request terms a receipt of version 2 binds, and what a player, a decider and a verifier do +with them, are in scope too (§8.6, §8.7). Out of scope: a transport; how a decider assembles each viewer's candidate pod (targeting); how beacons reach it; the SCTE-35 messages a cue may carry, which WAI carries verbatim and never reads (live-events §4.3); the stitched form of `wai.video.ssai`, which is unchanged (§8.1); and a pre-roll, which a room does not have: a viewer who joins a running break is given what remains of it (§7.4), and one who joins between breaks starts with the program. +Out of scope, too: what a GPP string's sections state, which the GPP string's texts define and +WAI does not decode; whether a viewer gave a consent signal, or any party honoured one; and how +sellers files and authorised-sellers files are fetched and kept fresh, which their own texts +say. Relationship to the core spec: `wai.live.ads` is a **record** ([SPEC](../SPEC.md) §5): a signed record a WAI tool emits and verifies. It names no payload format and is not @@ -89,6 +115,9 @@ the stitched form keeps its bytes. - **Session**: one viewer's playback session, named in a receipt by 16 bytes the decider assigns (§8.2); under a policy that requires presentation claims, the 16 bytes the player's session key names (ad-presentation §3), which the decider does not choose. +- **Consent signal**, **GPP string**: §8.6. +- **Supply chain**, **node**, **sellers file**, **authorised-sellers file**, **domain name**: + §8.7. Integers in the records of §3 and §4 are little-endian, and a digest there is SHA-256 (FIPS 180-4), 32 bytes. Integers of the receipt's signed bytes (§8) are big-endian and its hashes @@ -103,7 +132,7 @@ later step is `bad_signature`. A *long* field is a u16 length and that many byte | offset | size | field | meaning | |---|---|---|---| | 0 | 4 | `magic` | `WAP1` (ASCII) | -| 4 | 1 | `version` | 1, or 2 for a policy with presentation fields | +| 4 | 1 | `version` | 1; 2 for a policy with presentation fields; 3 for one with presentation fields and an audience | | 5 | 32 | `room` | the room id (live-rooms §3.2) | | 37 | 32 | `decider_grant` | the digest of the room grant the decider runs under (§3.4) | | 69 | 1 | `slots` | the most ads one break holds, 1 to 16 | @@ -114,7 +143,8 @@ later step is `bad_signature`. A *long* field is a u16 length and that many byte | 81 | 1 | `snap` | bit 0 **out**: leave the program at the segment boundary nearest the break's start; bit 1 **in**: return at the one nearest its end | | 82 | long | `asset_base` | the base of every asset-list URI (§7) | | … | long | `creative_base` | the base of every creative's URI (§7) | -| … | 4 + long | presentation fields | version 2 only: `presentation`, `viewability`, `token_type` and `issuer`, as ad-presentation §8 defines them | +| … | 4 + long | presentation fields | version 2 and 3: `presentation`, `viewability`, `token_type` and `issuer`, as ad-presentation §8 defines them | +| … | 1 | `audience` | version 3 only: bit 0 **child**: the room is directed to children (§8.6) | | … | 64 | `sig` | §3.2 | A **base** is `https://` followed by 1 to 492 bytes, each of `!` to `~` (0x21–0x7E) other @@ -122,10 +152,15 @@ than `"`, `#`, `&`, `'`, `<`, `>`, `?` and `\`, the last of them `/`. A base so character that HLS's quoted strings, JSON strings or XML attribute values would have to escape, and a URI built on it (§7) is the base followed by a name of the break or of the creative. +A policy of version 3 carries the presentation fields as a policy of version 2 does, all four +zero when it requires no claim, and then its `audience`. It requires every receipt to be of +version 2, stating the consent signal its decision was made under, and with **child** set a +child-directed one (§9.3 step 13). + ### 3.2 Digest and signature The **digest** of a policy is SHA-256 over its record from `magic` through `creative_base`, or -through `issuer` for a policy of version 2. +through `issuer` for a policy of version 2, or through `audience` for one of version 3. The **signature** is Ed25519 by the room descriptor's `authority` over the 20 bytes `wai:live-ads-policy` (ASCII) and 0x01, followed by the bytes the digest covers. @@ -135,13 +170,14 @@ A reader reads exactly the bytes it is given as one policy, in the order of §3. at the first of these that holds: 1. Fewer than 4 bytes: `truncated`. A magic other than `WAP1`: `malformed`. No byte after the - magic: `truncated`. A version other than 1 or 2: `unknown_version`. + magic: `truncated`. A version other than 1, 2 or 3: `unknown_version`. 2. The bytes end before a field the layout requires: `truncated`. 3. Bytes after `sig`: `malformed`. It then refuses as `ad_policy_malformed` a policy whose `slots` is not 1 to 16, whose `restrict` or `snap` sets a bit above bit 1, whose `asset_base` or `creative_base` is not a -base (§3.1), or, of version 2, whose presentation fields break a rule of ad-presentation §8. +base (§3.1), of version 2 or 3, whose presentation fields break a rule of ad-presentation §8, +or, of version 3, whose `audience` sets a bit above bit 0. ### 3.4 The decider @@ -486,12 +522,19 @@ field is lowercase hexadecimal of its exact length, and an integer a JSON intege | `signer_pubkey` | 32 bytes: the decider's Ed25519 key | | `signer_id` | a string: the decider's `did:key` or origin-bound id | | `sig` | 64 bytes: §8.4 | +| `version` | a receipt of version 2 alone: 2. A receipt with no `version` member is of version 1 | +| `consent` | version 2 only: the consent signal (§8.6) | +| `supply_chains` | version 2 only: the supply chains (§8.7), one for each impression, in the impressions' order, each `null` when the decider states none for it | `quartiles_reached` is the stitched form's: 0 never rendered, 1 started (the billable impression), 2 to 5 a quarter, a half, three quarters and all of it. A receipt that does not read so, or that carries the legacy class label `HwShunt` (energy-measurement §2.6), is refused `receipt_malformed`. +A receipt of version 1 has none of `version`, `consent` and `supply_chains`, and one of version 2 +has all three; their members are §8.6's and §8.7's, each of its type, or the receipt is refused +`receipt_malformed`. + ### 8.3 Hashes BLAKE3, with integers big-endian, as the stitched form computes them: @@ -527,6 +570,11 @@ The **signing payload** is the concatenation of: receipt hash, is BLAKE3 over `wai:video-ssai-receipt-id` 0x01, the signing payload and `sig`: the stitched form's identity domain, over a payload whose first bytes no stitched payload has. +A receipt of **version 2** signs, in place of item 1, `wai:video-ssai-break-receipt` (ASCII) and +0x02, and, between items 5 and 6, its consent signal and then its supply chains, each in its +signed encoding (§8.6, §8.7). No receipt of version 1 is one of version 2, and every receipt of +version 1 keeps its bytes and its identity. + ### 8.5 Its own rules A form-2 receipt holds, on its own: @@ -541,10 +589,176 @@ A form-2 receipt holds, on its own: class, or above 0 with a class, a hardware class's declared uncertainty with `relative_ppm` and `window_us` above 0 and at the resolution floor (`joules_micro` × `relative_ppm` at least 288 676), and a calibrated instrument's reference holding a - character that is not Unicode White_Space. + character that is not Unicode White_Space; +6. of version 2, its consent signal holds the rules of §8.6, and its supply chains the rules of + §8.7. A rule of 1, 3, 4 or 5 that fails is `receipt_invalid`. These rules are what a buyer holding one receipt checks alone; §9.3 adds what the room, the policy and the pod let a verifier check. +Rule 6 that fails is `consent_invalid` for the consent signal and `supply_chain_invalid` for the +chains. + +### 8.6 The consent signal + +A receipt of version 2 states, in its member `consent`, the **consent signal** its decision was +made under: + +| member | value | +|---|---| +| `gpp` | the Global Privacy Platform string (the **GPP string**) the player gave with its request for the asset list, byte for byte; `""` when it gave none | +| `gpp_sid` | the sections of that string the player gave as applying to the request, each a section id from 1 to 65 535, in the order given; `[]` for none | +| `child_directed` | 1 when the request is directed to children: the policy sets **child** (§3.1), or the player's request states so; 0 otherwise | + +They are three of the members OpenRTB 2.6 gives an ad request's `Regs` object, `gpp`, `gpp_sid` +and `coppa` (its §3.2.3), and the GPP string is the one the *Global Privacy Protocol String* +defines. The signal holds these rules, or the receipt is refused `consent_invalid` (§8.5 rule 6): + +1. `gpp` is at most 65 535 bytes, each an ASCII letter or digit, `-`, `_`, `.` or `~`, the + characters the GPP string's text lists as URL-safe; and when it is not empty, no part of it + is empty between two of its `~` delimiters, before the first or after the last: the header + and each section are there; +2. `gpp_sid` holds at most 16 section ids, no two the same, each at least 1, and none when `gpp` + is empty; +3. `child_directed` is 0 or 1. + +Its **signed encoding** (§8.4), big-endian as every field of the receipt: the length (u32) and +bytes of `gpp`; the count (u64) of `gpp_sid`, then each section id (u16); `child_directed` +(u8). + +What each party does with it: + +- **The player.** A player that holds a GPP string for the viewer from its platform's consent + tool **MUST** give it, with the sections that apply, with its request for the asset list, as + it holds it, by a means this revision does not fix (for one, the request's parameters `gpp` + and `gpp_sid`, as the GPP string's text names them for a service reached by a URL); a player + whose request is directed to children states so the same way. A player that holds no string + gives none. It **MUST NOT** write, alter or complete a GPP string: the GPP string's text has a + property's owner or its consent tool make one, and every other party pass it on as it is. +- **The decider.** A decider **MUST** state in the receipt the string and the sections it was + given, byte for byte, and **MUST** pass them unchanged in every request it makes to fill the + break (an OpenRTB 2.6 request's `Regs.gpp` and `Regs.gpp_sid`, or a URL's `gpp` and + `gpp_sid`). It **MUST** set `child_directed` when the policy sets **child** or the request + states it, and **MUST** then mark every request it makes to fill the break as directed to + children (an OpenRTB 2.6 request's `Regs.coppa` 1). It **MUST NOT** clear a flag the policy or + the request set, and **MUST NOT** state a string or a section it was not given. +- **What no party infers.** An empty `gpp` states that no string was given: not that the viewer + consented, and not that no law applies to the request. A `child_directed` of 0 states that + neither the policy nor the request marked the request as directed to children: not that the + viewer is not a child. Neither the string nor the flag names the viewer, and a decider or a + verifier **MUST NOT** use either to identify a viewer, or to link one viewer's receipts to one + another. That rule binds those parties and does not keep receipts from linking: a GPP string + that is not empty is made for the viewer by a consent tool, not for one request, and can stay + the same, byte for byte, across the viewer's sessions and rooms, so whoever holds two receipts + that carry it can link them through it (§12). +- **The verifier.** A verifier reads the string's characters and its parts, and does not decode + its sections: what a section states, and whether it permits what was done, is for the GPP + string's texts and the parties to judge. A receipt that verifies shows what the decider signed + that it was given and passed on; not that the viewer gave it, and not that any party honoured + it. + +### 8.7 The supply chain + +A receipt of version 2 states, in its member `supply_chains`, the chain of sellers each of its +impressions was sold through: the `SupplyChain` object (OpenRTB 2.6 §3.2.25 and §3.2.26, and +the *OpenRTB SupplyChain object*) of the request whose answer gave the decider that +impression's candidate, with references to the files that let a buyer check each seller. A +pod's candidates can come from the answers to several requests, each request with its own +chain, so the member is an array with one entry for each impression, in the impressions' order, +and two impressions whose candidates answered one request state the same chain. WAI carries the object +and the references as those texts define them, and defines no supply-chain scheme of its own. +An entry is `null` when the decider states no chain for its impression, or an object: + +| member | value | +|---|---| +| `ver` | the object's `ver`: its version, `major.minor` (`1.0` in the revision read) | +| `complete` | its `complete`: 1 when the chain holds every node back to the owner of the inventory, 0 otherwise | +| `nodes` | its `nodes`, in its order: the first the initial seller (in a complete chain, the owner's own account), the last the one that sent the request; each an object of the members below | +| `authorised` | the authorised-sellers file the chain is checked against, `{"file", "domain", "sha256"}`, or `null` | + +A node: + +| member | value | +|---|---| +| `asi` | the canonical domain of the advertising system (the SSP, exchange or other system that bidders connect to) | +| `sid` | the seller's or reseller's account id in that system | +| `hp` | 1 when the node is in the flow of payment for the inventory, 0 otherwise | +| `rid`, `name`, `domain` | the node's request id, the name and the business domain of the entity paid under `sid`, each `null` when the node has none | +| `sellers` | SHA-256 of the advertising system's **sellers file**, the file at `https:///sellers.json` (*Sellers.json* 1.0), its bytes as the decider held them when it made the decision; `null` when it held none | + +In `authorised`: `file` is `ads.txt` for a site's **authorised-sellers file** (*ads.txt +Specification* 1.1 §3.1) or `app-ads.txt` for an app's (*Authorized Sellers for Apps +(app-ads.txt)* 1.0), at `https:///` followed by `file`; `domain` is the host the file is +at, the site's or the one derived from the app's developer URL; and `sha256` is SHA-256 of the +file's bytes as the decider held them. The object's `ext` members are not carried. + +A **domain name** is 1 to 253 bytes: two labels or more separated by `.`, each 1 to 63 ASCII +letters, digits and `-`, neither beginning nor ending with `-`. The texts read have `asi` and a +node's `domain` be a bare domain, never a URL. Each chain holds rules 1 to 3, and the member +rule 4, or the receipt is refused `supply_chain_invalid` (§8.5 rule 6): + +1. `ver` is one or more digits, `.` and one or more digits, at most 15 bytes, and `complete` is + 0 or 1; +2. `nodes` holds 1 to 32 nodes; each node's `asi` is a domain name, its `sid` 1 to 255 bytes, + its `hp` 0 or 1, its `rid` and `name` `null` or 1 to 255 bytes, and its `domain` `null` or a + domain name; +3. `authorised`, when it is not `null`, has a `file` of `ads.txt` or `app-ads.txt` and a + `domain` that is a domain name; +4. `supply_chains` holds as many entries as the receipt holds impressions. + +Its **signed encoding** (§8.4), big-endian: the entries' count (u64), then each entry: 0 for +`null`; or 1, then `ver` (its length, u32, and its bytes), `complete` (u8), the nodes' count +(u64), and for each node `asi` and `sid` (each its length, u32, and its bytes), `hp` (u8), each +of `rid`, `name` and `domain` as 0, or 1 and its length (u32) and bytes, and `sellers` as 0, or +1 and its 32 bytes; then `authorised` as 0, or 1, `file` and `domain` (each its length, u32, and +its bytes) and `sha256`. + +**The files.** A verifier finds a file a chain names among the supply files it holds by its +SHA-256 (§9.1), and reads it so: + +- a **sellers file** reads when it is UTF-8 with no byte order mark at its start, and a JSON + text (RFC 8259) within the limits that RFC 8259 leaves a reader to set (its §4, §8.1, §8.2 and + §9), which are these: its arrays and objects nest at most 64 deep, the file's own object + counted as the first; its value is an object with a member `sellers` that is an array of + objects, the **sellers**; each seller has a member `seller_id` that is a string, and when it + has a `seller_type`, that is a string too; neither the file's object nor a seller has two + members of one of these three names; and no member name of the file's object or of a seller, + nor the string of a `seller_id` or a `seller_type`, holds a `\u` escape of a surrogate + (U+D800 to U+DFFF) unless it is a high one followed at once by a low one, the pair that + escapes one character (RFC 8259 §7). The rest of the file is read only by RFC 8259's grammar: + a number is not converted to a value, so one of any size reads, and a string's escapes are + not decoded, so one that escapes a lone surrogate reads; +- an **authorised-sellers file** reads when it is UTF-8, and is read as *ads.txt Specification* + 1.1 §3.2 to §3.5 describe it, by these rules: a line ends at LF, CR LF or CR; a `#` and the + rest of its line are a comment, and dropped; a line is trimmed of spaces and tabs, and passed + over when it is then empty; a line whose part before its first `=` is not empty and holds no + `,`, space or tab is a variable record, and passed over; any other line, with the part from + its first `;` (its extension fields) dropped, is a **data record** when it is three or four + fields separated by `,`, each not empty once trimmed of spaces and tabs: the advertising + system's domain, the account id, the relationship and, optionally, a certification + authority's id. Every other line is passed over, as the text has a consumer read liberally. + +A node is **listed** in a sellers file that has a seller whose `seller_id` is its `sid`, byte for +byte; the first such seller is the node's **seller** there. A node is **authorised** by an +authorised-sellers file that has a data record whose first field is its `asi`, ASCII letters +compared without regard to case, whose second field is its `sid`, byte for byte, and whose +relationship, compared without regard to case, is `DIRECT`, or, for any node but the first of a +complete chain, `RESELLER`. The first node of a complete chain +is the owner's own account, which *ads.txt Specification* 1.1 §3.3 has the owner list as +`DIRECT`; an account another party controls to resell is `RESELLER`. + +What each party does with it: + +- **The decider.** For each impression, a decider **MUST** state the chain of the request whose + answer gave it the impression's candidate, node for node as that request carried it, and + `null` only when that request carried no chain or no request gave the candidate; and it + **MUST** state as a node's `sellers`, or as `authorised`, only the digest of a file it held + when it made the decision. It **MUST NOT** state a node the request did not carry, leave out + one it did, or state for one impression the chain of another impression's request. +- **The verifier.** A verifier that holds a file a chain names checks the chain against it + (§9.3 step 14); a part whose file it does not hold stays unchecked, and the verification says + so (§9.4). What it checks is the chain against the bytes the decider bound: whether those are + the files the advertising systems and the owner publish now is for the parties, who fetch + them, to judge. ## 9. Verification @@ -558,7 +772,9 @@ a key-log head or none, and the chains), and: - **receipts**: form-2 receipts in JSON (§8.2), in a list, each with the **pod** its decision ran over, the candidates in the order the decider gave them; - under a policy that requires presentation claims, the **claims**, **creatives** and - **issuers** of ad-presentation §9.1. + **issuers** of ad-presentation §9.1; +- **supply files**: the sellers files and authorised-sellers files it holds, by their bytes, in + any order; a receipt's chains name each by its SHA-256 (§8.7). ### 9.2 Steps @@ -615,6 +831,19 @@ Step 12 holds the room's ad spend to one ceiling across every viewer and break, receipts are given. A decision's stated funds are the decider's statement; that the spend of every receipt together stays within the ceiling is checked, whatever was stated. +13. **The consent signal**, under a policy of version 3: the receipt is of version 2 + (`request_terms_absent`); and when the policy sets **child**, its `child_directed` is 1 + (`child_directed_unset`). +14. **The supply chains**, of a receipt of version 2: for each chain it states, in the + impressions' order, and in it each node in order whose sellers file the verifier holds, that + file reads (`supply_file_malformed`) and lists the node (`supply_seller_unlisted`); then, + when the verifier holds the authorised-sellers file the chain names, that file reads + (`supply_file_malformed`) and authorises every node, in order (`supply_seller_unauthorised`) + (§8.7). A chain two impressions state is checked for each. + +Steps 13 and 14 read what the receipt signed and the bytes of the files held; neither changes +the decision, the asset list or the spend. + ### 9.4 Outcomes and what a verification states - **refuse**: the reason and where (§9.2). @@ -632,7 +861,13 @@ With accept, the verifier states: - each receipt: its identity (§8.4), the asset list it binds, written by the verifier, and its billable spend; and the spend of every receipt together, the spend that counts (§9.3 step 12); -- under a policy that requires presentation claims, what ad-presentation §9.4 states. +- under a policy that requires presentation claims, what ad-presentation §9.4 states; +- each receipt of version 2: its consent signal, as §8.6 writes it; and its supply chains, one + for each impression in its order, each `null` when it states none, or: its `complete`, and for + each node its `asi` and `sid`, whether the verifier held its sellers file (`sellers_checked`), + the `seller_type` of its seller there (§8.7), as written, or `null`, and, when the + authorised-sellers file was held, the relationship of the first record that authorises it, in + capitals, or `null`; and whether that file was held (`authorised_checked`). ## 10. Reasons @@ -660,6 +895,13 @@ The codes this extension adds, each with where it is raised: | `asset_list_mismatch` | §9.3 step 10 | | `receipt_duplicate` | §9.3 step 11 | | `funds_exceeded` | §9.3 step 12 | +| `consent_invalid` | §8.5 rule 6, §8.6, §9.3 step 3 | +| `supply_chain_invalid` | §8.5 rule 6, §8.7, §9.3 step 3 | +| `request_terms_absent` | §9.3 step 13 | +| `child_directed_unset` | §9.3 step 13 | +| `supply_file_malformed` | §8.7, §9.3 step 14 | +| `supply_seller_unlisted` | §8.7, §9.3 step 14 | +| `supply_seller_unauthorised` | §8.7, §9.3 step 14 | An ads verifier also raises every code a room verifier raises (live-rooms §11 and the live-events codes it lists), at the room: `kind_not_permitted` among them for a cue or an @@ -688,7 +930,37 @@ its own, `NAME.m3u8`, one tag a line in the order of §5.4. It has: other rule of §8.5, each refused `receipt_invalid`; a cue by a principal whose grant does not permit `cue`, which the room refuses `kind_not_permitted`; and a decider grant revoked through the key log, and one whose window ends before the break; - - a room with a gap. + - a room with a gap; + - the request terms (§8.6, §8.7): the live break under a policy of version 3, its first + viewer's receipt stating a GPP string with its applicable section and, for its two ads, the + chains of the two requests they answered, one from the channel's own account at an SSP + through an exchange and one from the channel's own account at the exchange, each checked + against the sellers files and the channel's app-ads.txt, and its second viewer's stating no + string, a child-directed request and an incomplete chain whose sellers file the verifier + does not hold; a room the policy marks as directed to children; one chain two impressions + state, of four nodes that the channel's ads.txt authorises only when it is read by each rule + of §8.7 (a record of four fields, other letter cases, CR LF and a CR alone, an extension + field); and a sellers file that lists its node only when what §8.7 does not read is read by + RFC 8259's grammar alone (numbers past binary64's range and of 400 digits, escapes of lone + surrogates in members skipped, a member 64 deep); + - refused there: four receipts whose GPP string, child-directed flag, a node's seller id or + the order of its chains was changed after the decider signed them, each `bad_signature`; a + receipt stating version 3, one of version 1 carrying a consent signal, and one of version 2 + without its chains (`receipt_malformed`); a receipt for each rule of §8.6 + (`consent_invalid`) and for each rule of §8.7 (`supply_chain_invalid`); a receipt of + version 1 under a policy of version 3 (`request_terms_absent`) and one with no + child-directed flag in a room directed to children (`child_directed_unset`); a sellers file + with no sellers, one nested 65 deep, one whose second seller's id escapes a lone surrogate, + one with `sellers` twice and one holding `NaN`, and an app-ads.txt that is not UTF-8 (each + `supply_file_malformed`); a node its sellers file does not list (`supply_seller_unlisted`); + a first node of a complete chain authorised only as a reseller, a node the app-ads.txt does + not name, and one it names only in lines a reader passes over, a comment and a line of five + fields (each `supply_seller_unauthorised`); and a policy whose `audience` sets a bit above + bit 0 (`ad_policy_malformed`). + +The sellers files and the authorised-sellers files the vectors hold are files of their own, +`supply.NAME`, each named in the `supply_files` of the vectors that hold it, read as bytes; +every one is a fixture written for its case under `.example` names. A conforming verifier gives every room the outcome, the reason, the place and everything else the corpus states, and every tag file byte for byte. `wai_live_ads_vectors verify` and the test @@ -698,8 +970,9 @@ codes. `live-conformance/live_ads_verify.py` is the second implementation, writt text: it shares no code with `wai-rs`; it verifies each room with the live-rooms corpus's second implementation (`live_rooms_verify.py`), every signature with WAI's strict rule (`claim-conformance/_ed25519.py`) and every BLAKE3 hash with `claim-conformance/_blake3.py`; it -writes every tag, event and asset list from §5 to §7 and requires their bytes; and it mutates -copies of the corpus and requires every copy to fail. +writes every tag, event and asset list from §5 to §7 and requires their bytes; it reads every +receipt's request terms and every sellers file and authorised-sellers file by §8.6 and §8.7 +itself; and it mutates copies of the corpus and requires every copy to fail. ## 12. Security and privacy considerations @@ -723,12 +996,36 @@ copies of the corpus and requires every copy to fail. §9, §13 for what that does and does not prove). - A receipt names a session by 16 bytes the decider assigns. A decider **SHOULD** assign a value with no meaning outside its own records, and **MUST NOT** derive it from a viewer's key - or another identifier a third party holds, so that receipts do not link a viewer across - rooms; within a room, one session's receipts are linkable to one another by design, so that a - viewer is filled once for a break (§9.3 step 11). Under a policy that requires presentation - claims the session is derived from a key the player makes for that session of that room - alone (ad-presentation §3), which is no viewer's key and links nothing across rooms. + or another identifier a third party holds, so that receipts that carry no GPP string (§8.6) + do not link a viewer across rooms; within a room, one session's receipts are linkable to one + another by design, so that a viewer is filled once for a break (§9.3 step 11). Under a + policy that requires presentation claims the session is derived from a key the player makes + for that session of that room alone (ad-presentation §3), which is no viewer's key and links + nothing across rooms. - The asset-list request carries the viewer's playback-session parameter (Appendix D.4) to the decider, and nowhere else: the manifests every viewer shares carry nothing of any viewer. - The policy is signed by the room authority and the receipts by the decider. A decider whose grant is revoked or out of its window has its receipts refused (§9.2, §9.3). +- **The consent signal** is bound into what the decider signs: a receipt whose string, sections + or child-directed flag changed after signing fails its signature (§8.5 rule 2), as one whose + chains changed does. A verified receipt shows what §8.6 says it shows, what the decider stated + it was given and passed on, and no more. The string travels with the asset-list request to the + decider, from the decider to those it requests ads from, and in the receipt to whoever holds + it. +- **A GPP string links what carries it.** A consent tool makes the string for a viewer, not for + one request, and it can stay the same, byte for byte, across the viewer's sessions and rooms. + A receipt whose `gpp` is not empty can therefore be linked, through the string, to every other + receipt that carries the same string, in any room, and to a stitched pod-input record that + carries it (stitched-presentation §4.7), by whoever holds them: the same viewer's among them, + for as long as the string stays the same. §8.6's rule that a decider and a verifier not use + the string to link receipts binds those parties; it does not remove the link. The string is + in the signed bytes, so a holder cannot strip it from a receipt and keep the receipt + verifiable. What the session item above says of rooms holds of a receipt whose `gpp` is + empty. +- **A room for children.** A policy that sets **child** binds every decision of the room to a + child-directed signal: a receipt that does not carry one is refused (§9.3 step 13). That the + decider marked each of its requests so is its statement, as the funds a decision states are. +- **The supply chain** and the digests of the files it names are the decider's signed statement. + A verifier that holds the files checks every node against them; each file is fetched from the + domain it belongs to, by its own text's access method, and a verifier that needs them to be + the files published now fetches them itself and compares their digests with the receipt's. diff --git a/wai/extensions/stitched-presentation.md b/wai/extensions/stitched-presentation.md index 8db956a13..cb7587227 100644 --- a/wai/extensions/stitched-presentation.md +++ b/wai/extensions/stitched-presentation.md @@ -30,8 +30,14 @@ > Appendix D (interstitials and the asset list); ISO/IEC 23009-1:2026's MPD schema (its > `EventStream` and `Event`). Nothing here depends on a part of them live-ads did not read. > - RFC 8259 (JSON) and XML 1.0, for the subset of §3.5. +> - For the consent signal a record of version 2 carries (§4.7), the texts +> [live-ads](live-ads.md) read for its §8.6, at the revisions it names. > > A section number of one of these texts is that revision's. +> +> A pod-input record of version 2 also carries the consent signal its decision was made under, +> a Global Privacy Platform string with the sections that apply and a child-directed flag, bound +> into what the signer signs (§4.7); a verifier states it beside the record's verdict. ## 1. Scope @@ -41,6 +47,8 @@ form-1 `wai.video.ssai` receipt as its response (§4); the stitched claim, its s rules, its signature and its attestation challenge (§5); composited frames (§6); verification: each record's and each claim's verdict, each ad's presentations and quartiles, and the spend that counts, by currency (§7); and the reference producer, the tool and the service (§8). +The consent signal a record of version 2 carries, and what a player and a signer do with it, are +in scope too (§4.7). Out of scope: a transport; how a player's session key reaches the signer (§4.6 says what the signer does with it, not how it arrives); the ad decision itself, which stays the ad server's; @@ -194,7 +202,7 @@ element's **text** is its character data and CDATA children, joined in order, wi | size | field | meaning | |---|---|---| | 4 | `magic` | `WSI1` (ASCII) | -| 1 | `version` | 1 in this revision | +| 1 | `version` | 1; or 2 for a record that carries the consent signal (§4.7) | | 1 | `carriage` | how the stream marks its ads, 1 to 3 (§3.1) | | 16 | `session` | the session the decision was made for (§5.2); 16 zero bytes for a **shared** break, one every viewer of the stream is shown (§4.6) | | short | `avail_id` | the break, as the signer names it: a text field | @@ -209,6 +217,7 @@ element's **text** is its character data and CDATA children, joined in order, wi | 4 + long | measurement terms | `presentation`, `viewability`, `token_type` (u16) and `issuer` (long), as ad-presentation §8 defines them | | 1 | `n` | the ads' count, 1 to 16 | | … | `ads` | `n` ads, §4.2 | +| long + 2 + 2 × `k` | consent signal | version 2 only: `gpp`, `k`, `gpp_sid` and `child_directed` (§4.7) | | 32 | `signer` | the signer's Ed25519 key | | 64 | `sig` | §4.4 | @@ -262,7 +271,7 @@ A reader reads exactly the bytes it is given as one record, in the order of §4. it `pod_input_malformed` when the bytes end before a field the layout requires or go on after `sig`, or when: -1. its magic is not `WSI1` or its version not 1, or its `carriage` is not 1, 2 or 3; +1. its magic is not `WSI1` or its version not 1 or 2, or its `carriage` is not 1, 2 or 3; 2. `avail_id` is not a text field, or `avail_ms` is 0; 3. `response_format` is not 1, 2 or 3; or `id_kind` is not 1 or 2 with format 1 or 2, or not 3 with format 3; @@ -278,7 +287,9 @@ it `pod_input_malformed` when the bytes end before a field the layout requires o is 0; `price_micros` is above 10^12; `price_source` is not 1, 2 or 3, is 1 with a response format other than 1, is 2 with format 3, or is other than 3 with format 3; `evidence` is not zero exactly when `price_source` is not 2; -9. two ads have one `marker`. +9. two ads have one `marker`; +10. of version 2, its consent signal breaks a rule of live-ads §8.6, or, in a shared record + (§4.6), carries a GPP string. ### 4.5 A form-1 receipt as the response @@ -336,6 +347,40 @@ keys of its own can sign claims for sessions nobody watched; a record that buys **SHOULD** set ad-presentation §8's **attest** bit, so that each presentation costs that party a vouched device (ad-presentation §13). +### 4.7 The consent signal + +A record of version 2 carries, after its ads and before `signer`, the consent signal its +decision was made under: live-ads §8.6's, in this record's layout. + +| size | field | meaning | +|---|---|---| +| long | `gpp` | the GPP string the player gave, byte for byte; no bytes when it gave none | +| 1 | `k` | the count of `gpp_sid`, 0 to 16 | +| 2 × `k` | `gpp_sid` | u16 each: the string's sections the player gave as applying, in the order given | +| 1 | `child_directed` | 1 when the request is directed to children, 0 otherwise | + +The signal holds live-ads §8.6's rules (§4.4 rule 10), and the parties do with it what that +section says, the signer in the decider's place: + +- **The player** gives the string, its sections and a child-directed request with its request + for the session's playlist or asset list, as it gives its session key (§4.6), by a means this + revision does not fix, and **MUST NOT** write, alter or complete a GPP string. +- **The signer** states in the record what it was given, byte for byte, **MUST** pass it + unchanged in every ad request it makes for the decision (an ad request URL's `gpp` and + `gpp_sid` parameters, as the GPP string's text names them, or an OpenRTB 2.6 request's + `Regs.gpp`, `Regs.gpp_sid` and `Regs.coppa`), and **MUST NOT** state a string, a section or a + flag it was not given, or clear a flag it was given. +- **A shared record** (§4.6) is a decision every viewer is shown, and carries no viewer's + string: its `gpp` and `gpp_sid` are empty, and its `child_directed` is 1 when the stream is + directed to children. A signer **MUST NOT** apply one viewer's signal to a break every viewer + sees. +- **What no party infers** is live-ads §8.6's: an empty string is no string given, a flag of 0 + is no mark given, and neither names the viewer. A string that is not empty can still link + every record and receipt that carries it (§11). + +The signal is in the bytes the digest and the signature cover (§4.4): a record whose signal +changed after signing fails its signature. + ## 5. The stitched claim ### 5.1 Layout @@ -552,7 +597,8 @@ figures from the same signed bytes. claims given), presentations, verdict, quartiles, presented spend and, for a checked format-3 response, its stated quartile; the record's presented spend, and its stated spend for a checked format-3 response; and its frame's share (§6) and ad region, the rectangles of - live-compose §5.9, none for the full picture. + live-compose §5.9, none for the full picture; and, for a record of version 2, its consent + signal (§4.7), as live-ads §8.6 writes it. - For each claim, in order: its digest when it reads; its verdict; from step 4 on, the record and the ad's slot it names; and for an own claim, from step 8 on, the quartile it reaches, its run and its viewable time (ad-presentation §6.2). @@ -630,7 +676,13 @@ case. The vectors: and one over the player's area; - **form1_prefetch**: a stitcher's own decision sealed in a form-1 receipt whose beacons counted the second ad as started when its first segment was fetched ahead of the break; the viewer - left after the first ad; and a record naming the same receipt at another price. + left after the first ad; and a record naming the same receipt at another price; +- **consent**: two viewers' personalised playlists, each with one stitched ad, and a record of + version 2 per viewer: one carrying the GPP string its player gave with its applicable + section, one a child-directed request with no string, each presented; a record whose GPP + string was changed after signing (`bad_signature`), whose ad's claim then names no decision; + a shared record carrying a viewer's string, and a record stating a section with no string + (each `pod_input_malformed`). A conforming verifier gives every record and claim its verdict and facts, every ad its presentations, quartiles and spend, and every vector its spend by currency, as the corpus @@ -675,3 +727,13 @@ fail. The reference producer's tests (§8) require the corpus's claims from its region overstates its fraction in view, which attestation does not bound: a buyer that needs the measurement itself bounded needs the player's integrity, as ad-presentation §13 says of every claim. +- **The consent signal.** A record of version 2 binds the signal into what the signer signs, so + one whose string, sections or flag changed after signing is refused at its signature, and its + ads count nothing. A verified record shows what the signer stated it was given and passed on + (live-ads §8.6), not that the viewer gave it or that the ad server honoured it. A shared + record carries no viewer's string, so a break every viewer sees links to no viewer's signal. + A record of one session whose `gpp` is not empty can be linked through it, by whoever holds + them, to every other record and every live-ads receipt that carries the same string, in any + session or room, as live-ads §12 says of a receipt: the string is made for the viewer, not for + one session, and is in the bytes the signature covers. What **Linking** above says of claims + does not extend to such a record. diff --git a/wai/live-conformance/ads/README.md b/wai/live-conformance/ads/README.md index 087f687b8..7c492e12f 100644 --- a/wai/live-conformance/ads/README.md +++ b/wai/live-conformance/ads/README.md @@ -5,11 +5,16 @@ events open ad breaks, each a room (its `WRD1` descriptor, `WRG1` grants, key-lo and each principal's `WEV1` chain, the break annotations `WAB1` among them), its `WAP1` ad policy, the presentation time of `t0` on the DASH Period (`pt0_ms`), and the break-form `wai.video.ssai` receipts of its viewers, each with the pod its decision ran over; what a -verifier states of them; and each accepted room's HLS tags, byte for byte. +verifier states of them; and each accepted room's HLS tags, byte for byte. Receipts of version +2 bind the consent signal their decision was made under and the supply chain of each impression +(§8.6, §8.7), and the vectors that hold them name the sellers files and authorised-sellers +files they are checked against. **Ads equivalence.** For each vector, a conforming ads verifier given its `descriptor`, -`grants`, `key_log`, `chains`, `policy`, `pt0_ms`, `receipts` and `pods` (§9.1) reaches the -outcome the vector states (§9.4): +`grants`, `key_log`, `chains`, `policy`, `pt0_ms`, `receipts` and `pods`, and the files its +`supply_files` names when it has that member, each as its bytes (§9.1), reaches the outcome the +vector states +(§9.4): - `refuse`: the `reason` and where, `at`: `["room", …]` with the room verifier's own place (live-rooms §9.1), `["policy"]`, `["record", c, i]` for record `i` of chain `c`, or @@ -22,8 +27,11 @@ outcome the vector states (§9.4): and `duration_ms`; `hls`, the file holding the room's HLS tags; `dash`, the Period's event streams (§6.3); `dash_open`, each out's event as written before its in, by the out's index among the `breaks`; `receipt_facts`, each receipt's `identity` (§8.4), the `asset_list` it - binds as the verifier writes it, and its `spend_micros`; and `spend_micros`, the spend of - every receipt together. + binds as the verifier writes it, and its `spend_micros`, and, for a receipt of version 2, + its `consent` (`gpp`, `gpp_sid`, `child_directed`) and its `supply_chains`, one for each + impression, each `null` or its `complete`, its `nodes` (each `asi`, `sid`, + `sellers_checked`, `seller_type` and `relationship`) and `authorised_checked`; and + `spend_micros`, the spend of every receipt together. Each tag file, `NAME.m3u8`, is the HLS tags of vector `NAME` (§5.1 to §5.3), one tag a line in the order §5.4 gives, each line ended by a line feed. It is the date ranges of the room's Media @@ -37,7 +45,10 @@ authored in code, from fixed seeds. Each principal's key is SHA-256 of a fixed l principal's index; each creative's hash and each session id likewise; every time is a constant (the rooms' `t0` is 2026-10-16T00:00:00Z); every origin and base URI is an `.example` name (RFC 2606); and Ed25519 signing is deterministic (RFC 8032), so every file is -the same bytes on every machine. The generator asserts that each vector reaches the outcome it +the same bytes on every machine. The GPP string is the example the GPP string's text gives for +a service reached by a URL, with its section 2; the sellers files and the authorised-sellers +files are fixtures written for their cases under `.example` names, none fetched from a service. +The generator asserts that each vector reaches the outcome it was written to have, and that the corpus gives every reason of §10, before it writes. No recorded data. @@ -45,6 +56,16 @@ recorded data. - `vectors.json`: `principals` (the keys, by name, informative) and `vectors`. - `NAME.m3u8`: the HLS tags of each accepted room. +- `supply.NAME`: the sellers files (`supply.ssp.example.sellers.json`, + `supply.exchange.example.sellers.json`, and `supply.gateway.example.sellers.json`, which + reads only by RFC 8259's grammar outside what §8.7 reads), the channel's authorised-sellers + files (`supply.channel.example.app-ads.txt`, and `supply.channel.example.ads.txt`, whose + records need each reading rule of §8.7), and files that do not read or authorise: + `supply.not-sellers.json` (no sellers), `supply.too-deep.sellers.json`, + `supply.lone-surrogate.sellers.json`, `supply.sellers-twice.sellers.json`, + `supply.nan.sellers.json`, `supply.not-utf8.app-ads.txt` and + `supply.passed-over.app-ads.txt`. They are read as bytes: some end lines with CR LF or a CR + alone, and one is not UTF-8. `wai/.gitattributes` keeps every line ending as it is. Every room has a producer, whose grant permits `cue`, `meta` and `chat`; a viewer, whose grant permits `chat`; and the decider, whose grant permits no event and carries the room's ad budget, @@ -82,6 +103,46 @@ The rooms: (`outside_grant_window`), `pod_mismatch`, `decision_mismatch`, `asset_list_mismatch`, `receipt_duplicate` and `funds_exceeded` (a budget of 1 minor unit, and two viewers each billed 600 000 millionths). +- `consent_and_chain`: `live_break`'s room under a policy of version 3. The first viewer's + receipt states the GPP string its player gave, with section 2, and a chain for each of its + two ads, which answered two requests: a complete chain from the channel's own account at + `ssp.example` (`pub-1001`, `PUBLISHER`, `DIRECT`) through `exchange.example` (the SSP's + account `ssp-77`, `INTERMEDIARY`, `RESELLER`), and a complete chain of one node, the + channel's own account at the exchange (`pub-2001`, `PUBLISHER`, `DIRECT`), each checked + against the sellers files and the channel's app-ads.txt. The joining viewer's states no + string, a child-directed request, and an incomplete chain of one node whose sellers file the + verifier does not hold: unchecked. +- `child_room`: a 15 s break in a room whose policy marks it as directed to children; the + receipt states the request child-directed and no chain for its ad. +- `authorised_read_liberally`: the first viewer's two ads answered one request, so its receipt + states one chain twice: four nodes, each authorised by `supply.channel.example.ads.txt` only + when it is read by §8.7's rules (a record of four fields; one whose advertising system is in + other letter cases and that ends with CR LF; one whose relationship is in lower case and that + ends with a CR alone; one with an extension field). +- `sellers_read_by_grammar`: the first viewer's first ad sold through the gateway's seller + `pub-4001`, which `supply.gateway.example.sellers.json` lists only when what §8.7 does not + read is read by RFC 8259's grammar alone: a number past binary64's range, one of 400 digits, + escapes of lone surrogates in members skipped, a member name escaping a character with a + pair of surrogates, a member nested 64 deep with the file's object, and a seller id that + escapes one of its characters. Its second ad states no chain. +- refused, one defect each: `receipt_consent_altered`, `receipt_child_flag_cleared`, + `receipt_chain_altered` and `receipt_chains_swapped` (a GPP string, a child-directed flag, a + first node's seller id and the order of the two chains changed after signing, each + `bad_signature`); `receipt_version_three`, `receipt_v1_with_consent` and + `receipt_v2_without_chains` (`receipt_malformed`); `receipt_gpp_empty_section`, + `receipt_gpp_not_url_safe`, `receipt_sections_without_string`, `receipt_section_repeated` + and `receipt_child_flag_two` (`consent_invalid`); `receipt_chain_ver`, + `receipt_chain_complete_two`, `receipt_asi_url`, `receipt_authorised_file`, + `receipt_authorised_domain` and `receipt_chains_count` (`supply_chain_invalid`); + `receipt_without_terms` (a receipt of version 1 under a policy of version 3, + `request_terms_absent`); `child_flag_unset` (`child_directed_unset`); + `sellers_file_malformed`, `sellers_file_too_deep`, `sellers_file_lone_surrogate`, + `sellers_file_sellers_twice`, `sellers_file_nan` and `authorised_file_not_utf8` + (`supply_file_malformed`); `seller_unlisted` (`supply_seller_unlisted`); + `seller_unauthorised` (a chain stated complete whose first node is a reseller's account), + `seller_absent_from_authorised` and `authorised_record_passed_over` (a node named only in a + comment and in a line of five fields) (`supply_seller_unauthorised`); and + `policy_audience_bit` (`ad_policy_malformed`). ## Running @@ -101,6 +162,7 @@ that the corpus gives each of them. `live_ads_verify.py` is the second implement from the extension's text and sharing no code with `wai-rs`: it verifies each room with `live_rooms_verify.py`, writes every tag, event and asset list itself, verifies every receipt's signature with WAI's strict Ed25519 rule and computes its BLAKE3 hashes from the specification, -reads §10 from the extension, gives every case the same outcome, requires every reason of §10 -to appear, and then mutates copies of the corpus, one defect each, each of which must fail. +reads every receipt's request terms and every supply file itself, as bytes, reads §10 from the +extension, gives every case the same outcome, requires every reason of §10 to appear, and then mutates +copies of the corpus, one defect each, each of which must fail. Pass = an exit status of 0 from each. diff --git a/wai/live-conformance/ads/authorised_read_liberally.m3u8 b/wai/live-conformance/ads/authorised_read_liberally.m3u8 new file mode 100644 index 000000000..3311075f2 --- /dev/null +++ b/wai/live-conformance/ads/authorised_read_liberally.m3u8 @@ -0,0 +1,3 @@ +#EXT-X-DATERANGE:ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0-preload",CLASS="com.apple.hls.preload",START-DATE="2026-10-16T00:00:56.000Z",DURATION=4.000,X-URI="https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.json?_HLS_interstitial_id=wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",X-TARGET-ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",X-TARGET-CLASS="com.apple.hls.interstitial" +#EXT-X-DATERANGE:ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",CLASS="com.apple.hls.interstitial",START-DATE="2026-10-16T00:01:00.000Z",CUE="ONCE",X-ASSET-LIST="https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.json?_HLS_interstitial_id=wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0" +#EXT-X-DATERANGE:ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",CLASS="com.apple.hls.interstitial",DURATION=30.000,X-RESUME-OFFSET=30.000,X-PLAYOUT-LIMIT=30.000 diff --git a/wai/live-conformance/ads/child_room.m3u8 b/wai/live-conformance/ads/child_room.m3u8 new file mode 100644 index 000000000..fb49da5e9 --- /dev/null +++ b/wai/live-conformance/ads/child_room.m3u8 @@ -0,0 +1,2 @@ +#EXT-X-DATERANGE:ID="wai-ad-e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0-preload",CLASS="com.apple.hls.preload",START-DATE="2026-10-16T00:00:41.000Z",DURATION=4.000,X-URI="https://ads.rooms.example/sgai/wai-ad-e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0.json?_HLS_interstitial_id=wai-ad-e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0",X-TARGET-ID="wai-ad-e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0",X-TARGET-CLASS="com.apple.hls.interstitial" +#EXT-X-DATERANGE:ID="wai-ad-e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0",CLASS="com.apple.hls.interstitial",START-DATE="2026-10-16T00:00:45.000Z",CUE="ONCE",DURATION=15.000,X-ASSET-LIST="https://ads.rooms.example/sgai/wai-ad-e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0.json?_HLS_interstitial_id=wai-ad-e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0",X-RESUME-OFFSET=15.000,X-PLAYOUT-LIMIT=15.000 diff --git a/wai/live-conformance/ads/consent_and_chain.m3u8 b/wai/live-conformance/ads/consent_and_chain.m3u8 new file mode 100644 index 000000000..3311075f2 --- /dev/null +++ b/wai/live-conformance/ads/consent_and_chain.m3u8 @@ -0,0 +1,3 @@ +#EXT-X-DATERANGE:ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0-preload",CLASS="com.apple.hls.preload",START-DATE="2026-10-16T00:00:56.000Z",DURATION=4.000,X-URI="https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.json?_HLS_interstitial_id=wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",X-TARGET-ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",X-TARGET-CLASS="com.apple.hls.interstitial" +#EXT-X-DATERANGE:ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",CLASS="com.apple.hls.interstitial",START-DATE="2026-10-16T00:01:00.000Z",CUE="ONCE",X-ASSET-LIST="https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.json?_HLS_interstitial_id=wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0" +#EXT-X-DATERANGE:ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",CLASS="com.apple.hls.interstitial",DURATION=30.000,X-RESUME-OFFSET=30.000,X-PLAYOUT-LIMIT=30.000 diff --git a/wai/live-conformance/ads/sellers_read_by_grammar.m3u8 b/wai/live-conformance/ads/sellers_read_by_grammar.m3u8 new file mode 100644 index 000000000..3311075f2 --- /dev/null +++ b/wai/live-conformance/ads/sellers_read_by_grammar.m3u8 @@ -0,0 +1,3 @@ +#EXT-X-DATERANGE:ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0-preload",CLASS="com.apple.hls.preload",START-DATE="2026-10-16T00:00:56.000Z",DURATION=4.000,X-URI="https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.json?_HLS_interstitial_id=wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",X-TARGET-ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",X-TARGET-CLASS="com.apple.hls.interstitial" +#EXT-X-DATERANGE:ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",CLASS="com.apple.hls.interstitial",START-DATE="2026-10-16T00:01:00.000Z",CUE="ONCE",X-ASSET-LIST="https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.json?_HLS_interstitial_id=wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0" +#EXT-X-DATERANGE:ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",CLASS="com.apple.hls.interstitial",DURATION=30.000,X-RESUME-OFFSET=30.000,X-PLAYOUT-LIMIT=30.000 diff --git a/wai/live-conformance/ads/supply.channel.example.ads.txt b/wai/live-conformance/ads/supply.channel.example.ads.txt new file mode 100644 index 000000000..01275d97f --- /dev/null +++ b/wai/live-conformance/ads/supply.channel.example.ads.txt @@ -0,0 +1,5 @@ +# ads.txt of channel.example +ssp.example, pub-3001, DIRECT, f08c47fec0942fa0 +Exchange.EXAMPLE, ssp-77, RESELLER +wholesale.example, seat-5, reseller bridge.example, br-12, RESELLER;ext=video +CONTACT=adops@channel.example diff --git a/wai/live-conformance/ads/supply.channel.example.app-ads.txt b/wai/live-conformance/ads/supply.channel.example.app-ads.txt new file mode 100644 index 000000000..e3a6ea8ee --- /dev/null +++ b/wai/live-conformance/ads/supply.channel.example.app-ads.txt @@ -0,0 +1,6 @@ +# app-ads.txt of channel.example +OWNERDOMAIN=channel.example +ssp.example, pub-1001, DIRECT +exchange.example, ssp-77, RESELLER +exchange.example, pub-2001, DIRECT +CONTACT=adops@channel.example diff --git a/wai/live-conformance/ads/supply.exchange.example.sellers.json b/wai/live-conformance/ads/supply.exchange.example.sellers.json new file mode 100644 index 000000000..93e94abf6 --- /dev/null +++ b/wai/live-conformance/ads/supply.exchange.example.sellers.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "sellers": [ + {"seller_id": "ssp-77", "name": "Example SSP", "domain": "ssp.example", "seller_type": "INTERMEDIARY"}, + {"seller_id": "pub-2001", "name": "Example Channel", "domain": "channel.example", "seller_type": "PUBLISHER"} + ] +} diff --git a/wai/live-conformance/ads/supply.gateway.example.sellers.json b/wai/live-conformance/ads/supply.gateway.example.sellers.json new file mode 100644 index 000000000..5cbb01243 --- /dev/null +++ b/wai/live-conformance/ads/supply.gateway.example.sellers.json @@ -0,0 +1,11 @@ +{ + "version": 1.0, + "x-audited": 1e400, + "x-serial": 7777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777, + "x-note": "\ud800 escapes a lone surrogate", + "x-\ud83d\udcfa": true, + "ext": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], + "sellers": [ + {"seller_id": "pub-\u0034001", "seller_type": "PUBLISHER", "ext": {"note": "\udfff"}} + ] +} diff --git a/wai/live-conformance/ads/supply.lone-surrogate.sellers.json b/wai/live-conformance/ads/supply.lone-surrogate.sellers.json new file mode 100644 index 000000000..c91b63e5d --- /dev/null +++ b/wai/live-conformance/ads/supply.lone-surrogate.sellers.json @@ -0,0 +1 @@ +{"sellers": [{"seller_id": "pub-4001", "seller_type": "PUBLISHER"}, {"seller_id": "\ud800"}]} diff --git a/wai/live-conformance/ads/supply.nan.sellers.json b/wai/live-conformance/ads/supply.nan.sellers.json new file mode 100644 index 000000000..3eaceabf1 --- /dev/null +++ b/wai/live-conformance/ads/supply.nan.sellers.json @@ -0,0 +1 @@ +{"x-audited": NaN, "sellers": [{"seller_id": "pub-4001", "seller_type": "PUBLISHER"}]} diff --git a/wai/live-conformance/ads/supply.not-sellers.json b/wai/live-conformance/ads/supply.not-sellers.json new file mode 100644 index 000000000..703ffb7d7 --- /dev/null +++ b/wai/live-conformance/ads/supply.not-sellers.json @@ -0,0 +1,3 @@ +{ + "version": "1.0" +} diff --git a/wai/live-conformance/ads/supply.not-utf8.app-ads.txt b/wai/live-conformance/ads/supply.not-utf8.app-ads.txt new file mode 100644 index 000000000..b27099c35 --- /dev/null +++ b/wai/live-conformance/ads/supply.not-utf8.app-ads.txt @@ -0,0 +1,3 @@ +# app-ads.txt de Société Exemple +ssp.example, pub-1001, DIRECT +exchange.example, ssp-77, RESELLER diff --git a/wai/live-conformance/ads/supply.passed-over.app-ads.txt b/wai/live-conformance/ads/supply.passed-over.app-ads.txt new file mode 100644 index 000000000..6b246b642 --- /dev/null +++ b/wai/live-conformance/ads/supply.passed-over.app-ads.txt @@ -0,0 +1,3 @@ +# exchange.example, ssp-99, RESELLER +ssp.example, pub-1001, DIRECT +exchange.example, ssp-99, RESELLER, f08c47fec0942fa0, video diff --git a/wai/live-conformance/ads/supply.sellers-twice.sellers.json b/wai/live-conformance/ads/supply.sellers-twice.sellers.json new file mode 100644 index 000000000..f60f9e6c0 --- /dev/null +++ b/wai/live-conformance/ads/supply.sellers-twice.sellers.json @@ -0,0 +1 @@ +{"sellers": [], "sellers": [{"seller_id": "pub-4001", "seller_type": "PUBLISHER"}]} diff --git a/wai/live-conformance/ads/supply.ssp.example.sellers.json b/wai/live-conformance/ads/supply.ssp.example.sellers.json new file mode 100644 index 000000000..d393f0f56 --- /dev/null +++ b/wai/live-conformance/ads/supply.ssp.example.sellers.json @@ -0,0 +1,8 @@ +{ + "contact_email": "adops@ssp.example", + "version": "1.0", + "sellers": [ + {"seller_id": "pub-1001", "name": "Example Channel", "domain": "channel.example", "seller_type": "PUBLISHER"}, + {"seller_id": "pub-1002", "seller_type": "PUBLISHER", "is_confidential": 1} + ] +} diff --git a/wai/live-conformance/ads/supply.too-deep.sellers.json b/wai/live-conformance/ads/supply.too-deep.sellers.json new file mode 100644 index 000000000..8ffed128e --- /dev/null +++ b/wai/live-conformance/ads/supply.too-deep.sellers.json @@ -0,0 +1 @@ +{"ext": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], "sellers": [{"seller_id": "pub-4001", "seller_type": "PUBLISHER"}]} diff --git a/wai/live-conformance/ads/vectors.json b/wai/live-conformance/ads/vectors.json index ba05de684..18fce9c97 100644 --- a/wai/live-conformance/ads/vectors.json +++ b/wai/live-conformance/ads/vectors.json @@ -3133,6 +3133,5463 @@ "receipt", 1 ] + }, + { + "name": "consent_and_chain", + "about": "the live break under a policy of version 3: the viewer at the break's start gave a GPP string and its applicable section, and its two ads answered two requests, the first sold from the channel's own account at an SSP through an exchange and the second from the channel's own account at the exchange, two complete chains whose sellers files and app-ads.txt the verifier holds; the viewer who joins gave no string and a child-directed request, and its ad was sold through an incomplete chain of one node whose sellers file the verifier does not hold", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "706daac741ac5c0d05a3527a2a01d84cef781c3b697e2b523be594e9760f805619a86b18f4494aa9617e7f433b857bef308c70853756ba14d412638f57b0d701" + }, + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "0f041639e63167e41cc74c151102c01b", + "start_offset_ms": 12000, + "format": 2, + "asset_list_sha256": "0c1b410693e1a2fb79b2c2d8c0bb3ec16baa450f8a4936464675dc2c1ac768a7", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 1930000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + } + ], + "merkle_root": "70c4ff5e864102c0a890a33c5e4b340cbb9f4d2f1e3d41a870bf36a6b1968d09", + "budget_spent_micros": 40000, + "work_decide_ops": 3, + "joules_micro": 900, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 30000, + "window_us": 2000000 + }, + "energy_counter_filtering": "Unknown", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "", + "gpp_sid": [], + "child_directed": 1 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 0, + "nodes": [ + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": "93fc5b52fdce26953412425fa225b4090e86632a166fb674c0e5cf06ef4b7a34" + } + ], + "authorised": null + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "21e114a19c9c0c950179808d89900382177cbe9de50e780438484ade50b642a9056345daf2948e27b0e9ec0faac220967bd377fd9810ab9753165d176046c108" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ], + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "accept", + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "breaks": [ + { + "digest": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "id": "wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "event_id": 5068147555119423, + "author": "ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9", + "cue_id": 101, + "t": 60000000, + "type": "out", + "placement": "mid", + "planned_ms": 0, + "avail_ms": 30000, + "duration_ms": 30000, + "closed_by": "e97d8be15b595855a21ac9fc5a5820d64303f96f71d860c587cbb4069ac08b6b", + "end": 90000000, + "hls_list_uri": "https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.json?_HLS_interstitial_id=wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "dash_list_uri": "https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.mpd" + } + ], + "chapters": [], + "hls": "consent_and_chain.m3u8", + "dash": "", + "dash_open": [ + { + "break": 0, + "event": "" + } + ], + "receipt_facts": [ + { + "identity": "5a0ca4d742449e6712b140c2a9d91b5e2056cf7aa8cc2a4a7afbf3911d656548", + "asset_list": "{\"ASSETS\":[{\"URI\":\"https://cdn.rooms.example/creatives/8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60.m3u8\",\"DURATION\":15.000},{\"URI\":\"https://cdn.rooms.example/creatives/ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814.m3u8\",\"DURATION\":15.000}]}", + "spend_micros": 70000, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "sellers_checked": true, + "seller_type": "PUBLISHER", + "relationship": "DIRECT" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "sellers_checked": true, + "seller_type": "INTERMEDIARY", + "relationship": "RESELLER" + } + ], + "authorised_checked": true + }, + { + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "sellers_checked": true, + "seller_type": "PUBLISHER", + "relationship": "DIRECT" + } + ], + "authorised_checked": true + } + ] + }, + { + "identity": "72f5b643020c2680bd847edbabdd2119dececa4193594d9578d42704aacc6c5a", + "asset_list": "\nhttps://cdn.rooms.example/creatives/8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60.mpd", + "spend_micros": 40000, + "consent": { + "gpp": "", + "gpp_sid": [], + "child_directed": 1 + }, + "supply_chains": [ + { + "complete": 0, + "nodes": [ + { + "asi": "exchange.example", + "sid": "ssp-77", + "sellers_checked": false, + "seller_type": null, + "relationship": null + } + ], + "authorised_checked": false + } + ] + } + ], + "spend_micros": 110000 + }, + { + "name": "child_room", + "about": "a 15 s break in a room whose policy marks it as directed to children: the viewer gave no GPP string, and the decider states the request child-directed and no supply chain for its ad", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90000000000000000000000000000000000000000000000000000000000000000000000000000000040a5ae020000000000020a000415000000983a000000c9dd9b0483c4f4e29dfde3ef65a3e06ec25230504147d45e9b4769beb7b103a36941339b7f9309258296719ed7a5e408e0718795f9948218b55519afd98d310a" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000001c8b59866291a727237ca9c8e8174e9bd9c744b92bb42100def52d1c66af304d991c04daa4c233e3185e3d416eb561676e816761beb725bd591082b19e0643a0d", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0", + "session": "7f81c27ec0fad3a49d0453480b8281d9", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "1edcb37a85fc3d98630f51a37f1e95568d7a3fe84ed073de9129af0061d35fad", + "avail_ms": 15000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + } + ], + "merkle_root": "70c4ff5e864102c0a890a33c5e4b340cbb9f4d2f1e3d41a870bf36a6b1968d09", + "budget_spent_micros": 40000, + "work_decide_ops": 3, + "joules_micro": 0, + "energy_provenance": null, + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "", + "gpp_sid": [], + "child_directed": 1 + }, + "supply_chains": [ + null + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "21d438959074ae2bf350d2ea31c0805a5762a81f57612f8d7fb0e6f8c3bd49467389795203c226062f94ee95c4e075abbbb3082cb6a25bdfd953a6f2904bec05" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "outcome": "accept", + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "breaks": [ + { + "digest": "e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0", + "id": "wai-ad-e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0", + "event_id": 843293208057698, + "author": "ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9", + "cue_id": 21, + "t": 45000000, + "type": "break", + "placement": "mid", + "planned_ms": 15000, + "avail_ms": 15000, + "duration_ms": 15000, + "closed_by": null, + "end": 60000000, + "hls_list_uri": "https://ads.rooms.example/sgai/wai-ad-e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0.json?_HLS_interstitial_id=wai-ad-e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0", + "dash_list_uri": "https://ads.rooms.example/sgai/wai-ad-e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0.mpd" + } + ], + "chapters": [], + "hls": "child_room.m3u8", + "dash": "", + "dash_open": [], + "receipt_facts": [ + { + "identity": "894dbffd1f3f96c90f2f4f56552a52ffeeb4737bbf1a710135897caf43f62367", + "asset_list": "{\"ASSETS\":[{\"URI\":\"https://cdn.rooms.example/creatives/8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60.m3u8\",\"DURATION\":15.000}]}", + "spend_micros": 40000, + "consent": { + "gpp": "", + "gpp_sid": [], + "child_directed": 1 + }, + "supply_chains": [ + null + ] + } + ], + "spend_micros": 40000 + }, + { + "name": "authorised_read_liberally", + "about": "both of the first viewer's ads answered one request, so its receipt states one chain twice: four nodes, each authorised by the channel's ads.txt only when it is read by §8.7's rules: a record of four fields, a record whose advertising system is in other letter cases and that ends with CR LF, a record whose relationship is in lower case and that ends with a CR alone, and a record with an extension field", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-3001", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": null + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": null + }, + { + "asi": "wholesale.example", + "sid": "seat-5", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": null + }, + { + "asi": "bridge.example", + "sid": "br-12", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": null + } + ], + "authorised": { + "file": "ads.txt", + "domain": "channel.example", + "sha256": "fc2656ad3df75002606ccaef6642d30d288bef6669f1f5c3c57a151983c4754c" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-3001", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": null + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": null + }, + { + "asi": "wholesale.example", + "sid": "seat-5", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": null + }, + { + "asi": "bridge.example", + "sid": "br-12", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": null + } + ], + "authorised": { + "file": "ads.txt", + "domain": "channel.example", + "sha256": "fc2656ad3df75002606ccaef6642d30d288bef6669f1f5c3c57a151983c4754c" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "b71f635287423bc548a5d14df517faf85eac9de31885711e75923219d25d7921757029ab22e60fc4524349d32e294bcbbb532dfedbee86ed36e3c9000f64a801" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.channel.example.ads.txt" + ], + "outcome": "accept", + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "breaks": [ + { + "digest": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "id": "wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "event_id": 5068147555119423, + "author": "ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9", + "cue_id": 101, + "t": 60000000, + "type": "out", + "placement": "mid", + "planned_ms": 0, + "avail_ms": 30000, + "duration_ms": 30000, + "closed_by": "e97d8be15b595855a21ac9fc5a5820d64303f96f71d860c587cbb4069ac08b6b", + "end": 90000000, + "hls_list_uri": "https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.json?_HLS_interstitial_id=wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "dash_list_uri": "https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.mpd" + } + ], + "chapters": [], + "hls": "authorised_read_liberally.m3u8", + "dash": "", + "dash_open": [ + { + "break": 0, + "event": "" + } + ], + "receipt_facts": [ + { + "identity": "a662db5cccf9371a7a47ee6a5fe3f0c5f569d81d906f51e8ce1bfb0bcc461f7f", + "asset_list": "{\"ASSETS\":[{\"URI\":\"https://cdn.rooms.example/creatives/8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60.m3u8\",\"DURATION\":15.000},{\"URI\":\"https://cdn.rooms.example/creatives/ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814.m3u8\",\"DURATION\":15.000}]}", + "spend_micros": 70000, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-3001", + "sellers_checked": false, + "seller_type": null, + "relationship": "DIRECT" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "sellers_checked": false, + "seller_type": null, + "relationship": "RESELLER" + }, + { + "asi": "wholesale.example", + "sid": "seat-5", + "sellers_checked": false, + "seller_type": null, + "relationship": "RESELLER" + }, + { + "asi": "bridge.example", + "sid": "br-12", + "sellers_checked": false, + "seller_type": null, + "relationship": "RESELLER" + } + ], + "authorised_checked": true + }, + { + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-3001", + "sellers_checked": false, + "seller_type": null, + "relationship": "DIRECT" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "sellers_checked": false, + "seller_type": null, + "relationship": "RESELLER" + }, + { + "asi": "wholesale.example", + "sid": "seat-5", + "sellers_checked": false, + "seller_type": null, + "relationship": "RESELLER" + }, + { + "asi": "bridge.example", + "sid": "br-12", + "sellers_checked": false, + "seller_type": null, + "relationship": "RESELLER" + } + ], + "authorised_checked": true + } + ] + } + ], + "spend_micros": 70000 + }, + { + "name": "sellers_read_by_grammar", + "about": "the first viewer's first ad sold through the gateway's seller pub-4001, listed in a sellers file that reads only when what §8.7 does not read is read by RFC 8259's grammar alone: numbers past binary64's range and of 400 digits, escapes of lone surrogates in skipped members, a member nested 64 deep with the file's object, and a seller id that escapes one of its characters; the second ad states no chain", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 0, + "nodes": [ + { + "asi": "gateway.example", + "sid": "pub-4001", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": "e16c38802f2899ab4cadd218bc3a11589188f8e2e0db98d6e8377de4fefab137" + } + ], + "authorised": null + }, + null + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "a3b1d5b615af34380403d961865bfdc3501938de839079f369dd03393f2139ce78a46fbf2287b0017ac1a4ba01acb46be34a82f783045097e0970d616cf0a108" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.gateway.example.sellers.json" + ], + "outcome": "accept", + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "breaks": [ + { + "digest": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "id": "wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "event_id": 5068147555119423, + "author": "ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9", + "cue_id": 101, + "t": 60000000, + "type": "out", + "placement": "mid", + "planned_ms": 0, + "avail_ms": 30000, + "duration_ms": 30000, + "closed_by": "e97d8be15b595855a21ac9fc5a5820d64303f96f71d860c587cbb4069ac08b6b", + "end": 90000000, + "hls_list_uri": "https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.json?_HLS_interstitial_id=wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "dash_list_uri": "https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.mpd" + } + ], + "chapters": [], + "hls": "sellers_read_by_grammar.m3u8", + "dash": "", + "dash_open": [ + { + "break": 0, + "event": "" + } + ], + "receipt_facts": [ + { + "identity": "f62d9ab591adb6cb4431845c7834fbf08f91494edd3dcc0fc486169d8fa76d9a", + "asset_list": "{\"ASSETS\":[{\"URI\":\"https://cdn.rooms.example/creatives/8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60.m3u8\",\"DURATION\":15.000},{\"URI\":\"https://cdn.rooms.example/creatives/ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814.m3u8\",\"DURATION\":15.000}]}", + "spend_micros": 70000, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "complete": 0, + "nodes": [ + { + "asi": "gateway.example", + "sid": "pub-4001", + "sellers_checked": true, + "seller_type": "PUBLISHER", + "relationship": null + } + ], + "authorised_checked": false + }, + null + ] + } + ], + "spend_micros": 70000 + }, + { + "name": "receipt_consent_altered", + "about": "the first viewer's receipt with the last section of its GPP string changed after the decider signed it", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YYN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "706daac741ac5c0d05a3527a2a01d84cef781c3b697e2b523be594e9760f805619a86b18f4494aa9617e7f433b857bef308c70853756ba14d412638f57b0d701" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "bad_signature", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_child_flag_cleared", + "about": "the child room's receipt with its child-directed flag cleared after the decider signed it", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90000000000000000000000000000000000000000000000000000000000000000000000000000000040a5ae020000000000020a000415000000983a000000c9dd9b0483c4f4e29dfde3ef65a3e06ec25230504147d45e9b4769beb7b103a36941339b7f9309258296719ed7a5e408e0718795f9948218b55519afd98d310a" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000001c8b59866291a727237ca9c8e8174e9bd9c744b92bb42100def52d1c66af304d991c04daa4c233e3185e3d416eb561676e816761beb725bd591082b19e0643a0d", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0", + "session": "7f81c27ec0fad3a49d0453480b8281d9", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "1edcb37a85fc3d98630f51a37f1e95568d7a3fe84ed073de9129af0061d35fad", + "avail_ms": 15000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + } + ], + "merkle_root": "70c4ff5e864102c0a890a33c5e4b340cbb9f4d2f1e3d41a870bf36a6b1968d09", + "budget_spent_micros": 40000, + "work_decide_ops": 3, + "joules_micro": 0, + "energy_provenance": null, + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "", + "gpp_sid": [], + "child_directed": 0 + }, + "supply_chains": [ + null + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "21d438959074ae2bf350d2ea31c0805a5762a81f57612f8d7fb0e6f8c3bd49467389795203c226062f94ee95c4e075abbbb3082cb6a25bdfd953a6f2904bec05" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "outcome": "refuse", + "reason": "bad_signature", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_chain_altered", + "about": "the first viewer's receipt with the seller id of its first chain's first node changed after the decider signed it", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1002", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "706daac741ac5c0d05a3527a2a01d84cef781c3b697e2b523be594e9760f805619a86b18f4494aa9617e7f433b857bef308c70853756ba14d412638f57b0d701" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "bad_signature", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_chains_swapped", + "about": "the first viewer's receipt with its two impressions' chains swapped after the decider signed it: each chain is bound to its impression", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "706daac741ac5c0d05a3527a2a01d84cef781c3b697e2b523be594e9760f805619a86b18f4494aa9617e7f433b857bef308c70853756ba14d412638f57b0d701" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "bad_signature", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_version_three", + "about": "a receipt stating version 3, which §8.2 does not define", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 3, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "706daac741ac5c0d05a3527a2a01d84cef781c3b697e2b523be594e9760f805619a86b18f4494aa9617e7f433b857bef308c70853756ba14d412638f57b0d701" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "receipt_malformed", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_v1_with_consent", + "about": "a receipt of version 1, with no version member, carrying a consent signal", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "33e44911a03df215447afca25b44ff24d6fa2682905aab4178355269694fcd7591e676001be8d95e2befd704f51c6fc1e957b42ef560d9cd7bb5d5bc80603c0e", + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + } + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "receipt_malformed", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_v2_without_chains", + "about": "a receipt of version 2 without its member supply_chains", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "sig": "706daac741ac5c0d05a3527a2a01d84cef781c3b697e2b523be594e9760f805619a86b18f4494aa9617e7f433b857bef308c70853756ba14d412638f57b0d701", + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "receipt_malformed", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_gpp_empty_section", + "about": "a receipt whose GPP string has an empty part between two delimiters", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "f692a356551a9510cba25b7d4e938098a2ee9e9c75eb4a563286ac724ea291418e1c471017bbb413ec9b29fec0c5436f9810bea8ddda9e5fdc96c42e15caca05" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "consent_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_gpp_not_url_safe", + "about": "a receipt whose GPP string holds a +, a character of base64 that is not among the URL-safe ones the GPP string's text lists", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB+CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "f1dad440a36fbe5f7cd43f2c87b308d921307c5e0985973522faa419b8bc63e5bd2c9346d60067bbc8e2a69ad9e9889ac2f84bd035b7d30a631f3636b4eacc00" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "consent_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_sections_without_string", + "about": "a receipt stating section 2 as applicable, with no GPP string", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "380a177db88e822075658c033c4d0252282e8c1effbea59675706d20da6aad4f75817046a67ed4a3952fbef1a7509de3e44f15ff7d1b8a9e7a2967d733c94f0f" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "consent_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_section_repeated", + "about": "a receipt stating section 2 twice", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2, + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "4ba3256609a914a431db3361bc07d05389bb7fa543cf92ea9b93960716d36af3ce44df21a8c79badeb317fedb50d3b57bd0ae6fd33e904a07173ba0b748fd109" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "consent_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_child_flag_two", + "about": "a receipt whose child-directed flag is 2", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 2 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "235b785fc4dd8afc84000f249d60dc6c74c0e0d105199a8309386cd7f9df4403acae22c8ce1d865666ebfbfbc4769136cea76f45abf84a2e9017e153006b0d00" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "consent_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_chain_ver", + "about": "a receipt whose first chain states its version as 1, with no minor part", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "50440b3bebd013edaf2b1056697d7d0fd7eacc98dd9f4032026f767e0e835cd806b2a556c0725b50ccacc2cb04b341b0e2236dac99e10aace525b3e619e0fb0d" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "supply_chain_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_chain_complete_two", + "about": "a receipt whose first chain states complete as 2", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 2, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "612b9da4ed290e2935832fa0911448854c06548977be14944c9d30ff867d42039aaf9290c8f8af321f1646b17f9c50da5f7ca59765190901477f39fe1a2a600d" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "supply_chain_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_asi_url", + "about": "a receipt whose first chain names its first advertising system by a URL, not a domain", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "https://ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "540d5d2b6e1c87e240a2c12904ec98f50d4b0d5b79b10fc0cc96b66015ba68ab599387a497606130949ce55279a06bf92a574653d42fc16f5f319dd35d076d06" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "supply_chain_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_authorised_file", + "about": "a receipt whose first chain names, as its authorised-sellers file, a file that is neither ads.txt nor app-ads.txt", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "sellers.json", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "e26129fc76696dc12a7c073f698aba30414bdd668a9706823d68aba6699e11a39d3a97a7333f7acde5072822d62a6935ab3ec37c27a3b177407521ecffcb4d06" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "supply_chain_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_authorised_domain", + "about": "a receipt whose first chain names its authorised-sellers file at a host of one label", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "df987f8ae42eb2eaf35d63e44acb4716e165acab285bedc7eb3639777334cebd13f5a412d3be40bee67d3bb3a98b04f6c660d5e42d4e873a18990352ab150f00" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "supply_chain_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_chains_count", + "about": "a receipt of two impressions stating one chain", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "53acdba323337f240c8ead3dd7babb93e95adb0a27a82a586c95924dd7e04619d1a792beee4da9404f12276d37424ae59e3a8f91420668fe05c12c1f4a31ab07" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "supply_chain_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "receipt_without_terms", + "about": "a receipt of version 1, which states no consent signal, under a policy of version 3", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "33e44911a03df215447afca25b44ff24d6fa2682905aab4178355269694fcd7591e676001be8d95e2befd704f51c6fc1e957b42ef560d9cd7bb5d5bc80603c0e" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "request_terms_absent", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "child_flag_unset", + "about": "the child room's receipt signed with its child-directed flag 0", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90000000000000000000000000000000000000000000000000000000000000000000000000000000040a5ae020000000000020a000415000000983a000000c9dd9b0483c4f4e29dfde3ef65a3e06ec25230504147d45e9b4769beb7b103a36941339b7f9309258296719ed7a5e408e0718795f9948218b55519afd98d310a" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000001c8b59866291a727237ca9c8e8174e9bd9c744b92bb42100def52d1c66af304d991c04daa4c233e3185e3d416eb561676e816761beb725bd591082b19e0643a0d", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "e682fef8801b976232efcecc820d195b48696a1a61d6722d948db7a6a180e6b0", + "session": "7f81c27ec0fad3a49d0453480b8281d9", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "1edcb37a85fc3d98630f51a37f1e95568d7a3fe84ed073de9129af0061d35fad", + "avail_ms": 15000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + } + ], + "merkle_root": "70c4ff5e864102c0a890a33c5e4b340cbb9f4d2f1e3d41a870bf36a6b1968d09", + "budget_spent_micros": 40000, + "work_decide_ops": 3, + "joules_micro": 0, + "energy_provenance": null, + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "", + "gpp_sid": [], + "child_directed": 0 + }, + "supply_chains": [ + null + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "f906bfdaaa91bc443d5de6e047c7fc01836875ae6417c244b85ae51ee4831c1807400abe6fc8152fe0be83d82bd5b4108232ce56faf8610cd627c68aefd6ec07" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "outcome": "refuse", + "reason": "child_directed_unset", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "sellers_file_malformed", + "about": "a chain whose first node names, as its sellers file, a file the verifier holds that has no sellers", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "2cdfde28f802c589637eff58fe37804c937ce46dac46e38b497712aa8261ce0f" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "c8fd5cdf54e4c6a313b2cc7457588b7f41eb30461cf22c4feac7dad4f8c529be93c7fafb3115a9064573ce1b06e0bfbebc63e21c4b1ae636c7887093a167980e" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt", + "supply.not-sellers.json" + ], + "outcome": "refuse", + "reason": "supply_file_malformed", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "sellers_file_too_deep", + "about": "a sellers file whose member `ext` nests 65 deep with the file's object, one past §8.7's limit", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 0, + "nodes": [ + { + "asi": "gateway.example", + "sid": "pub-4001", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": "d7db0b87704727a5baf6c9600b5d0f28057d56a8334d2aadd55fe460d29b8e2c" + } + ], + "authorised": null + }, + null + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "f4782b742c6b8c5c53f2a1a16f06186aaf66693fe609170768c06128c436dcae928cb389a58a95f9380edf6269306466128c6689c03698c3241dfe88350ce808" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.too-deep.sellers.json" + ], + "outcome": "refuse", + "reason": "supply_file_malformed", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "sellers_file_lone_surrogate", + "about": "a sellers file whose second seller's id escapes a lone surrogate", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 0, + "nodes": [ + { + "asi": "gateway.example", + "sid": "pub-4001", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": "82667107dd1aade782b9e9f62e154343db32ce3fb2e91ea06dc64aa309497b78" + } + ], + "authorised": null + }, + null + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "0c85b4f200035448e172228fbffb303acdf917e27b140041249eda9e9a7f7367222308a652222c36a86b1ca2e015d5a63750d4727d7a8a3d98de12918288330c" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.lone-surrogate.sellers.json" + ], + "outcome": "refuse", + "reason": "supply_file_malformed", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "sellers_file_sellers_twice", + "about": "a sellers file with two members `sellers`", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 0, + "nodes": [ + { + "asi": "gateway.example", + "sid": "pub-4001", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": "750dd1d3630030f16cf3efdafb38297d65376bda3cbf39d5abd86840981e0e35" + } + ], + "authorised": null + }, + null + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "c0019e924c2ac3d19701a3e8307c11c73b834c78c3514b4595a25fa82b2aca1124a0b54d17d8d23721b8ef3731588aa2531f35011ebc6cdf0e64f2b9bacaec0e" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.sellers-twice.sellers.json" + ], + "outcome": "refuse", + "reason": "supply_file_malformed", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "sellers_file_nan", + "about": "a sellers file holding NaN, which is no JSON number", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 0, + "nodes": [ + { + "asi": "gateway.example", + "sid": "pub-4001", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": "65fcee1d8885e0409c2cfdd4d8136cf00fbdada7aaa583265e876936dd292f63" + } + ], + "authorised": null + }, + null + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "ed00cc646d443524d9ae5a1ff390f774c562be53b8d2b77a8c9c351c2907250aececf3464f4d3f0c86732923baa95253598bec801ce3d18334deb1768ff4800e" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.nan.sellers.json" + ], + "outcome": "refuse", + "reason": "supply_file_malformed", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "authorised_file_not_utf8", + "about": "a complete chain checked against an app-ads.txt that is not UTF-8", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "5e4f7e33c2cc5c82d86cd61385771d98b2c48a8e7e7486533ed224e6d61ff6ed" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "af7739ec6c610e736777e0446c25fc579cdde1210bd0c3f70cc6c0179389404627f32aabc770a3da1fd3061724f97949af04a60217101996d4f279e73246c409" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt", + "supply.not-utf8.app-ads.txt" + ], + "outcome": "refuse", + "reason": "supply_file_malformed", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "seller_unlisted", + "about": "a chain whose first node's seller id the SSP's sellers file does not list", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1003", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "b5c47e59118f40fca045c34ca3613ee65e31223f6708d00203a55583d740c59ce62a9825c0edcf2db25a0f717688e545be617d7e2bf7bce31f5a67d6a0c5380a" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "supply_seller_unlisted", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "seller_unauthorised", + "about": "a chain stated complete whose first node is the SSP's account at the exchange, which the channel's app-ads.txt lists as a reseller, not as its own", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "ae9516301a0b572e751a738d26f2a0a7c76608ace28e3e410cf00d4fcb210477e709ad95606a00ab0496c3b269238e8583e9a7941b13cf754c771b17b5f9030d" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "supply_seller_unauthorised", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "seller_absent_from_authorised", + "about": "a chain whose third node, the exchange's account at a wholesaler, the channel's app-ads.txt does not name", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": "req-60-1", + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-77", + "hp": 1, + "rid": "req-60-1-x", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + }, + { + "asi": "wholesale.example", + "sid": "seat-9", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": null + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "72a3976923de5cbec376e18b98be63e23f22cbe5e7c8a4afc6b6d6e5d87d3009ac99f4d305d676a9626c291da665eb82fb086a43e600d224320fa88cb711890d" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt" + ], + "outcome": "refuse", + "reason": "supply_seller_unauthorised", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "authorised_record_passed_over", + "about": "a complete chain whose second node, the SSP's account ssp-99 at the exchange, is named in the channel's app-ads.txt only by lines a reader passes over: a comment, and a line of five fields", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00000000000000a8b1db8a489b56e5b9bcdb60d5067b5187d93464b587054b345617b853a85363ba9ae888d226d035da6b28dcffec1804a85832e740e9467c4f3142f726a9940b", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "version": 2, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + }, + "supply_chains": [ + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "ssp.example", + "sid": "pub-1001", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": "1f346895b7efb91dc92105313957ec0aa5e292fa8f6d6ecf688eee0a8e8155f6" + }, + { + "asi": "exchange.example", + "sid": "ssp-99", + "hp": 1, + "rid": null, + "name": null, + "domain": null, + "sellers": null + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "80f6c43eab431b4fc49d2a1645b6e85b0d4f57b9c4fb8faf6c7dae5339efce86" + } + }, + { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "exchange.example", + "sid": "pub-2001", + "hp": 1, + "rid": "req-60-2", + "name": null, + "domain": null, + "sellers": "c1eaadf61d07e2bba4ff70d13f64c483151910dca60f73390d9b9f03bfd55c03" + } + ], + "authorised": { + "file": "app-ads.txt", + "domain": "channel.example", + "sha256": "2b07ce82075fe89bb5997f0cc904f7da3875c75dce35d0ca341ec2423e34425a" + } + } + ], + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "8141cf9c8ee8b53b5e3bdf94b967dab6e440e76af2e9c9bc0704b674e2c43737fa1dfe74a0f587fee984203548e74e957c219c2ab5bd57558e633694ef2f9a06" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "supply_files": [ + "supply.ssp.example.sellers.json", + "supply.exchange.example.sellers.json", + "supply.channel.example.app-ads.txt", + "supply.passed-over.app-ads.txt" + ], + "outcome": "refuse", + "reason": "supply_seller_unauthorised", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "policy_audience_bit", + "about": "a policy of version 3 whose audience sets a bit above bit 0", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031030cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f000000000000025468314b7f38e355d978c256a3fc8f2e9c68b50a5b3f81c88b86d6663859e78fe144772f84e3dc0ac0195e95368471c1e6f42d7a2e86041976e9a1fa59457e0a", + "pt0_ms": 0, + "receipts": [], + "pods": [], + "outcome": "refuse", + "reason": "ad_policy_malformed", + "at": [ + "policy" + ] } ] } diff --git a/wai/live-conformance/live_ads_verify.py b/wai/live-conformance/live_ads_verify.py index ebcb6795b..8f27e1942 100644 --- a/wai/live-conformance/live_ads_verify.py +++ b/wai/live-conformance/live_ads_verify.py @@ -13,10 +13,16 @@ is verified by the live-rooms corpus's own second implementation, live_rooms_ver - the HLS interstitial, closing and preload tags (§5) and the DASH events (§6), byte for byte; - the decision and the asset lists (§7), byte for byte; - the break form of the wai.video.ssai receipt (§8): its JSON carriage, hashes, signing - payload, identity and rules; -- verification, from the room to the spend (§9). + payload, identity and rules, and a receipt of version 2's request terms: the consent signal + (§8.6) and the supply chain of each impression (§8.7), their rules and signed encodings, and + the sellers files and authorised-sellers files a chain is checked against, read here by + §8.7: a sellers file by RFC 8259's grammar outside what §8.7 reads, its numbers never + converted; +- verification, from the room to the spend (§9), and steps 13 and 14 under a policy of + version 3 and for each chain a receipt states. -A policy of version 2 is read here with its presentation fields (§3.1, ad-presentation §8). A +A policy of version 2 or 3 is read here with its presentation fields (§3.1, ad-presentation §8), +and one of version 3 with its audience. A room whose policy requires presentation claims is verified with ad_presentation_verify.py, which gives accept() the judge of those claims (ad-presentation §9). @@ -52,6 +58,7 @@ EXTENSION = os.path.join(HERE, "..", "extensions", "live-ads.md") POLICY_MAGIC, ANNOTATION_MAGIC = b"WAP1", b"WAB1" POLICY_DOMAIN = b"wai:live-ads-policy\x01" # §3.2 BREAK_DOMAIN = b"wai:video-ssai-break-receipt\x01" # §8.4 +TERMS_DOMAIN = b"wai:video-ssai-break-receipt\x02" # §8.4, version 2 ID_DOMAIN = b"wai:video-ssai-receipt-id\x01" POD_DOMAIN, IMP_DOMAIN = b"wai:video-ssai-pod\x01", b"wai:video-ssai-imp\x01" # §8.3 LEAF, NODE = b"moq-jwp:merkle-leaf\x01", b"moq-jwp:merkle-node\x01" # jwp-receipts §2.2 @@ -112,7 +119,7 @@ def read_policy(b): raise Refuse("malformed") if len(b) < 5: raise Refuse("truncated") - if b[4] not in (1, 2): + if b[4] not in (1, 2, 3): raise Refuse("unknown_version") r = LEV.Reader(b, "truncated") r.take(5) @@ -121,15 +128,17 @@ def read_policy(b): p["asset_base"] = r.take(r.u16()) p["creative_base"] = r.take(r.u16()) p["presentation"] = None - if b[4] == 2: # ad-presentation §8 + if b[4] >= 2: # ad-presentation §8 p["presentation"] = {"require": r.u8(), "rule": r.u8(), "token_type": r.u16(), "issuer": r.take(r.u16())} + p["audience"] = r.u8() if b[4] == 3 else None # §3.1 p["body"] = b[:r.p] p["sig"] = r.take(64) if not r.done(): raise Refuse("malformed") if (not 1 <= p["slots"] <= 16 or p["restrict"] > 3 or p["snap"] > 3 or not is_base(p["asset_base"]) or not is_base(p["creative_base"]) - or (p["presentation"] is not None and not presentation_ok(p["presentation"]))): + or (p["presentation"] is not None and not presentation_ok(p["presentation"])) + or (p["audience"] is not None and p["audience"] > 1)): raise Refuse("ad_policy_malformed") p["asset_base"], p["creative_base"] = p["asset_base"].decode(), p["creative_base"].decode() return p @@ -149,6 +158,11 @@ def requires_claims(p): return p["presentation"] is not None and p["presentation"]["require"] & 1 == 1 +def child_room(p): + """§3.1: the policy's audience sets child.""" + return p["audience"] is not None and p["audience"] & 1 == 1 + + # ---------------------------------------------------------------- §4 breaks def read_annotation(b): @@ -407,7 +421,10 @@ def read_receipt(o): if label is not None and label not in EVIDENCE: raise Refuse("receipt_malformed") want = set(MEMBERS) | set(EVIDENCE.get(label, [])) - if set(o) != want or u(o["form"]) != 2: + v2 = "version" in o # §8.2: version 2 + if v2: + want |= {"version", "consent", "supply_chains"} + if set(o) != want or u(o["form"]) != 2 or (v2 and u(o["version"]) != 2): raise Refuse("receipt_malformed") r = {"room": hexn(o["room"], 32), "cue": hexn(o["cue"], 32), "session": hexn(o["session"], 16), "start_offset_ms": u(o["start_offset_ms"]), "format": u(o["format"], 255), @@ -447,9 +464,262 @@ def read_receipt(o): if label == "CalibratedInstrument": c["ref"] = text(o["energy_calibration_ref"]) r["class"] = c + r["terms"] = read_terms(o["consent"], o["supply_chains"]) if v2 else None return r +def exactly(o, members): + if not isinstance(o, dict) or set(o) != set(members): + raise Refuse("receipt_malformed") + return o + + +def opt_text(v): + return None if v is None else text(v) + + +def read_terms(consent, chains): + """§8.6, §8.7: the JSON carriage of a receipt of version 2's request terms.""" + c = exactly(consent, ["gpp", "gpp_sid", "child_directed"]) + if not isinstance(c["gpp_sid"], list) or not isinstance(chains, list): + raise Refuse("receipt_malformed") + return {"consent": {"gpp": text(c["gpp"]), "gpp_sid": [u(x, 65535) for x in c["gpp_sid"]], + "child_directed": u(c["child_directed"], 255)}, + "supply_chains": [None if ch is None else read_chain(ch) for ch in chains]} + + +def read_chain(chain): + """§8.7: one entry of supply_chains that is not null.""" + ch = exactly(chain, ["ver", "complete", "nodes", "authorised"]) + if not isinstance(ch["nodes"], list): + raise Refuse("receipt_malformed") + nodes = [] + for n in ch["nodes"]: + n = exactly(n, ["asi", "sid", "hp", "rid", "name", "domain", "sellers"]) + nodes.append({"asi": text(n["asi"]), "sid": text(n["sid"]), "hp": u(n["hp"], 255), "rid": opt_text(n["rid"]), + "name": opt_text(n["name"]), "domain": opt_text(n["domain"]), + "sellers": None if n["sellers"] is None else hexn(n["sellers"], 32)}) + a = ch["authorised"] + if a is not None: + a = exactly(a, ["file", "domain", "sha256"]) + a = {"file": text(a["file"]), "domain": text(a["domain"]), "sha256": hexn(a["sha256"], 32)} + return {"ver": text(ch["ver"]), "complete": u(ch["complete"], 255), "nodes": nodes, "authorised": a} + + +GPP_CHARS = set(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~") + + +def consent_ok(c): + """§8.6's rules.""" + g = c["gpp"].encode("utf-8") + if len(g) > 65535 or any(ch not in GPP_CHARS for ch in g): + return False + if g and any(part == b"" for part in g.split(b"~")): + return False + sid = c["gpp_sid"] + if len(sid) > 16 or any(x < 1 for x in sid) or len(set(sid)) != len(sid) or (not g and sid): + return False + return c["child_directed"] in (0, 1) + + +def domain_ok(d): + """§8.7: a domain name.""" + b = d.encode("utf-8") + if not 1 <= len(b) <= 253: + return False + labels = b.split(b".") + alnum = set(b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-") + return len(labels) >= 2 and all(1 <= len(lb) <= 63 and all(ch in alnum for ch in lb) and lb[:1] != b"-" + and lb[-1:] != b"-" for lb in labels) + + +def text_ok(v): + return 1 <= len(v.encode("utf-8")) <= 255 + + +def chain_ok(ch): + """§8.7's rules.""" + major, dot, minor = ch["ver"].partition(".") + digits = set("0123456789") + if not (dot and major and minor and set(major) <= digits and set(minor) <= digits and len(ch["ver"]) <= 15): + return False + if ch["complete"] not in (0, 1) or not 1 <= len(ch["nodes"]) <= 32: + return False + for n in ch["nodes"]: + if not (domain_ok(n["asi"]) and text_ok(n["sid"]) and n["hp"] in (0, 1) + and (n["rid"] is None or text_ok(n["rid"])) and (n["name"] is None or text_ok(n["name"])) + and (n["domain"] is None or domain_ok(n["domain"]))): + return False + a = ch["authorised"] + return a is None or (a["file"] in ("ads.txt", "app-ads.txt") and domain_ok(a["domain"])) + + +def text_bytes(v): + b = v.encode("utf-8") + return struct.pack(">I", len(b)) + b + + +def terms_bytes(t): + """§8.6, §8.7: the signed encodings of the consent signal and the supply chains.""" + c = t["consent"] + out = [text_bytes(c["gpp"]), be64(len(c["gpp_sid"]))] + [struct.pack(">H", x) for x in c["gpp_sid"]] + out.append(bytes([c["child_directed"]])) + out.append(be64(len(t["supply_chains"]))) + for ch in t["supply_chains"]: + if ch is None: + out.append(b"\x00") + continue + out += [b"\x01", text_bytes(ch["ver"]), bytes([ch["complete"]]), be64(len(ch["nodes"]))] + for n in ch["nodes"]: + out += [text_bytes(n["asi"]), text_bytes(n["sid"]), bytes([n["hp"]])] + out += [b"\x00" if n[k] is None else b"\x01" + text_bytes(n[k]) for k in ("rid", "name", "domain")] + out.append(b"\x00" if n["sellers"] is None else b"\x01" + n["sellers"]) + a = ch["authorised"] + out.append(b"\x00" if a is None else b"\x01" + text_bytes(a["file"]) + text_bytes(a["domain"]) + a["sha256"]) + return b"".join(out) + + +def ascii_lower(v): + return "".join(chr(ord(ch) + 32) if "A" <= ch <= "Z" else ch for ch in v) + + +def ascii_upper(v): + return "".join(chr(ord(ch) - 32) if "a" <= ch <= "z" else ch for ch in v) + + +def reject_constant(name): + raise ValueError("not a JSON number: " + name) + + +SELLERS_DEPTH = 64 # §8.7 + + +class Number: + """A JSON number as RFC 8259's grammar reads it, never converted to a value (§8.7).""" + + def __init__(self, written): + self.written = written + + +class Object: + """A JSON object: its members in order, a name given twice kept twice (§8.7).""" + + def __init__(self, members): + self.members = members + + +def nests_within(b, limit): + """§8.7: do the arrays and objects of the text nest at most limit deep, the outermost the + first? Brackets in strings are not counted; on a text that is not JSON it does not matter.""" + depth, in_string, escaped = 0, False, False + for c in b: + if in_string: + if escaped: + escaped = False + elif c == 0x5C: + escaped = True + elif c == 0x22: + in_string = False + elif c == 0x22: + in_string = True + elif c in (0x5B, 0x7B): + depth += 1 + if depth > limit: + return False + elif c in (0x5D, 0x7D): + depth = max(depth - 1, 0) + return True + + +def scalar_text(v): + """A string of Unicode scalar values: the JSON string escaped no lone surrogate (RFC 8259 + §7; a pair escapes one character, and UTF-8 text holds no surrogate).""" + return isinstance(v, str) and not any(0xD800 <= ord(ch) <= 0xDFFF for ch in v) + + +def read_sellers(b): + """§8.7: a sellers file's (seller_id, seller_type) pairs, or None when it does not read. + What §8.7 does not read is read by the grammar alone: numbers are kept as written, and a + string the reader skips may escape a lone surrogate.""" + try: + t = b.decode("utf-8") + except UnicodeDecodeError: + return None + if t.startswith("\ufeff") or not nests_within(b, SELLERS_DEPTH): + return None + try: + v = json.loads(t, parse_int=Number, parse_float=Number, parse_constant=reject_constant, object_pairs_hook=Object) + except ValueError: + return None + if not isinstance(v, Object) or not all(scalar_text(k) for k, _ in v.members): + return None + sellers = [x for k, x in v.members if k == "sellers"] + if len(sellers) != 1 or not isinstance(sellers[0], list): + return None + out = [] + for x in sellers[0]: + if not isinstance(x, Object) or not all(scalar_text(k) for k, _ in x.members): + return None + ids = [s for k, s in x.members if k == "seller_id"] + kinds = [s for k, s in x.members if k == "seller_type"] + if len(ids) != 1 or len(kinds) > 1 or not all(scalar_text(s) for s in ids + kinds): + return None + out.append((ids[0], kinds[0] if kinds else None)) + return out + + +def read_authorised(b): + """§8.7: an authorised-sellers file's data records, or None when it is not UTF-8.""" + try: + t = b.decode("utf-8") + except UnicodeDecodeError: + return None + out = [] + for line in t.replace("\r\n", "\n").replace("\r", "\n").split("\n"): + line = line.split("#", 1)[0].strip(" \t") + if not line: + continue + name, eq, _ = line.partition("=") + if eq and name and not any(ch in name for ch in ", \t"): + continue # a variable record + fields = [f.strip(" \t") for f in line.split(";", 1)[0].split(",")] + if 3 <= len(fields) <= 4 and all(fields): + out.append((fields[0], fields[1], fields[2])) + return out + + +def check_chain(ch, held): + """§9.3 step 14: what a verification states of the chain, or Refuse.""" + nodes = [] + for n in ch["nodes"]: + f = {"asi": n["asi"], "sid": n["sid"], "sellers_checked": False, "seller_type": None, "relationship": None} + b = held.get(n["sellers"]) if n["sellers"] is not None else None + if b is not None: + sellers = read_sellers(b) + if sellers is None: + raise Refuse("supply_file_malformed") + hit = [kind for sid, kind in sellers if sid == n["sid"]] + if not hit: + raise Refuse("supply_seller_unlisted") + f["sellers_checked"], f["seller_type"] = True, hit[0] + nodes.append(f) + checked, a = False, ch["authorised"] + if a is not None and a["sha256"] in held: + records = read_authorised(held[a["sha256"]]) + if records is None: + raise Refuse("supply_file_malformed") + for k, (n, f) in enumerate(zip(ch["nodes"], nodes)): + direct_only = k == 0 and ch["complete"] == 1 + rel = [ascii_upper(r) for system, account, r in records + if ascii_lower(system) == ascii_lower(n["asi"]) and account == n["sid"]] + rel = [x for x in rel if x == "DIRECT" or (not direct_only and x == "RESELLER")] + if not rel: + raise Refuse("supply_seller_unauthorised") + f["relationship"] = rel[0] + checked = True + return {"complete": ch["complete"], "nodes": nodes, "authorised_checked": checked} + + def b3(*parts): return B3.blake3(b"".join(parts)) @@ -499,10 +769,10 @@ def class_bytes(c): def payload(r): - """§8.4.""" + """§8.4; a receipt of version 2 under its own domain, with its request terms.""" g = r["grant"] cap, sid = g["capability"].encode(), r["signer_id"].encode() - out = [BREAK_DOMAIN, r["room"], r["cue"], r["session"], be64(r["start_offset_ms"]), bytes([r["format"]]), + out = [BREAK_DOMAIN if r.get("terms") is None else TERMS_DOMAIN, r["room"], r["cue"], r["session"], be64(r["start_offset_ms"]), bytes([r["format"]]), r["asset_list_sha256"], be64(r["avail_ms"]), be64(r["slots"]), be64(r["funds_remaining_micros"]), r["pod_hash"], r["merkle_root"], be64(r["budget_spent_micros"]), be64(r["work_decide_ops"]), be64(r["joules_micro"]), class_bytes(r["class"]), g["grant_hash"], struct.pack(">I", len(cap)), cap, @@ -510,12 +780,27 @@ def payload(r): out.append(b"\x00" if g["funds_ceiling"] is None else b"\x01" + be64(g["funds_ceiling"])) out.append(be64(len(r["impressions"]))) out += [leaf(i) for i in r["impressions"]] + if r.get("terms") is not None: + out.append(terms_bytes(r["terms"])) out += [r["signer_pubkey"], struct.pack(">I", len(sid)), sid] return b"".join(out) def own_rules(r): - """§8.5: None, or the refusal.""" + """§8.5: None, or the refusal; rule 6, a receipt of version 2's request terms, last.""" + bad = rules_one_to_five(r) + if bad or r.get("terms") is None: + return bad + if not consent_ok(r["terms"]["consent"]): + return "consent_invalid" + chains = r["terms"]["supply_chains"] + if len(chains) != len(r["impressions"]) or any(ch is not None and not chain_ok(ch) for ch in chains): + return "supply_chain_invalid" # §8.7 rules 1 to 4 + return None + + +def rules_one_to_five(r): + """§8.5 rules 1 to 5.""" if r["format"] not in (HLS, DASH): return "receipt_invalid" bad = ED.verify(r["signer_pubkey"], payload(r), r["sig"]) @@ -555,9 +840,10 @@ class Stop(Exception): self.reason, self.at = reason, at -def ads(v, judge=None): +def ads(v, judge=None, files=None): """§9: a dict of what the corpus states for vector v, and the run's HLS tags. judge, when - given, makes the judge of a policy's presentation claims (ad-presentation §9).""" + given, makes the judge of a policy's presentation claims (ad-presentation §9); files holds + the corpus's files by name, among them the supply files the vector names (§9.1).""" descriptor = bytes.fromhex(v["descriptor"]) grants = [bytes.fromhex(g) for g in v["grants"]] chains = [[bytes.fromhex(h) for h in c] for c in v["chains"]] @@ -566,13 +852,14 @@ def ads(v, judge=None): return {"outcome": "refuse", "reason": room["reason"], "at": ["room"] + room["at"]}, None if room["outcome"] == "gap": return {"outcome": "gap", "gaps": room["gaps"]}, None + supply = {sha(files[n]): files[n] for n in v.get("supply_files", [])} if files is not None else {} try: - return accept(v, descriptor, grants, chains, room, judge) + return accept(v, descriptor, grants, chains, room, judge, supply) except Stop as e: return {"outcome": "refuse", "reason": e.reason, "at": e.at}, None -def accept(v, descriptor, grants, chains, room, judge=None): +def accept(v, descriptor, grants, chains, room, judge=None, supply=None): d = LRV.read_descriptor(descriptor) held_grants, _ = LRV.check_grants(d, grants) try: # step 2 @@ -647,8 +934,23 @@ def accept(v, descriptor, grants, chains, room, judge=None): total += counted if funds is not None and (r["funds_remaining_micros"] > funds * 10 ** 6 or total > funds * 10 ** 6): raise Stop("funds_exceeded", at) + t = r["terms"] + if p["audience"] is not None: # step 13 + if t is None: + raise Stop("request_terms_absent", at) + if child_room(p) and t["consent"]["child_directed"] != 1: + raise Stop("child_directed_unset", at) fact = {"identity": identity(r).hex(), "asset_list": listed.decode(), "spend_micros": r["budget_spent_micros"]} fact.update(extra) + if t is not None: # step 14 + chains = [] + for ch in t["supply_chains"]: + try: + chains.append(None if ch is None else check_chain(ch, supply or {})) + except Refuse as e: + raise Stop(e.reason, at) + fact["consent"] = dict(t["consent"]) + fact["supply_chains"] = chains stated.append(fact) out = { "outcome": "accept", @@ -678,7 +980,7 @@ def check(doc, files, quiet=False): fails, written = [], {} for v in doc.get("vectors", []): try: - got, tags = ads(v) + got, tags = ads(v, files=files) except Exception as e: # a crash on a mutated copy is a failure to read it got, tags = None, None bad = ["the vector does not verify: %s: %s" % (type(e).__name__, e)] @@ -708,6 +1010,7 @@ def mutations(doc, files): """Copies of the corpus, each with one defect a conforming verifier must catch.""" vn = {v["name"]: k for k, v in enumerate(doc["vectors"])} live, mid, post, chap, dup = vn["live_break"], vn["mid_roll"], vn["post_roll"], vn["chapters"], vn["receipt_duplicate"] + cc, kids = vn["consent_and_chain"], vn["child_room"] def mutate(label, f): d, fs = copy.deepcopy(doc), dict(files) @@ -751,6 +1054,36 @@ def mutations(doc, files): mutate("the funds control's second receipt given once", lambda vs, fs: vs[vn["funds_exceeded"]]["receipts"].pop()), mutate("a tag file removed", lambda vs, fs: fs.pop("chapters.m3u8")), + # The request terms (§8.6, §8.7, §9.3 steps 13 and 14). + mutate("the first viewer's GPP string with a section changed", + lambda vs, fs: vs[cc]["receipts"][0]["consent"].update(gpp=vs[cc]["receipts"][0]["consent"]["gpp"].replace("1YNN", "1NNN"))), + mutate("the first viewer's applicable sections stated as 2 and 6", + lambda vs, fs: vs[cc]["receipts"][0]["consent"].update(gpp_sid=[2, 6])), + mutate("the stated child-directed flag of the joining viewer", + lambda vs, fs: vs[cc]["receipt_facts"][1]["consent"].update(child_directed=0)), + mutate("a node's seller type stated as BOTH", + lambda vs, fs: vs[cc]["receipt_facts"][0]["supply_chains"][0]["nodes"][0].update(seller_type="BOTH")), + mutate("the second node's relationship stated as DIRECT", + lambda vs, fs: vs[cc]["receipt_facts"][0]["supply_chains"][0]["nodes"][1].update(relationship="DIRECT")), + mutate("the second impression's chain stated as none", + lambda vs, fs: vs[cc]["receipt_facts"][0]["supply_chains"].__setitem__(1, None)), + mutate("the first viewer's two chains given in the other order", + lambda vs, fs: vs[cc]["receipts"][0]["supply_chains"].reverse()), + mutate("the liberal ads.txt's lone CR made a space", edit_file( + "supply.channel.example.ads.txt", b"reseller\r", b"reseller ")), + mutate("the gateway's sellers file nested one deeper", + lambda vs, fs: fs.update({"supply.gateway.example.sellers.json": fs["supply.gateway.example.sellers.json"] + .replace(b"[" * 63, b"[" * 64, 1).replace(b"]" * 63, b"]" * 64, 1)})), + mutate("the app-ads.txt lists the SSP's account as a reseller", edit_file( + "supply.channel.example.app-ads.txt", b"ssp.example, pub-1001, DIRECT", b"ssp.example, pub-1001, RESELLER")), + mutate("the SSP's sellers file no longer lists the channel", + edit_file("supply.ssp.example.sellers.json", b'"seller_id": "pub-1001"', b'"seller_id": "pub-1009"')), + mutate("the supply files withheld from the chain's vector", lambda vs, fs: vs[cc].pop("supply_files")), + mutate("the child room's policy stated with no audience bit", + lambda vs, fs: vs[kids].update(policy=vs[kids]["policy"][:-130] + "00" + vs[kids]["policy"][-128:])), + mutate("the cleared child-directed flag set back", lambda vs, fs: vs[vn["receipt_child_flag_cleared"]]["receipts"][0]["consent"].update(child_directed=1)), + mutate("a version 2 receipt read as version 1", + lambda vs, fs: [vs[cc]["receipts"][0].pop(k) for k in ("version", "consent", "supply_chains")]), ] @@ -759,20 +1092,22 @@ def main(corpus_dir): st = ED._self_test() print(("PASS" if not st else "FAIL") + " Ed25519 strict rule self-test " + ",".join(st)) fails += ["ed25519 self-test"] if st else [] - ok = len(REASONS) == 20 + ok = len(REASONS) == 27 print(("PASS" if ok else "FAIL") + " §10 read from the extension: %d reasons" % len(REASONS)) fails += [] if ok else ["§10 does not read"] with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f: doc = json.load(f) files = {} + supply = {n for v in doc["vectors"] for n in v.get("supply_files", [])} for name in sorted(os.listdir(corpus_dir)): - if name.endswith(".m3u8"): + if name.endswith(".m3u8") or name in supply: with open(os.path.join(corpus_dir, name), "rb") as f: files[name] = f.read() got, written = check(doc, files) fails += got - extra = sorted(set(os.listdir(corpus_dir)) - set(written) - {"vectors.json", "README.md"}) - print(("PASS" if not extra else "FAIL") + " no file but vectors.json, README.md and the tag files the rooms write") + extra = sorted(set(os.listdir(corpus_dir)) - set(written) - supply - {"vectors.json", "README.md"}) + print(("PASS" if not extra else "FAIL") + " no file but vectors.json, README.md, the tag files the rooms write and the " + "supply files the vectors name") fails += ["an extra file: %s" % n for n in extra] seen = {v.get("reason") for v in doc["vectors"]} missing = [r for r in REASONS if r not in seen] diff --git a/wai/live-conformance/stitched/README.md b/wai/live-conformance/stitched/README.md index 7da42f4c5..10b4387a6 100644 --- a/wai/live-conformance/stitched/README.md +++ b/wai/live-conformance/stitched/README.md @@ -15,8 +15,9 @@ each check; what a verifier states of them. read) and its `verdict`, `accepted` or a reason; for an accepted record, `response_checked`, its `ads` (each `slot`, its `own_claims` by index, `presentations`, `verdict`, `quartiles`, `presented_spend_micros` and `stated_quartile`), its `presented_spend_micros`, its - `stated_spend_micros`, and its frame's `share_bp` and ad `region` (rectangles - `[x, y, w, h]`, `null` for the full picture); + `stated_spend_micros`, its frame's `share_bp` and ad `region` (rectangles + `[x, y, w, h]`, `null` for the full picture), and, for a record of version 2, its `consent` + (`gpp`, `gpp_sid`, `child_directed`); - `claim_facts`, each claim in the order given: its `digest`, its `verdict`, `presented` or a reason, the `record` and `slot` it names (from §7.3 step 4 on), and, for an own claim, the `quartile` it reaches, its `run_ms` and its `viewable_ms`; @@ -79,6 +80,13 @@ The vectors: ad-f2 as started when its first segment was fetched ahead of the break; viewer 41 left after ad-f1. Stated: quartiles 5 and 1, 5 000 000 µ-units; presented: ad-f1 alone, 3 000 000. Control: `pod_response_mismatch` (the same receipt at another price). +- `consent`: two viewers' personalised playlists, each with one stitched ad, and a record of + version 2 per viewer (§4.7): viewer 51's carries the GPP string its player gave, the example + the GPP string's text gives for a service reached by a URL, with section 2; viewer 52's no + string and a child-directed request. Presented: both. Controls: viewer 53's record with its + GPP string changed after signing (`bad_signature`), whose ad's claim then names no decision + (`claim_no_decision`); a shared record carrying a viewer's string, and a record stating a + section with no string (each `pod_input_malformed`). ## Running @@ -129,5 +137,6 @@ draft whose SHA-256 is taken) or `response` (hexadecimal), `id_kind`, `id_attr`, (`require`, `rule`, `token_type`, `issuer`)), and `ads`, each with `marker` as its fields (`id` and `start` for carriage 1; and `index` for 2; `period`, `scheme`, `value` and `id` for 3), `ad_id`, `serving_id`, `creative_id`, `registry`, `creative_hash`, `duration_ms`, -`price_micros`, `price_source` and `evidence`. It refuses a draft that breaks §4.4, and prints -the signed record in hexadecimal. +`price_micros`, `price_source` and `evidence`; and, for a record of version 2, `consent`, as +live-ads §8.6's JSON carriage writes it (`gpp`, `gpp_sid`, `child_directed`). It refuses a draft +that breaks §4.4, and prints the signed record in hexadecimal. diff --git a/wai/live-conformance/stitched/consent.viewer51.m3u8 b/wai/live-conformance/stitched/consent.viewer51.m3u8 new file mode 100644 index 000000000..294a081b1 --- /dev/null +++ b/wai/live-conformance/stitched/consent.viewer51.m3u8 @@ -0,0 +1,23 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:5 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PROGRAM-DATE-TIME:2026-10-25T12:09:52.000Z +#EXTINF:4.000, +program/0.m4s +#EXTINF:4.000, +program/1.m4s +#EXT-X-DATERANGE:ID="break-600",CLASS="urn:example:break",START-DATE="2026-10-25T12:10:00.000Z",PLANNED-DURATION=15.000 +#EXT-X-DATERANGE:ID="v51-ad-0",CLASS="urn:example:ad",START-DATE="2026-10-25T12:10:00.000Z",DURATION=15.000,X-AD-ID="srv-v51-0" +#EXT-X-DISCONTINUITY +#EXTINF:4.000, +ads/912c17fb35083738d6eb0bf29548911111b41da0a5cb6b512ac98f17dcf91f21.m4s +#EXTINF:4.000, +ads/d3e30b815bc076686aba9bcea30afd9caa7c41a72ef35bfb9a98c6869d1e12f7.m4s +#EXTINF:4.000, +ads/c31ec984541ddb7ff258804c0fb0e9b66e94fe6528fb5ee1ba25a70144407063.m4s +#EXTINF:3.000, +ads/202266273b25c5dd444eab6c903ad7438cd19f5bcace495a941e97e34184fed9.m4s +#EXT-X-DISCONTINUITY +#EXTINF:4.000, +program/2.m4s diff --git a/wai/live-conformance/stitched/consent.viewer51.vast.xml b/wai/live-conformance/stitched/consent.viewer51.vast.xml new file mode 100644 index 000000000..0cb792f65 --- /dev/null +++ b/wai/live-conformance/stitched/consent.viewer51.vast.xml @@ -0,0 +1,23 @@ + + + + + example ad server + ad-a + srv-v51-0 + + 25.00 + + + UID-A + + 00:00:15.000 + + + + + + + + + diff --git a/wai/live-conformance/stitched/consent.viewer52.m3u8 b/wai/live-conformance/stitched/consent.viewer52.m3u8 new file mode 100644 index 000000000..2a74355dc --- /dev/null +++ b/wai/live-conformance/stitched/consent.viewer52.m3u8 @@ -0,0 +1,23 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:5 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PROGRAM-DATE-TIME:2026-10-25T12:09:52.000Z +#EXTINF:4.000, +program/0.m4s +#EXTINF:4.000, +program/1.m4s +#EXT-X-DATERANGE:ID="break-600",CLASS="urn:example:break",START-DATE="2026-10-25T12:10:00.000Z",PLANNED-DURATION=15.000 +#EXT-X-DATERANGE:ID="v52-ad-0",CLASS="urn:example:ad",START-DATE="2026-10-25T12:10:00.000Z",DURATION=15.000,X-AD-ID="srv-v52-0" +#EXT-X-DISCONTINUITY +#EXTINF:4.000, +ads/bea9d70662ff2ffaa4c1f8fef5dfed6b3dfba0e28664eb9602b87a5c7f9d43dd.m4s +#EXTINF:4.000, +ads/7ad95f226ee22f864b1818023079aab7a3a38303c3aa028edd992db97b09c08f.m4s +#EXTINF:4.000, +ads/d4236e07b3ad5d7ca4aa508317c25e41968a9a7650bf7905ec288f94434adb8c.m4s +#EXTINF:3.000, +ads/01eb03276ebac720029d3e9593bd0f1f1c2e0c7e7b584062b144be66261627ba.m4s +#EXT-X-DISCONTINUITY +#EXTINF:4.000, +program/2.m4s diff --git a/wai/live-conformance/stitched/consent.viewer52.vast.xml b/wai/live-conformance/stitched/consent.viewer52.vast.xml new file mode 100644 index 000000000..5adb69075 --- /dev/null +++ b/wai/live-conformance/stitched/consent.viewer52.vast.xml @@ -0,0 +1,23 @@ + + + + + example ad server + ad-b + srv-v52-0 + + 18.00 + + + UID-B + + 00:00:15.000 + + + + + + + + + diff --git a/wai/live-conformance/stitched/vectors.json b/wai/live-conformance/stitched/vectors.json index 770bcc0f9..84e97e98a 100644 --- a/wai/live-conformance/stitched/vectors.json +++ b/wai/live-conformance/stitched/vectors.json @@ -181,7 +181,11 @@ "26": "b971161e6f8753f8146f7e75e913943c4b7190b8c2f2b36c794bcd31e619e5a4", "31": "6e6f0d61db1bed7bfaf01088224317706db3f45f9e3819302d89829adc8f9b48", "32": "5b40e268c3ab78910157a9f7af1cd5f150a5cd75eb09cb1a641680d935fe4bf3", - "41": "5e5ed077b4b876f9853c7e460a83b2156e5b82420af023ccb91afe103a0753fa" + "41": "5e5ed077b4b876f9853c7e460a83b2156e5b82420af023ccb91afe103a0753fa", + "51": "6308b3b7b9a0e52a3e9159022682b85d124373686ea5abce1d9412a9cff359e5", + "52": "21a795ab0c8c11a316bcbd9c44e71efdb9d6543bb9f0aa458165a7106a86bd54", + "53": "d0d7adca761f48895b4b57f37af13ebe6c8d51f06635faf97f7681a23390c2c5", + "54": "a47c9467dc13f0ab30300484a926ace9ab218a017b564c14ca5d8bf8cd801a1f" }, "vectors": [ { @@ -968,6 +972,158 @@ "spend": { "USD": 3000000 } + }, + { + "name": "consent", + "about": "two viewers' personalised HLS playlists, each with one stitched ad, and a pod-input record of version 2 per viewer carrying the consent signal its decision was made under: one a GPP string with its applicable section, one a child-directed request with no string; a record whose GPP string was changed after signing, a shared record carrying a viewer's string, and a record stating a section with no string", + "manifests": [ + "consent.viewer51.m3u8", + "consent.viewer52.m3u8" + ], + "responses": [ + "consent.viewer51.vast.xml", + "consent.viewer52.vast.xml" + ], + "receipts": [], + "bound": [], + "records": [ + "575349310201d3f234282bbec8577f5aba66068815de09627265616b2d363030983a000001b74d3231b19e59b5af957bf2af017424b8f8e375f34b802ac13d605e0cb56e400107582d41442d4944555344020000000000000000000000000001010000000001006312e4025e54996631f5c082c35ae26fef3973eee2b91196eab679893997c4f1097372762d7635312d30097372762d7635312d30055549442d411072656769737472792e6578616d706c656b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f983a0000a0252600000000000100000000000000000000000000000000000000000000000000000000000000003900444241434e59417e4350587852664150587852664141664b4142454e422d436741414141414141414141596741414141414141417e31594e4e01020000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d356da3caff9769ab94945043f2a75331ee67ef1d447bf8827165b1b21f476802c55010ff84d12f5c3311c98bd78d2334b8eb93b191fc295ebd9374ae8ad43bfe70b", + "575349310201442a821454d9051a6a67d88c2f3a9c9609627265616b2d363030983a000001010498fb7fb4e791908f63d101fdb20ee2724c054b648a9cf80ce51a2f7246140107582d41442d494455534402000000000000000000000000000101000000000100014e4dd1fc2e7077975e1ce14d511c904ab34cec07b341a4bff0f8563aeb8c50097372762d7635322d30097372762d7635322d30055549442d421072656769737472792e6578616d706c654bbe86d73166f8e342b72f079d4ce024b629c8eb91441e1b657ce508f893c403983a000040771b000000000001000000000000000000000000000000000000000000000000000000000000000000000001d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d3564d48703364ed7a17d4b87cabc7e86438e26da92b94bee102d119286e018cf555b3e7da637452d757d4c8c4ffa30045663f9d2c1600e776e2a388f317ec39dc08", + "5753493102014a0656a98825fffafc22bf2a38bfa42d09627265616b2d363030983a000001b74d3231b19e59b5af957bf2af017424b8f8e375f34b802ac13d605e0cb56e400107582d41442d494455534402000000000000000000000000000101000000000100b77aa94277128ea87548c4ec7c2906b1eb085d66ece6a5ab7d8e8ab9d40aaf7e097372762d7635312d30097372762d7635312d30055549442d411072656769737472792e6578616d706c656b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f983a0000a0252600000000000100000000000000000000000000000000000000000000000000000000000000003900444241434e59417e4350587852664150587852664141664b4142454e422d436741414141414141414141596741414141414141417e3159594e01020000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d356512d3639e7bdf9da9c1fe4cb29d0a862a5a51dbedef36531cd5a04b5f8675c720db85ee813e5b84d5fa18276a012f4548dd68575181fff500cbc3eda397da50a", + "5753493102010000000000000000000000000000000009627265616b2d363030983a000001b74d3231b19e59b5af957bf2af017424b8f8e375f34b802ac13d605e0cb56e400107582d41442d494455534402000000000000000000000000000101000000000100212069bf0dbd0e7b4f8b3bea382ab43fc668bb61d243fdeb60253d3c87a37787097372762d7635312d30097372762d7635312d30055549442d411072656769737472792e6578616d706c656b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f983a0000a0252600000000000100000000000000000000000000000000000000000000000000000000000000003900444241434e59417e4350587852664150587852664141664b4142454e422d436741414141414141414141596741414141414141417e31594e4e01020000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d3564ff953fe31b8668b0a9db57eb400281cb3e479947201bb6c806f6d3fae1e537c726ee9d06293af506c040e51033c90e08e5b65129600857f708d08f735e04403", + "5753493102013d68ad572453752d0bbd2a481e001ced09627265616b2d363030983a000001b74d3231b19e59b5af957bf2af017424b8f8e375f34b802ac13d605e0cb56e400107582d41442d494455534402000000000000000000000000000101000000000100d41d38223c001953eeffc0f5480ee936cc71626cbafec1ae2cf31c86b693b81a097372762d7635312d30097372762d7635312d30055549442d411072656769737472792e6578616d706c656b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f983a0000a025260000000000010000000000000000000000000000000000000000000000000000000000000000000001020000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d35675dfa3aa8ec911a76772b3a47c132efc83fca94a1572688433ce235dbfb723f26802d933505e1a07da0580331cb6ef1e078130d8f32e58fcdde4d4dbfc917d04" + ], + "record_about": [ + "viewer 51's break, a record of version 2: the GPP string viewer 51's player gave, its applicable section 2, and a request not directed to children", + "viewer 52's break, a record of version 2: no GPP string given, and a child-directed request", + "viewer 53's record with the last section of its GPP string changed after the signer signed it", + "a shared record, which every viewer is shown, carrying one viewer's GPP string", + "a record stating section 2 as applicable, with no GPP string" + ], + "claims": [ + "575343310101eef083ea9c3480de4771f68ffa500b35f98f281a7138e9243acd1f1522998b63d3f234282bbec8577f5aba66068815de6b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f6308b3b7b9a0e52a3e9159022682b85d124373686ea5abce1d9412a9cff359e5011027ffffffff240008007635312d61642d301800323032362d31302d32355431323a31303a30302e3030305a097372762d7635312d300400912c17fb35083738d6eb0bf29548911111b41da0a5cb6b512ac98f17dcf91f21d3e30b815bc076686aba9bcea30afd9caa7c41a72ef35bfb9a98c6869d1e12f7c31ec984541ddb7ff258804c0fb0e9b66e94fe6528fb5ee1ba25a70144407063202266273b25c5dd444eab6c903ad7438cd19f5bcace495a941e97e34184fed9010000000000983a00001027010000232b271b9f9d1481ad6078df2d52db1f07ed4127979e81ae14d4c4eb639722dffe3a60a9a7f05c63ce015b7ebd6d6a62dfbdc87924bf557e9ce0da1be446b506", + "575343310101f72965d20cb6f4c5078d889f4e386a21478fe67b5187b0fc250b1a074308e638442a821454d9051a6a67d88c2f3a9c964bbe86d73166f8e342b72f079d4ce024b629c8eb91441e1b657ce508f893c40321a795ab0c8c11a316bcbd9c44e71efdb9d6543bb9f0aa458165a7106a86bd54011027ffffffff240008007635322d61642d301800323032362d31302d32355431323a31303a30302e3030305a097372762d7635322d300400bea9d70662ff2ffaa4c1f8fef5dfed6b3dfba0e28664eb9602b87a5c7f9d43dd7ad95f226ee22f864b1818023079aab7a3a38303c3aa028edd992db97b09c08fd4236e07b3ad5d7ca4aa508317c25e41968a9a7650bf7905ec288f94434adb8c01eb03276ebac720029d3e9593bd0f1f1c2e0c7e7b584062b144be66261627ba010000000000983a000010270100003b21ec37ab5277d488370ebbb5bd14a0df5b5c42e6b9f9c7048bb8faafb06eed817ef60342f98e98f107cb9dcd0b0b47c05b634a9cbda5da700aa572069cc704", + "575343310101eef083ea9c3480de4771f68ffa500b35f98f281a7138e9243acd1f1522998b634a0656a98825fffafc22bf2a38bfa42d6b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3fd0d7adca761f48895b4b57f37af13ebe6c8d51f06635faf97f7681a23390c2c5011027ffffffff240008007635332d61642d301800323032362d31302d32355431323a31303a30302e3030305a097372762d7635312d300400912c17fb35083738d6eb0bf29548911111b41da0a5cb6b512ac98f17dcf91f21d3e30b815bc076686aba9bcea30afd9caa7c41a72ef35bfb9a98c6869d1e12f7c31ec984541ddb7ff258804c0fb0e9b66e94fe6528fb5ee1ba25a70144407063202266273b25c5dd444eab6c903ad7438cd19f5bcace495a941e97e34184fed9010000000000983a00001027010000972bf30e06f494cb58aa70b82f0cfaf5dc674b4096341af71fbbaa3b24fb183ca973ac108351598ff544cf056f53e81e13cbb5e5b00524dbb1d0e91c69770500" + ], + "claim_about": [ + "viewer 51, ad-a: in view and audible from start to end", + "viewer 52, ad-b: in view and audible from start to end", + "viewer 53's claim of the ad of its altered record, which names no decision" + ], + "record_facts": [ + { + "digest": "2989a862461444bb7e85d938fba2e356266cd03546f6bae01666c8df1a7734cc", + "verdict": "accepted", + "response_checked": true, + "ads": [ + { + "slot": 0, + "own_claims": [ + 0 + ], + "presentations": 1, + "verdict": "presented", + "quartiles": [ + 1, + 1, + 1, + 1, + 1 + ], + "presented_spend_micros": 2500000, + "stated_quartile": null + } + ], + "presented_spend_micros": 2500000, + "stated_spend_micros": null, + "share_bp": 10000, + "region": null, + "consent": { + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + "gpp_sid": [ + 2 + ], + "child_directed": 0 + } + }, + { + "digest": "a0fd4a3e7617ffaee89c113dc9e2e1c0c2f94551b40e54b457afd9a5bcbf9e8c", + "verdict": "accepted", + "response_checked": true, + "ads": [ + { + "slot": 0, + "own_claims": [ + 1 + ], + "presentations": 1, + "verdict": "presented", + "quartiles": [ + 1, + 1, + 1, + 1, + 1 + ], + "presented_spend_micros": 1800000, + "stated_quartile": null + } + ], + "presented_spend_micros": 1800000, + "stated_spend_micros": null, + "share_bp": 10000, + "region": null, + "consent": { + "gpp": "", + "gpp_sid": [], + "child_directed": 1 + } + }, + { + "digest": "9b54b8f46f5c4f36d15d3738555cde621894cf450693461f0a6488972eb08925", + "verdict": "bad_signature" + }, + { + "digest": null, + "verdict": "pod_input_malformed" + }, + { + "digest": null, + "verdict": "pod_input_malformed" + } + ], + "claim_facts": [ + { + "digest": "0b515c89086c1a0cb81e90d490ddf98d5dfb98b6ef89dd03f871b29df542c9ce", + "verdict": "presented", + "record": 0, + "slot": 0, + "quartile": 5, + "run_ms": 15000, + "viewable_ms": 15000 + }, + { + "digest": "8ab65080ff62814801418ef7f6acd980a7ea77e110ab603f15a0ae84c3ce1f0b", + "verdict": "presented", + "record": 1, + "slot": 0, + "quartile": 5, + "run_ms": 15000, + "viewable_ms": 15000 + }, + { + "digest": "cc6b7deb71acc75c9beda4d4f9479b7402223936938df88f05f1be3dfbfba8ce", + "verdict": "claim_no_decision", + "record": null, + "slot": null, + "quartile": null, + "run_ms": null, + "viewable_ms": null + } + ], + "spend": { + "USD": 4300000 + } } ] } diff --git a/wai/live-conformance/stitched_presentation_verify.py b/wai/live-conformance/stitched_presentation_verify.py index 5c79cd795..8fac7afe6 100644 --- a/wai/live-conformance/stitched_presentation_verify.py +++ b/wai/live-conformance/stitched_presentation_verify.py @@ -7,13 +7,15 @@ strict rule (SPEC §7 *Signatures*), and BLAKE3 with ../claim-conformance/_blake corpora's other second implementations it takes, by reference as the extension does: the spans' quartile and run and RSASSA-PSS from ad_presentation_verify.py (ad-presentation §6, §7); a form-1 receipt's leaves, Merkle root and class encoding from live_ads_verify.py (live-ads §8.3, -energy-measurement §2.7); and an ad frame from live_compose_verify.py (live-compose §5.9). +energy-measurement §2.7), and the consent signal's rules from it (live-ads §8.6, §4.7 here); +and an ad frame from live_compose_verify.py (live-compose §5.9). Everything else is here, from the standard library: - markers, their bodies and digests (§3.1), and reading them from a Media Playlist's date range tags, an asset list and an MPD over the XML subset of §3.5, with a reader of its own; -- the pod-input record, its rules and signature (§4), the VAST check and a form-1 receipt's - JSON carriage, payload, identity and rules (§4.5, §7.2); +- the pod-input record, its rules and signature (§4), a record of version 2's consent signal + in its layout (§4.7), the VAST check and a form-1 receipt's JSON carriage, payload, identity + and rules (§4.5, §7.2); - the claim, its session, its rules and signature, and its attestation challenge (§5); - each record's and claim's verdict, each ad's presentations and quartiles, and the spend by currency (§7). @@ -493,7 +495,10 @@ def read_record(b): """§4.4: the record, or Bad(pod_input_malformed).""" why = "pod_input_malformed" c = Cursor(b, why) - if c.take(4) != POD_MAGIC or c.take(1) != b"\x01": + if c.take(4) != POD_MAGIC: + raise Bad(why) + version = c.u("B") + if version not in (1, 2): raise Bad(why) r = {"carriage": c.u("B"), "session": c.take(16), "avail_id": c.short(), "avail_ms": c.u("I"), "format": c.u("B"), "response": c.take(32), "id_kind": c.u("B"), "id_attr": c.short(), @@ -507,6 +512,15 @@ def read_record(b): "creative_id": c.short(), "registry": c.short(), "creative_hash": c.take(32), "duration_ms": c.u("I"), "price_micros": c.u("Q"), "price_source": c.u("B"), "evidence": c.take(32)}) + r["consent"] = None + if version == 2: # §4.7 + gpp = c.long() + k = c.u("B") + sid = [c.u("H") for _ in range(k)] + try: + r["consent"] = {"gpp": gpp.decode("utf-8"), "gpp_sid": sid, "child_directed": c.u("B")} + except UnicodeDecodeError: + raise Bad(why) r["signer"] = c.take(32) r["body"] = b[:c.p] r["sig"] = c.take(64) @@ -536,6 +550,9 @@ def read_record(b): ok = ok and not (r["format"] == FORM1 and src != STATED) ok = ok and (ad["evidence"] != bytes(32)) == (src == WINNING_BID) ok = ok and all(x["marker"] != ad["marker"] for x in r["ads"][:k]) + cs = r["consent"] # rule 10 + if cs is not None and "consent" not in SKIP: + ok = ok and LAV.consent_ok(cs) and not (r["session"] == bytes(16) and cs["gpp"]) if not ok: raise Bad(why) return r @@ -803,6 +820,8 @@ def verify(inp, through_attestation=True): region = None if r["frame"]["kind"] == 0 else [list(x) for x in frame_region(r["frame"])] f.update(response_checked=checked, stated_spend_micros=stated_spend, share_bp=share_of(r["frame"]), region=region) + if r["consent"] is not None: # §7.5 + f["consent"] = dict(r["consent"]) accepted.append(r) except Bad as e: f = {"digest": f["digest"], "verdict": str(e)} @@ -1006,12 +1025,18 @@ def mutations(doc, files): mut("double_box: the stated currency's spend", lambda d: vec(d, "double_box")["spend"].update({"EUR": 1800001})) mut("form1_prefetch: the receipt's stated quartile", g=edit("form1_prefetch.receipt.json", b'"quartiles_reached": 1', b'"quartiles_reached": 2')) mut("form1_prefetch: the stated spend beside the presented", lambda d: vec(d, "form1_prefetch")["record_facts"][0].update(stated_spend_micros=3000000)) + mut("consent: viewer 51's record with a byte of its GPP string changed", + set_record("consent", 0, lambda h: h.replace(b"1YNN".hex(), b"1YYN".hex(), 1))) + mut("consent: viewer 52's stated child-directed flag", + lambda d: vec(d, "consent")["record_facts"][1]["consent"].update(child_directed=0)) + mut("consent: viewer 51's stated sections", lambda d: vec(d, "consent")["record_facts"][0]["consent"].update(gpp_sid=[])) mut("a file the corpus does not name", g=lambda fs: fs.__setitem__("extra.m3u8", b"#EXTM3U\n")) return out CHECKS = ["record_rules", "signer", "pod_duplicate", "response", "vast_price", "vast_creative", "claim_rules", - "key_bound", "session", "manifest", "marker", "claim_duplicate", "asset", "frame", "viewable", "attest"] + "key_bound", "session", "manifest", "marker", "claim_duplicate", "asset", "frame", "viewable", "attest", + "consent"] def main(corpus_dir): diff --git a/wai/wai-rs/src/bin/wai_live_ads_vectors.rs b/wai/wai-rs/src/bin/wai_live_ads_vectors.rs index a025eeb5c..25ab6a1f1 100644 --- a/wai/wai-rs/src/bin/wai_live_ads_vectors.rs +++ b/wai/wai-rs/src/bin/wai_live_ads_vectors.rs @@ -1,7 +1,8 @@ //! Conformance corpus for live ads (`live-conformance/ads/`, extensions/live-ads.md §11). //! -//! wai_live_ads_vectors gen write vectors.json and each accepted room's HLS -//! tags (NAME.m3u8), from fixed seeds +//! wai_live_ads_vectors gen write vectors.json, each accepted room's HLS +//! tags (NAME.m3u8) and the supply files +//! (supply.NAME), from fixed seeds //! wai_live_ads_vectors verify every file regenerates byte for byte, no other //! file but README.md is there, and every case gets //! the outcome it states @@ -53,9 +54,10 @@ fn verify(dir: &Path) -> i32 { if name == "README.md" { continue; } - match std::fs::read_to_string(e.path()) { - Ok(s) => { - files.insert(name, s); + // Read as bytes: a supply file need not be UTF-8 (live-ads §8.7). + match std::fs::read(e.path()) { + Ok(b) => { + files.insert(name, b); } Err(err) => { eprintln!("read {name}: {err}"); @@ -63,7 +65,7 @@ fn verify(dir: &Path) -> i32 { } } } - let generated: BTreeMap = corpus::generate().into_iter().collect(); + let generated: BTreeMap> = corpus::generate().into_iter().collect(); let same = files == generated; println!("{} every file regenerates byte for byte from its seeds, and no other file is there", if same { "PASS" } else { "FAIL" }); let fails = corpus::verify(&files); diff --git a/wai/wai-rs/src/bin/wai_stitched_presentation.rs b/wai/wai-rs/src/bin/wai_stitched_presentation.rs index cbcb1a2a4..8038ecdc3 100644 --- a/wai/wai-rs/src/bin/wai_stitched_presentation.rs +++ b/wai/wai-rs/src/bin/wai_stitched_presentation.rs @@ -33,6 +33,7 @@ use serde_json::{json, Value}; use sha2::{Digest, Sha256}; use wai::live_ads::presentation::{Creative, CreativeObject, Issuer, Requirement}; use wai::stitched_presentation::{self as sp, corpus, Frame, Marker, PodAd, PodInput}; +use wai::wai_video_ssai::terms::Consent; fn main() { let a: Vec = std::env::args().collect(); @@ -268,6 +269,12 @@ fn sign_pod(draft: &Path, key: &Path) -> Result { issuer: r.get("issuer").and_then(Value::as_str).unwrap_or("").as_bytes().to_vec(), }; let currency: [u8; 3] = text(&d, "currency")?.try_into().map_err(|_| "currency: three letters")?; + // A draft's `consent` (§4.7) makes a record of version 2: its GPP string, its applicable + // sections and its child-directed flag, as live-ads §8.6's JSON carriage writes them. + let consent = match d.get("consent") { + None => None, + Some(c) => Some(Consent::from_json(c).ok_or("consent: {\"gpp\", \"gpp_sid\", \"child_directed\"}")?), + }; let ads = field(&d, "ads")? .as_array() .ok_or("ads: an array")? @@ -308,6 +315,7 @@ fn sign_pod(draft: &Path, key: &Path) -> Result { frame, requirement, ads, + consent, signer: [0; 32], sig: [0; 64], }; diff --git a/wai/wai-rs/src/impression_aggregate/corpus.rs b/wai/wai-rs/src/impression_aggregate/corpus.rs index 31b94ffed..8db69f995 100644 --- a/wai/wai-rs/src/impression_aggregate/corpus.rs +++ b/wai/wai-rs/src/impression_aggregate/corpus.rs @@ -278,6 +278,7 @@ fn record( evidence: [0; 32], }) .collect(), + consent: None, signer: [0; 32], sig: [0; 64], } diff --git a/wai/wai-rs/src/live_ads.rs b/wai/wai-rs/src/live_ads.rs index 015d4d89d..6761f4574 100644 --- a/wai/wai-rs/src/live_ads.rs +++ b/wai/wai-rs/src/live_ads.rs @@ -18,7 +18,11 @@ //! - §7: the asset lists, HLS and DASH; //! - §9: verification from the room's events to the spend; under a policy of version 2 //! that requires presentation claims, the spend of each receipt counts only the -//! impressions whose claim verifies ([`presentation`], extensions/ad-presentation.md). +//! impressions whose claim verifies ([`presentation`], extensions/ad-presentation.md); +//! under a policy of version 3, every receipt states the consent signal its decision was +//! made under, child-directed in a room the policy marks so; and each supply chain a +//! receipt states for one of its impressions is checked against the sellers files and the +//! authorised-sellers file the verifier holds (§8.6, §8.7, [`crate::wai_video_ssai::terms`]). //! //! The corpus `live-conformance/ads/` is written by [`corpus::generate`] and checked by //! [`corpus::verify`], and by `live-conformance/live_ads_verify.py`, which shares no code @@ -33,6 +37,7 @@ use sha2::{Digest, Sha256}; use crate::live_events::{self as events, cue_type, iso8601_ms, kind, Event, Payload}; use crate::live_room as room; +use crate::wai_video_ssai::terms::{self, ChainFacts, Consent, SupplyChain, SupplyFault}; use crate::wai_video_ssai::{asset_format, decide, pod_hash, within_ceiling, AdCandidate, BreakReceiptFault, Impression, SsaiBreakReceipt}; pub mod corpus; @@ -52,6 +57,8 @@ pub const ANNOTATION_MAGIC: [u8; 4] = *b"WAB1"; pub const RECORD_VERSION: u8 = 1; /// The version of a policy with presentation fields (§3.1; ad-presentation §8). pub const POLICY_VERSION_PRESENTATION: u8 = 2; +/// The version of a policy with presentation fields and an audience (§3.1). +pub const POLICY_VERSION_AUDIENCE: u8 = 3; /// The domain a policy's signature is made under (§3.2). pub const POLICY_DOMAIN: &[u8] = b"wai:live-ads-policy\x01"; /// The length of a break annotation (§4.2). @@ -87,6 +94,12 @@ pub mod snap { pub const IN: u8 = 2; } +/// The bits of a policy's `audience` (§3.1). +pub mod audience { + /// The room is directed to children (§8.6). + pub const CHILD: u8 = 1; +} + /// Why a policy, a schedule or a receipt is refused (§10). #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Reason { @@ -112,11 +125,18 @@ pub enum Reason { AssetListMismatch, ReceiptDuplicate, FundsExceeded, + ConsentInvalid, + SupplyChainInvalid, + RequestTermsAbsent, + ChildDirectedUnset, + SupplyFileMalformed, + SupplySellerUnlisted, + SupplySellerUnauthorised, } impl Reason { /// The codes this extension adds, in §10's order. - pub const ALL: [Reason; 20] = [ + pub const ALL: [Reason; 27] = [ Reason::AdPolicyMalformed, Reason::DeciderGrantUnknown, Reason::AnnotationMalformed, @@ -137,6 +157,13 @@ impl Reason { Reason::AssetListMismatch, Reason::ReceiptDuplicate, Reason::FundsExceeded, + Reason::ConsentInvalid, + Reason::SupplyChainInvalid, + Reason::RequestTermsAbsent, + Reason::ChildDirectedUnset, + Reason::SupplyFileMalformed, + Reason::SupplySellerUnlisted, + Reason::SupplySellerUnauthorised, ]; /// The reason's code. @@ -163,6 +190,13 @@ impl Reason { Reason::AssetListMismatch => "asset_list_mismatch", Reason::ReceiptDuplicate => "receipt_duplicate", Reason::FundsExceeded => "funds_exceeded", + Reason::ConsentInvalid => "consent_invalid", + Reason::SupplyChainInvalid => "supply_chain_invalid", + Reason::RequestTermsAbsent => "request_terms_absent", + Reason::ChildDirectedUnset => "child_directed_unset", + Reason::SupplyFileMalformed => "supply_file_malformed", + Reason::SupplySellerUnlisted => "supply_seller_unlisted", + Reason::SupplySellerUnauthorised => "supply_seller_unauthorised", } } } @@ -212,9 +246,12 @@ pub struct AdPolicy { pub snap: u8, pub asset_base: String, pub creative_base: String, - /// The presentation fields of a policy of version 2 (ad-presentation §8); `None` for a - /// policy of version 1. + /// The presentation fields of a policy of version 2 or 3 (ad-presentation §8); `None` for + /// a policy of version 1. pub presentation: Option, + /// The audience of a policy of version 3 ([`audience`]): such a policy requires every + /// receipt to state its consent signal (§8.6). `None` for a policy of version 1 or 2. + pub audience: Option, pub sig: [u8; 64], } @@ -272,7 +309,13 @@ impl AdPolicy { /// The policy without `sig` (§3.1). pub fn body(&self) -> Vec { let mut o = POLICY_MAGIC.to_vec(); - o.push(if self.presentation.is_some() { POLICY_VERSION_PRESENTATION } else { RECORD_VERSION }); + o.push(if self.audience.is_some() { + POLICY_VERSION_AUDIENCE + } else if self.presentation.is_some() { + POLICY_VERSION_PRESENTATION + } else { + RECORD_VERSION + }); o.extend_from_slice(&self.room); o.extend_from_slice(&self.decider_grant); o.push(self.slots); @@ -283,12 +326,22 @@ impl AdPolicy { o.push(self.snap); put_long(&mut o, self.asset_base.as_bytes()); put_long(&mut o, self.creative_base.as_bytes()); - if let Some(q) = &self.presentation { + // A policy of version 3 carries the presentation fields too, all zero when it states + // none (a policy of version 2 with `presentation` 0, ad-presentation §8). + let q = match (&self.presentation, self.audience) { + (Some(q), _) => Some(q.clone()), + (None, Some(_)) => Some(Requirement::default()), + (None, None) => None, + }; + if let Some(q) = q { o.push(q.require); o.push(q.rule); o.extend_from_slice(&q.token_type.to_le_bytes()); put_long(&mut o, &q.issuer); } + if let Some(a) = self.audience { + o.push(a); + } o } @@ -297,6 +350,11 @@ impl AdPolicy { self.presentation.as_ref().is_some_and(Requirement::claims) } + /// Does the policy mark the room as directed to children (§3.1, §8.6)? + pub fn child_directed(&self) -> bool { + self.audience.is_some_and(|a| a & audience::CHILD != 0) + } + pub fn to_bytes(&self) -> Vec { let mut o = self.body(); o.extend_from_slice(&self.sig); @@ -327,10 +385,11 @@ impl AdPolicy { if b.len() < 5 { return Err(ev(events::Reason::Truncated)); } - if !matches!(b[4], RECORD_VERSION | POLICY_VERSION_PRESENTATION) { + if !matches!(b[4], RECORD_VERSION | POLICY_VERSION_PRESENTATION | POLICY_VERSION_AUDIENCE) { return Err(ev(events::Reason::UnknownVersion)); } - let v2 = b[4] == POLICY_VERSION_PRESENTATION; + let v2 = b[4] >= POLICY_VERSION_PRESENTATION; + let v3 = b[4] == POLICY_VERSION_AUDIENCE; let mut r = Rd { b, p: 5 }; let read = |r: &mut Rd| -> Option<(AdPolicy, Vec, Vec)> { let room = r.d32()?; @@ -348,6 +407,7 @@ impl AdPolicy { } else { None }; + let audience = if v3 { Some(r.u8()?) } else { None }; let sig: [u8; 64] = r.take(64)?.try_into().ok()?; let p = AdPolicy { room, @@ -361,6 +421,7 @@ impl AdPolicy { asset_base: String::new(), creative_base: String::new(), presentation, + audience, sig, }; Some((p, asset, creative)) @@ -375,6 +436,7 @@ impl AdPolicy { || !base_ok(&asset) || !base_ok(&creative) || p.presentation.as_ref().is_some_and(|q| !q.well_formed()) + || p.audience.is_some_and(|a| a & !audience::CHILD != 0) { return Err(Reason::AdPolicyMalformed); } @@ -807,6 +869,9 @@ pub struct Input<'a> { pub creatives: &'a [Creative], /// The attestation issuers the verifier holds keys of (ad-presentation §7.3). pub issuers: &'a [Issuer], + /// The sellers files and authorised-sellers files the verifier holds, by their bytes + /// (§9.1); each is found by its SHA-256. + pub supply_files: &'a [Vec], } /// Where a verification stopped (§9.2). @@ -838,6 +903,17 @@ pub struct ReceiptFacts { /// (ad-presentation §9.3), and each impression's count. pub presented_spend_micros: Option, pub impressions: Vec, + /// A receipt of version 2's consent signal, and its supply chains with what was checked + /// of each (§8.6, §8.7, §9.3 step 14). + pub terms: Option, +} + +/// What a verification states of a receipt of version 2's request terms (§9.4). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TermsFacts { + pub consent: Consent, + /// One for each impression, in its order: `None` when the receipt states no chain for it. + pub supply_chains: Vec>, } /// What a verification holds at its end. @@ -957,6 +1033,8 @@ pub fn verify(inp: &Input) -> Run { Err(BreakReceiptFault::BadKey) => return run.refuse(at, ev(events::Reason::BadKey)), Err(BreakReceiptFault::BadSignature) => return run.refuse(at, ev(events::Reason::BadSignature)), Err(BreakReceiptFault::Invalid) => return run.refuse(at, Reason::ReceiptInvalid), + Err(BreakReceiptFault::ConsentInvalid) => return run.refuse(at, Reason::ConsentInvalid), + Err(BreakReceiptFault::SupplyChainInvalid) => return run.refuse(at, Reason::SupplyChainInvalid), } if r.signer_pubkey != decider.grant.principal { return run.refuse(at, Reason::ReceiptNotDecider); @@ -1004,7 +1082,43 @@ pub fn verify(inp: &Input) -> Run { { return run.refuse(at, Reason::FundsExceeded); } - run.receipts.push(ReceiptFacts { identity: r.receipt_hash(), asset_list: list, spend_micros: r.budget_spent_micros, presented_spend_micros: presented, impressions }); + // Step 13: under a policy of version 3, the receipt states its consent signal, and in + // a room the policy marks as directed to children, a child-directed one. + if p.audience.is_some() { + match &r.terms { + None => return run.refuse(at, Reason::RequestTermsAbsent), + Some(t) if p.child_directed() && t.consent.child_directed != 1 => return run.refuse(at, Reason::ChildDirectedUnset), + Some(_) => {} + } + } + // Step 14: each impression's supply chain, in order, against the files the verifier + // holds. + let facts = match &r.terms { + None => None, + Some(t) => { + let mut chains = Vec::with_capacity(t.supply_chains.len()); + for c in &t.supply_chains { + chains.push(match c { + None => None, + Some(c) => match terms::check_chain(c, inp.supply_files) { + Ok(f) => Some((c.clone(), f)), + Err(SupplyFault::FileMalformed) => return run.refuse(at, Reason::SupplyFileMalformed), + Err(SupplyFault::Unlisted) => return run.refuse(at, Reason::SupplySellerUnlisted), + Err(SupplyFault::Unauthorised) => return run.refuse(at, Reason::SupplySellerUnauthorised), + }, + }); + } + Some(TermsFacts { consent: t.consent.clone(), supply_chains: chains }) + } + }; + run.receipts.push(ReceiptFacts { + identity: r.receipt_hash(), + asset_list: list, + spend_micros: r.budget_spent_micros, + presented_spend_micros: presented, + impressions, + terms: facts, + }); } run.spend_micros = u64::try_from(total).unwrap_or(u64::MAX); run.claims = ledger.map(Ledger::finish).unwrap_or_default(); @@ -1029,6 +1143,7 @@ mod tests { asset_base: "https://ads.example/r/".into(), creative_base: "https://cdn.example/c/".into(), presentation: None, + audience: None, sig: [0; 64], } .sign(&SigningKey::from_bytes(&[7; 32])) @@ -1055,8 +1170,25 @@ mod tests { assert_eq!(AdPolicy::parse(&b[..b.len() - 1]), Err(ev(events::Reason::Truncated))); assert_eq!(AdPolicy::parse(&[b.as_slice(), &[0]].concat()), Err(ev(events::Reason::Malformed))); assert_eq!(AdPolicy::parse(b"WAP2"), Err(ev(events::Reason::Malformed))); - assert_eq!(AdPolicy::parse(b"WAP1\x03"), Err(ev(events::Reason::UnknownVersion))); + assert_eq!(AdPolicy::parse(b"WAP1\x04"), Err(ev(events::Reason::UnknownVersion))); assert_eq!(AdPolicy::parse(b"WAP1\x02"), Err(ev(events::Reason::Truncated)), "version 2 is a policy with presentation fields"); + assert_eq!(AdPolicy::parse(b"WAP1\x03"), Err(ev(events::Reason::Truncated)), "version 3 is a policy with an audience"); + } + + #[test] + fn a_policy_of_version_three_carries_its_audience() { + let child = AdPolicy { presentation: Some(Requirement::default()), audience: Some(audience::CHILD), ..policy() }.sign(&SigningKey::from_bytes(&[7; 32])); + let b = child.to_bytes(); + assert_eq!(b[4], POLICY_VERSION_AUDIENCE); + assert_eq!(AdPolicy::parse(&b), Ok(child.clone())); + assert!(child.child_directed() && !child.requires_claims()); + let open = AdPolicy { audience: Some(0), ..child.clone() }; + assert!(!AdPolicy::parse(&open.to_bytes()).expect("a policy of version 3 with no audience bit").child_directed()); + let none = AdPolicy { presentation: None, ..child.clone() }; + assert_eq!(none.body(), child.body(), "a policy of version 3 states no presentation fields as zeros"); + let bad = AdPolicy { audience: Some(2), ..child.clone() }; + assert_eq!(AdPolicy::parse(&bad.to_bytes()), Err(Reason::AdPolicyMalformed)); + assert!(!policy().child_directed()); } #[test] diff --git a/wai/wai-rs/src/live_ads/corpus.rs b/wai/wai-rs/src/live_ads/corpus.rs index fd88d3027..43007a8c4 100644 --- a/wai/wai-rs/src/live_ads/corpus.rs +++ b/wai/wai-rs/src/live_ads/corpus.rs @@ -19,6 +19,7 @@ use crate::key_log::{commitment, KeyEntry, Revision}; use crate::live_events::Policy as EventPolicy; use crate::live_room::{Caps, Descriptor, Detail, Grant, Limits, Recording, Review, Role, KEY_LOG_AUTHORITY, NO_CEILING}; use crate::quantum_ops::GrantRef; +use crate::wai_video_ssai::terms::{Authorised, RequestTerms, SupplyNode, ADS_TXT, APP_ADS_TXT, MAX_SELLERS_DEPTH}; use crate::wai_video_ssai::BreakBinding; use serde_json::{json, Map, Value}; @@ -257,6 +258,7 @@ pub(super) fn policy(room: &Room, f: impl FnOnce(&mut AdPolicy)) -> AdPolicy { asset_base: ASSET_BASE.into(), creative_base: CREATIVE_BASE.into(), presentation: None, + audience: None, sig: [0; 64], }; f(&mut p); @@ -307,6 +309,8 @@ pub(super) struct Seal { work_ops: Option, /// A figure sealed with no class. joules_only: Option, + /// The request terms of a receipt of version 2 (§8.6, §8.7); none for version 1. + terms: Option, } impl Seal { @@ -329,6 +333,7 @@ impl Seal { list_format: None, work_ops: None, joules_only: None, + terms: None, } } } @@ -362,7 +367,7 @@ pub(super) fn seal(room: &Room, p: &AdPolicy, s: &Seal) -> SsaiBreakReceipt { }; let signer = key(s.signer); let id = crate::signer_id::did_key(&signer.verifying_key().to_bytes()); - SsaiBreakReceipt::seal(&signer, id, binding, pod_hash(&s.pod), imps, s.work_ops.unwrap_or(s.pod.len() as u64), joules, class, grant) + SsaiBreakReceipt::seal_with_terms(&signer, id, binding, pod_hash(&s.pod), imps, s.work_ops.unwrap_or(s.pod.len() as u64), joules, class, grant, s.terms.clone()) } // ---- vectors ---- @@ -385,6 +390,9 @@ struct Vector { receipts: Vec, pods: Vec>, intent: Intent, + /// The sellers files and authorised-sellers files the verifier holds (§9.1): each a file + /// of the corpus, by its name and its bytes, which need not be UTF-8. + supply_files: Vec<(String, Vec)>, } impl Vector { @@ -402,6 +410,7 @@ impl Vector { claims: &[], creatives: &[], issuers: &[], + supply_files: &self.supply_files.iter().map(|(_, b)| b.clone()).collect::>(), }) } } @@ -432,7 +441,17 @@ fn live_break(spec: Spec) -> LiveBreak { } fn vector(name: &'static str, about: &'static str, room: &Room, policy: &AdPolicy, receipts: Vec, pods: Vec>, intent: Intent) -> Vector { - Vector { name, about, room: room.clone(), policy: policy.to_bytes(), pt0_ms: 0, receipts: receipts.iter().map(SsaiBreakReceipt::to_json).collect(), pods, intent } + Vector { + name, + about, + room: room.clone(), + policy: policy.to_bytes(), + pt0_ms: 0, + receipts: receipts.iter().map(SsaiBreakReceipt::to_json).collect(), + pods, + intent, + supply_files: vec![], + } } fn accepted() -> Vec { @@ -693,9 +712,421 @@ fn refused() -> Vec { out } +// ---- the request terms (§8.6, §8.7) ---- + +/// The example GPP string the GPP string's text gives for a URL-based service, with its +/// applicable section, 2. WAI does not decode it. +const GPP: &str = "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN"; + +/// The channel's account at the SSP, and the SSP's and the channel's at the exchange: the +/// sellers files of the two advertising systems and the channel's authorised-sellers file, +/// fixtures written here. +const SSP_SELLERS: &str = r#"{ + "contact_email": "adops@ssp.example", + "version": "1.0", + "sellers": [ + {"seller_id": "pub-1001", "name": "Example Channel", "domain": "channel.example", "seller_type": "PUBLISHER"}, + {"seller_id": "pub-1002", "seller_type": "PUBLISHER", "is_confidential": 1} + ] +} +"#; +const EXCHANGE_SELLERS: &str = r#"{ + "version": "1.0", + "sellers": [ + {"seller_id": "ssp-77", "name": "Example SSP", "domain": "ssp.example", "seller_type": "INTERMEDIARY"}, + {"seller_id": "pub-2001", "name": "Example Channel", "domain": "channel.example", "seller_type": "PUBLISHER"} + ] +} +"#; +const APP_ADS: &str = "# app-ads.txt of channel.example\nOWNERDOMAIN=channel.example\nssp.example, pub-1001, DIRECT\nexchange.example, ssp-77, RESELLER\nexchange.example, pub-2001, DIRECT\nCONTACT=adops@channel.example\n"; +/// A file a node names as its sellers file that is not one: it has no `sellers`. +const NOT_SELLERS: &str = "{\n \"version\": \"1.0\"\n}\n"; + +/// The channel's ads.txt for its web player, written to be read by each rule of §8.7: a +/// record of four fields, one whose advertising system is in other letter cases, one ended by +/// CR LF, one whose relationship is in lower case, ended by a CR alone, and one with an +/// extension field. +const ADS_LIBERAL: &[u8] = b"# ads.txt of channel.example\nssp.example, pub-3001, DIRECT, f08c47fec0942fa0\nExchange.EXAMPLE, ssp-77, RESELLER\r\nwholesale.example, seat-5, reseller\rbridge.example, br-12, RESELLER;ext=video\nCONTACT=adops@channel.example\n"; +/// An app-ads.txt that is not UTF-8: its comment is in another encoding. +const NOT_UTF8: &[u8] = b"# app-ads.txt de Soci\xe9t\xe9 Exemple\nssp.example, pub-1001, DIRECT\nexchange.example, ssp-77, RESELLER\n"; +/// An app-ads.txt whose only lines naming the exchange's account `ssp-99` are lines a reader +/// passes over: a comment, and a line of five fields. +const PASSED_OVER: &[u8] = b"# exchange.example, ssp-99, RESELLER\nssp.example, pub-1001, DIRECT\nexchange.example, ssp-99, RESELLER, f08c47fec0942fa0, video\n"; + +/// A sellers file that reads only by RFC 8259's grammar outside what §8.7 reads: numbers past +/// binary64's range and of 400 digits, an escape of a lone surrogate in members the reader +/// skips, a member name escaping a character with a pair of surrogates, a member nested 64 +/// deep with the file's object, and a `seller_id` that escapes one of its characters. +fn gateway_sellers() -> String { + format!( + "{{\n \"version\": 1.0,\n \"x-audited\": 1e400,\n \"x-serial\": {},\n \"x-note\": \"\\ud800 escapes a lone surrogate\",\n \"x-\\ud83d\\udcfa\": true,\n \"ext\": {}{},\n \"sellers\": [\n {{\"seller_id\": \"pub-\\u0034001\", \"seller_type\": \"PUBLISHER\", \"ext\": {{\"note\": \"\\udfff\"}}}}\n ]\n}}\n", + "7".repeat(400), + "[".repeat(MAX_SELLERS_DEPTH - 1), + "]".repeat(MAX_SELLERS_DEPTH - 1), + ) +} + +/// Sellers files that do not read (§8.7), each listing the gateway's seller `pub-4001`: one +/// nested 65 deep, one whose other seller's id escapes a lone surrogate, one with `sellers` +/// twice, and one holding a constant RFC 8259 does not have. +fn gateway_sellers_unread() -> [(&'static str, &'static str, String); 4] { + let listed = "{\"seller_id\": \"pub-4001\", \"seller_type\": \"PUBLISHER\"}"; + [ + ( + "sellers_file_too_deep", + "a sellers file whose member `ext` nests 65 deep with the file's object, one past §8.7's limit", + format!("{{\"ext\": {}{}, \"sellers\": [{listed}]}}\n", "[".repeat(MAX_SELLERS_DEPTH), "]".repeat(MAX_SELLERS_DEPTH)), + ), + ( + "sellers_file_lone_surrogate", + "a sellers file whose second seller's id escapes a lone surrogate", + format!("{{\"sellers\": [{listed}, {{\"seller_id\": \"\\ud800\"}}]}}\n"), + ), + ("sellers_file_sellers_twice", "a sellers file with two members `sellers`", format!("{{\"sellers\": [], \"sellers\": [{listed}]}}\n")), + ("sellers_file_nan", "a sellers file holding NaN, which is no JSON number", format!("{{\"x-audited\": NaN, \"sellers\": [{listed}]}}\n")), + ] +} + +fn supply(name: &str, bytes: &[u8]) -> (String, Vec) { + (format!("supply.{name}"), bytes.to_vec()) +} + +fn supply_files() -> Vec<(String, Vec)> { + vec![ + supply("ssp.example.sellers.json", SSP_SELLERS.as_bytes()), + supply("exchange.example.sellers.json", EXCHANGE_SELLERS.as_bytes()), + supply("channel.example.app-ads.txt", APP_ADS.as_bytes()), + ] +} + +fn node(asi: &str, sid: &str, sellers: Option<&[u8]>) -> SupplyNode { + SupplyNode { asi: asi.into(), sid: sid.into(), hp: 1, rid: None, name: None, domain: None, sellers: sellers.map(|t| sha(&[t])) } +} + +fn app_ads(file: &[u8]) -> Option { + Some(Authorised { file: APP_ADS_TXT.into(), domain: "channel.example".into(), sha256: sha(&[file]) }) +} + +/// A complete chain from the channel's own account at the SSP through the exchange, checked +/// against the channel's app-ads.txt. +fn full_chain() -> SupplyChain { + SupplyChain { + ver: "1.0".into(), + complete: 1, + nodes: vec![ + SupplyNode { rid: Some("req-60-1".into()), ..node("ssp.example", "pub-1001", Some(SSP_SELLERS.as_bytes())) }, + SupplyNode { rid: Some("req-60-1-x".into()), ..node("exchange.example", "ssp-77", Some(EXCHANGE_SELLERS.as_bytes())) }, + ], + authorised: app_ads(APP_ADS.as_bytes()), + } +} + +/// A complete chain of one node, the channel's own account at the exchange: the request the +/// decider sent to the exchange directly. +fn direct_chain() -> SupplyChain { + SupplyChain { + ver: "1.0".into(), + complete: 1, + nodes: vec![SupplyNode { rid: Some("req-60-2".into()), ..node("exchange.example", "pub-2001", Some(EXCHANGE_SELLERS.as_bytes())) }], + authorised: app_ads(APP_ADS.as_bytes()), + } +} + +/// An incomplete chain of one node, the gateway's seller `pub-4001`, checked against the +/// sellers file given. +fn gateway_chain(sellers: &[u8]) -> SupplyChain { + SupplyChain { ver: "1.0".into(), complete: 0, nodes: vec![node("gateway.example", "pub-4001", Some(sellers))], authorised: None } +} + +fn consent(gpp: &str, gpp_sid: &[u16], child_directed: u8) -> Consent { + Consent { gpp: gpp.into(), gpp_sid: gpp_sid.to_vec(), child_directed } +} + +/// A policy of version 3, with no presentation fields and this audience. +fn terms_policy(room: &Room, audience: u8) -> AdPolicy { + policy(room, |p| { + p.presentation = Some(Requirement::default()); + p.audience = Some(audience); + }) +} + +/// A receipt of version 2: this consent signal, and a chain or none for each impression. +fn with_terms(s: &Seal, c: Consent, chains: Vec>) -> Seal { + Seal { terms: Some(RequestTerms { consent: c, supply_chains: chains }), ..s.clone() } +} + +/// A receipt's JSON with one member of its request terms changed after it was signed. +fn altered(r: &SsaiBreakReceipt, f: impl FnOnce(&mut Value)) -> Value { + let mut j = r.to_json(); + f(&mut j); + j +} + +fn terms_vectors() -> Vec { + let mut out = Vec::new(); + let lb = live_break(SPEC); + let r = &lb.room; + let p = terms_policy(r, 0); + // The first viewer's two ads answered two requests: one sent to the SSP and resold + // through the exchange, one sent to the exchange. + let first_chains = || vec![Some(full_chain()), Some(direct_chain())]; + let first = with_terms(&lb.first, consent(GPP, &[2], 0), first_chains()); + let joined_chain = SupplyChain { complete: 0, nodes: vec![node("exchange.example", "ssp-77", Some(b"a sellers file the verifier does not hold".as_slice()))], authorised: None, ..full_chain() }; + let joined = with_terms(&lb.joined, consent("", &[], 1), vec![Some(joined_chain)]); + let held = |mut v: Vector, files: Vec<(String, Vec)>| { + v.supply_files = files; + v + }; + out.push(held( + vector( + "consent_and_chain", + "the live break under a policy of version 3: the viewer at the break's start gave a GPP string and its applicable section, and its two ads answered two requests, the first sold from the channel's own account at an SSP through an exchange and the second from the channel's own account at the exchange, two complete chains whose sellers files and app-ads.txt the verifier holds; the viewer who joins gave no string and a child-directed request, and its ad was sold through an incomplete chain of one node whose sellers file the verifier does not hold", + r, + &p, + vec![seal(r, &p, &first), seal(r, &p, &joined)], + vec![pod_one(), pod_one()], + Intent::Accept, + ), + supply_files(), + )); + // A room the policy marks as directed to children. + let mut kids = room(SPEC); + let b = kids.cue(PRODUCER, 45 * S, cue_type::BREAK, 21, 15_000); + let child = terms_policy(&kids, audience::CHILD); + let child_seal = with_terms(&Seal::new(b, 15_000, 11, asset_format::HLS, pod_one(), &[5]), consent("", &[], 1), vec![None]); + out.push(vector( + "child_room", + "a 15 s break in a room whose policy marks it as directed to children: the viewer gave no GPP string, and the decider states the request child-directed and no supply chain for its ad", + &kids, + &child, + vec![seal(&kids, &child, &child_seal)], + vec![pod_one()], + Intent::Accept, + )); + // The reading rules of §8.7, each needed: an ads.txt read liberally, and a sellers file + // read by the grammar alone outside what §8.7 reads. + let liberal = SupplyChain { + ver: "1.0".into(), + complete: 1, + nodes: vec![node("ssp.example", "pub-3001", None), node("exchange.example", "ssp-77", None), node("wholesale.example", "seat-5", None), node("bridge.example", "br-12", None)], + authorised: Some(Authorised { file: ADS_TXT.into(), domain: "channel.example".into(), sha256: sha(&[ADS_LIBERAL]) }), + }; + out.push(held( + vector( + "authorised_read_liberally", + "both of the first viewer's ads answered one request, so its receipt states one chain twice: four nodes, each authorised by the channel's ads.txt only when it is read by §8.7's rules: a record of four fields, a record whose advertising system is in other letter cases and that ends with CR LF, a record whose relationship is in lower case and that ends with a CR alone, and a record with an extension field", + r, + &p, + vec![seal(r, &p, &with_terms(&lb.first, consent(GPP, &[2], 0), vec![Some(liberal.clone()), Some(liberal)]))], + vec![pod_one()], + Intent::Accept, + ), + vec![supply("channel.example.ads.txt", ADS_LIBERAL)], + )); + let gateway = gateway_sellers(); + out.push(held( + vector( + "sellers_read_by_grammar", + "the first viewer's first ad sold through the gateway's seller pub-4001, listed in a sellers file that reads only when what §8.7 does not read is read by RFC 8259's grammar alone: numbers past binary64's range and of 400 digits, escapes of lone surrogates in skipped members, a member nested 64 deep with the file's object, and a seller id that escapes one of its characters; the second ad states no chain", + r, + &p, + vec![seal(r, &p, &with_terms(&lb.first, consent(GPP, &[2], 0), vec![Some(gateway_chain(gateway.as_bytes())), None]))], + vec![pod_one()], + Intent::Accept, + ), + vec![supply("gateway.example.sellers.json", gateway.as_bytes())], + )); + // Refused: a receipt whose terms were altered after signing, each signed field alone. + let refuse = |name, about, room: &Room, policy: &AdPolicy, receipts: Vec, pods: Vec>, reason| { + let mut v = vector(name, about, room, policy, vec![], pods, Intent::Refuse("receipt", reason)); + v.receipts = receipts; + v.supply_files = supply_files(); + v + }; + let good = seal(r, &p, &first); + out.push(refuse( + "receipt_consent_altered", + "the first viewer's receipt with the last section of its GPP string changed after the decider signed it", + r, + &p, + vec![altered(&good, |j| j["consent"]["gpp"] = json!(GPP.replace("1YNN", "1YYN")))], + vec![pod_one()], + ev(events::Reason::BadSignature), + )); + let kid = seal(&kids, &child, &child_seal); + let mut v = refuse( + "receipt_child_flag_cleared", + "the child room's receipt with its child-directed flag cleared after the decider signed it", + &kids, + &child, + vec![altered(&kid, |j| j["consent"]["child_directed"] = json!(0))], + vec![pod_one()], + ev(events::Reason::BadSignature), + ); + v.supply_files = vec![]; + out.push(v); + out.push(refuse( + "receipt_chain_altered", + "the first viewer's receipt with the seller id of its first chain's first node changed after the decider signed it", + r, + &p, + vec![altered(&good, |j| j["supply_chains"][0]["nodes"][0]["sid"] = json!("pub-1002"))], + vec![pod_one()], + ev(events::Reason::BadSignature), + )); + out.push(refuse( + "receipt_chains_swapped", + "the first viewer's receipt with its two impressions' chains swapped after the decider signed it: each chain is bound to its impression", + r, + &p, + vec![altered(&good, |j| { + let a = j["supply_chains"][0].take(); + j["supply_chains"][0] = j["supply_chains"][1].take(); + j["supply_chains"][1] = a; + })], + vec![pod_one()], + ev(events::Reason::BadSignature), + )); + // Refused: the JSON carriage of §8.2. + let mut v1_consent = seal(r, &p, &lb.first).to_json(); + v1_consent.as_object_mut().expect("an object").insert("consent".into(), good.to_json()["consent"].clone()); + for (name, about, j) in [ + ("receipt_version_three", "a receipt stating version 3, which §8.2 does not define", altered(&good, |j| j["version"] = json!(3))), + ("receipt_v1_with_consent", "a receipt of version 1, with no version member, carrying a consent signal", v1_consent), + ( + "receipt_v2_without_chains", + "a receipt of version 2 without its member supply_chains", + altered(&good, |j| { + j.as_object_mut().expect("an object").remove("supply_chains"); + }), + ), + ] { + out.push(refuse(name, about, r, &p, vec![j], vec![pod_one()], Reason::ReceiptMalformed)); + } + // Refused: the rules of §8.6 and §8.7, and steps 13 and 14 of §9.3. + let one = |s: &Seal| vec![seal(r, &p, s).to_json()]; + let signal = |name, about, c: Consent| refuse(name, about, r, &p, one(&with_terms(&lb.first, c, first_chains())), vec![pod_one()], Reason::ConsentInvalid); + out.push(signal("receipt_gpp_empty_section", "a receipt whose GPP string has an empty part between two delimiters", consent("DBACNYA~~1YNN", &[2], 0))); + out.push(signal( + "receipt_gpp_not_url_safe", + "a receipt whose GPP string holds a +, a character of base64 that is not among the URL-safe ones the GPP string's text lists", + consent(&GPP.replace("B-Cg", "B+Cg"), &[2], 0), + )); + out.push(signal("receipt_sections_without_string", "a receipt stating section 2 as applicable, with no GPP string", consent("", &[2], 0))); + out.push(signal("receipt_section_repeated", "a receipt stating section 2 twice", consent(GPP, &[2, 2], 0))); + out.push(signal("receipt_child_flag_two", "a receipt whose child-directed flag is 2", consent(GPP, &[2], 2))); + let chain = |name, about, c: SupplyChain| refuse(name, about, r, &p, one(&with_terms(&lb.first, consent(GPP, &[2], 0), vec![Some(c), Some(direct_chain())])), vec![pod_one()], Reason::SupplyChainInvalid); + out.push(chain("receipt_chain_ver", "a receipt whose first chain states its version as 1, with no minor part", SupplyChain { ver: "1".into(), ..full_chain() })); + out.push(chain("receipt_chain_complete_two", "a receipt whose first chain states complete as 2", SupplyChain { complete: 2, ..full_chain() })); + let mut url = full_chain(); + url.nodes[0].asi = "https://ssp.example".into(); + out.push(chain("receipt_asi_url", "a receipt whose first chain names its first advertising system by a URL, not a domain", url)); + let mut file = full_chain(); + file.authorised.as_mut().expect("a file").file = "sellers.json".into(); + out.push(chain("receipt_authorised_file", "a receipt whose first chain names, as its authorised-sellers file, a file that is neither ads.txt nor app-ads.txt", file)); + let mut host = full_chain(); + host.authorised.as_mut().expect("a file").domain = "channel".into(); + out.push(chain("receipt_authorised_domain", "a receipt whose first chain names its authorised-sellers file at a host of one label", host)); + out.push(refuse( + "receipt_chains_count", + "a receipt of two impressions stating one chain", + r, + &p, + one(&with_terms(&lb.first, consent(GPP, &[2], 0), vec![Some(full_chain())])), + vec![pod_one()], + Reason::SupplyChainInvalid, + )); + out.push(refuse( + "receipt_without_terms", + "a receipt of version 1, which states no consent signal, under a policy of version 3", + r, + &p, + one(&lb.first), + vec![pod_one()], + Reason::RequestTermsAbsent, + )); + let mut v = refuse( + "child_flag_unset", + "the child room's receipt signed with its child-directed flag 0", + &kids, + &child, + vec![seal(&kids, &child, &with_terms(&child_seal, consent("", &[], 0), vec![None])).to_json()], + vec![pod_one()], + Reason::ChildDirectedUnset, + ); + v.supply_files = vec![]; + out.push(v); + // Step 14, against the files held. + let checked = |name, about, c: SupplyChain, reason, extra: Vec<(String, Vec)>| { + let mut v = refuse(name, about, r, &p, one(&with_terms(&lb.first, consent(GPP, &[2], 0), vec![Some(c), Some(direct_chain())])), vec![pod_one()], reason); + v.supply_files.extend(extra); + v + }; + let mut not_sellers = full_chain(); + not_sellers.nodes[0].sellers = Some(sha(&[NOT_SELLERS.as_bytes()])); + out.push(checked( + "sellers_file_malformed", + "a chain whose first node names, as its sellers file, a file the verifier holds that has no sellers", + not_sellers, + Reason::SupplyFileMalformed, + vec![supply("not-sellers.json", NOT_SELLERS.as_bytes())], + )); + for (name, about, file) in gateway_sellers_unread() { + let mut v = refuse(name, about, r, &p, one(&with_terms(&lb.first, consent(GPP, &[2], 0), vec![Some(gateway_chain(file.as_bytes())), None])), vec![pod_one()], Reason::SupplyFileMalformed); + v.supply_files = vec![supply(&format!("{}.sellers.json", name.trim_start_matches("sellers_file_").replace('_', "-")), file.as_bytes())]; + out.push(v); + } + out.push(checked( + "authorised_file_not_utf8", + "a complete chain checked against an app-ads.txt that is not UTF-8", + SupplyChain { authorised: app_ads(NOT_UTF8), ..full_chain() }, + Reason::SupplyFileMalformed, + vec![supply("not-utf8.app-ads.txt", NOT_UTF8)], + )); + let mut unlisted = full_chain(); + unlisted.nodes[0].sid = "pub-1003".into(); + out.push(checked("seller_unlisted", "a chain whose first node's seller id the SSP's sellers file does not list", unlisted, Reason::SupplySellerUnlisted, vec![])); + let mut reseller_first = full_chain(); + reseller_first.nodes.remove(0); + out.push(checked( + "seller_unauthorised", + "a chain stated complete whose first node is the SSP's account at the exchange, which the channel's app-ads.txt lists as a reseller, not as its own", + reseller_first, + Reason::SupplySellerUnauthorised, + vec![], + )); + let mut absent = full_chain(); + absent.nodes.push(node("wholesale.example", "seat-9", None)); + out.push(checked( + "seller_absent_from_authorised", + "a chain whose third node, the exchange's account at a wholesaler, the channel's app-ads.txt does not name", + absent, + Reason::SupplySellerUnauthorised, + vec![], + )); + out.push(checked( + "authorised_record_passed_over", + "a complete chain whose second node, the SSP's account ssp-99 at the exchange, is named in the channel's app-ads.txt only by lines a reader passes over: a comment, and a line of five fields", + SupplyChain { + nodes: vec![node("ssp.example", "pub-1001", Some(SSP_SELLERS.as_bytes())), node("exchange.example", "ssp-99", None)], + authorised: app_ads(PASSED_OVER), + ..full_chain() + }, + Reason::SupplySellerUnauthorised, + vec![supply("passed-over.app-ads.txt", PASSED_OVER)], + )); + let mut bad = policy(r, |p| { + p.presentation = Some(Requirement::default()); + p.audience = Some(2); + }); + bad = bad.sign(&key(AUTHORITY)); + out.push(vector("policy_audience_bit", "a policy of version 3 whose audience sets a bit above bit 0", r, &bad, vec![], vec![], Intent::Refuse("policy", Reason::AdPolicyMalformed))); + out +} + fn build() -> Vec<(Vector, Run)> { let mut out = Vec::new(); - for v in accepted().into_iter().chain(refused()) { + for v in accepted().into_iter().chain(refused()).chain(terms_vectors()) { let run = v.run(); let ok = match (v.intent, run.outcome()) { (Intent::Accept, Outcome::Accept) => true, @@ -829,11 +1260,17 @@ pub(super) fn run_json(name: &str, run: &Run) -> Map { run.receipts .iter() .map(|r| { - json!({ + let mut f = json!({ "identity": hex(&r.identity), "asset_list": String::from_utf8(r.asset_list.clone()).expect("an asset list is UTF-8"), "spend_micros": r.spend_micros, - }) + }); + // A receipt of version 2 states its consent signal and its supply chains. + if let Some(t) = &r.terms { + f["consent"] = t.consent.to_json(); + f["supply_chains"] = t.supply_chains.iter().map(|c| c.as_ref().map_or(Value::Null, |(c, cf)| cf.to_json(c))).collect(); + } + f }) .collect(), ), @@ -855,12 +1292,16 @@ fn vector_json(v: &Vector, run: &Run) -> Value { o.insert("pt0_ms".into(), json!(v.pt0_ms)); o.insert("receipts".into(), Value::Array(v.receipts.clone())); o.insert("pods".into(), Value::Array(v.pods.iter().map(|p| pod_json(p)).collect())); + if !v.supply_files.is_empty() { + o.insert("supply_files".into(), json!(v.supply_files.iter().map(|(n, _)| n).collect::>())); + } o.extend(run_json(v.name, run)); Value::Object(o) } -/// Write the corpus: `vectors.json` and each accepted room's HLS tags. -pub fn generate() -> Vec<(String, String)> { +/// Write the corpus: `vectors.json`, each accepted room's HLS tags, and the supply files the +/// vectors hold, each file by its name and its bytes. +pub fn generate() -> Vec<(String, Vec)> { let rooms = build(); let seen: BTreeSet<&str> = rooms .iter() @@ -872,12 +1313,17 @@ pub fn generate() -> Vec<(String, String)> { for r in Reason::ALL { assert!(seen.contains(r.code()), "no vector gives {}", r.code()); } - let mut files = Vec::new(); + let mut files: Vec<(String, Vec)> = Vec::new(); + let mut supply: BTreeMap> = BTreeMap::new(); for (v, run) in &rooms { if matches!(run.outcome(), Outcome::Accept) { - files.push((hls_file(v.name), hls_body(run))); + files.push((hls_file(v.name), hls_body(run).into_bytes())); + } + for (n, b) in &v.supply_files { + assert!(supply.insert(n.clone(), b.clone()).is_none_or(|was| was == *b), "two vectors give the file {n} other bytes"); } } + files.extend(supply); let principals: Map = PRINCIPALS.iter().map(|(n, i)| (n.to_string(), json!(hex(&pk(*i))))).collect(); let doc = json!({ "about": "extensions/live-ads.md §11: rooms whose cues open ad breaks, their ad policies, the HLS interstitials and DASH alternative-presentation events each break maps onto, each viewer's asset list and break-form wai.video.ssai receipt, and the spend against the decider grant's ceiling", @@ -886,7 +1332,7 @@ pub fn generate() -> Vec<(String, String)> { }); let mut body = serde_json::to_string_pretty(&doc).expect("JSON"); body.push('\n'); - files.push(("vectors.json".into(), body)); + files.push(("vectors.json".into(), body.into_bytes())); files.sort(); files } @@ -902,11 +1348,12 @@ pub(super) fn hex_list(v: &Value) -> Option>> { v.as_array()?.iter().map(|h| unhex(h.as_str()?)).collect() } -/// Check a corpus's files against this module: every room gets the outcome and everything -/// else it states, and every tag file is the run's. Returns the failures. -pub fn verify(files: &BTreeMap) -> Vec { +/// Check a corpus's files, each by its name and its bytes, against this module: every room +/// gets the outcome and everything else it states, and every tag file is the run's. Returns +/// the failures. +pub fn verify(files: &BTreeMap>) -> Vec { let mut fails = Vec::new(); - let Some(doc) = files.get("vectors.json").and_then(|s| serde_json::from_str::(s).ok()) else { + let Some(doc) = files.get("vectors.json").and_then(|s| serde_json::from_slice::(s).ok()) else { return vec!["vectors.json does not read".into()]; }; for v in doc["vectors"].as_array().into_iter().flatten() { @@ -923,9 +1370,13 @@ pub fn verify(files: &BTreeMap) -> Vec { v["pt0_ms"].as_u64()?, v["receipts"].as_array()?.clone(), pods, + match v.get("supply_files") { + None => vec![], + Some(n) => n.as_array()?.iter().map(|n| files.get(n.as_str()?).cloned()).collect::>>()?, + }, )) })(); - let Some((descriptor, grants, head, chains, policy, pt0_ms, receipts, pods)) = parsed else { + let Some((descriptor, grants, head, chains, policy, pt0_ms, receipts, pods, supply_files)) = parsed else { fails.push(format!("vector {name}: its inputs do not read")); continue; }; @@ -941,6 +1392,7 @@ pub fn verify(files: &BTreeMap) -> Vec { claims: &[], creatives: &[], issuers: &[], + supply_files: &supply_files, }); let want = run_json(&name, &run); for (k, w) in &want { @@ -948,7 +1400,7 @@ pub fn verify(files: &BTreeMap) -> Vec { fails.push(format!("vector {name}: {k} is {w}, the corpus states {}", v.get(k).unwrap_or(&Value::Null))); } } - if matches!(run.outcome(), Outcome::Accept) && files.get(&hls_file(&name)) != Some(&hls_body(&run)) { + if matches!(run.outcome(), Outcome::Accept) && files.get(&hls_file(&name)).map(Vec::as_slice) != Some(hls_body(&run).as_bytes()) { fails.push(format!("vector {name}: {} differs from the tags the room writes", hls_file(&name))); } } diff --git a/wai/wai-rs/src/live_ads/presentation_corpus.rs b/wai/wai-rs/src/live_ads/presentation_corpus.rs index 7f83419a3..338172d36 100644 --- a/wai/wai-rs/src/live_ads/presentation_corpus.rs +++ b/wai/wai-rs/src/live_ads/presentation_corpus.rs @@ -216,6 +216,7 @@ impl Vector { claims: &claims, creatives: &cr, issuers: &is, + supply_files: &[], }) } } @@ -615,6 +616,7 @@ pub fn verify(files: &BTreeMap) -> Vec { claims: &claims, creatives: &creatives, issuers: &issuers, + supply_files: &[], }); for (k, w) in &run_json(&name, &run) { if v.get(k) != Some(w) { diff --git a/wai/wai-rs/src/opportunity_report.rs b/wai/wai-rs/src/opportunity_report.rs index f3de7efc9..4201c337f 100644 --- a/wai/wai-rs/src/opportunity_report.rs +++ b/wai/wai-rs/src/opportunity_report.rs @@ -532,6 +532,7 @@ pub fn verify_room(r: &RoomInput) -> Result { claims: &[], creatives: &[], issuers: &[], + supply_files: &[], }); match run.outcome() { ads::Outcome::Accept => {} diff --git a/wai/wai-rs/src/opportunity_report/corpus.rs b/wai/wai-rs/src/opportunity_report/corpus.rs index 1d5195d20..40ab0b7a0 100644 --- a/wai/wai-rs/src/opportunity_report/corpus.rs +++ b/wai/wai-rs/src/opportunity_report/corpus.rs @@ -147,6 +147,7 @@ impl RoomBuild { asset_base: "https://ads.channel.example/sgai/".into(), creative_base: "https://cdn.channel.example/creatives/".into(), presentation: None, + audience: None, sig: [0; 64], } .sign(&key("authority")); diff --git a/wai/wai-rs/src/receipt_ledger.rs b/wai/wai-rs/src/receipt_ledger.rs index 79879be3a..bbe7cddfe 100644 --- a/wai/wai-rs/src/receipt_ledger.rs +++ b/wai/wai-rs/src/receipt_ledger.rs @@ -1414,6 +1414,7 @@ fn ads_source(o: &Map) -> Result { claims: &claims, creatives: &creatives, issuers: &issuers, + supply_files: &[], }; let run = ads::verify(&input); match run.outcome() { diff --git a/wai/wai-rs/src/stitched_presentation.rs b/wai/wai-rs/src/stitched_presentation.rs index b35213e87..5b04d02df 100644 --- a/wai/wai-rs/src/stitched_presentation.rs +++ b/wai/wai-rs/src/stitched_presentation.rs @@ -20,7 +20,9 @@ //! - §3: markers, their bodies and digests, and reading them from a playlist, an asset list //! or an MPD ([`markers`]); //! - §4: the pod-input record, its rules and signature; §4.5's form-1 response is -//! [`crate::wai_video_ssai::SsaiReceipt`]'s JSON carriage; +//! [`crate::wai_video_ssai::SsaiReceipt`]'s JSON carriage; §4.7's consent signal, which a +//! record of version 2 carries, is live-ads §8.6's +//! ([`crate::wai_video_ssai::terms::Consent`]); //! - §5: the stitched claim, its session, its rules, its signature and its attestation //! challenge; //! - §6: the ad region and its share, from live-compose §5.9; @@ -39,6 +41,7 @@ use sha2::{Digest, Sha256}; use crate::live_ads::presentation::{self as ap, Creative, Issuer, Requirement, Span}; use crate::live_compose::layout::{self as lc, kind as frame_kind}; +use crate::wai_video_ssai::terms::Consent; use crate::wai_video_ssai::SsaiReceipt; pub mod corpus; @@ -52,8 +55,11 @@ pub const CAPABILITY: &str = "wai.video.stitched_presentation"; pub const POD_MAGIC: [u8; 4] = *b"WSI1"; /// The magic of a stitched claim (§5.1). pub const CLAIM_MAGIC: [u8; 4] = *b"WSC1"; -/// The version of each record this revision defines. +/// The version of each record this revision defines, and of a pod-input record with no +/// consent signal. pub const VERSION: u8 = 1; +/// The version of a pod-input record that carries the consent signal (§4.1, §4.7). +pub const POD_VERSION_CONSENT: u8 = 2; /// The domain of a pod-input record's signature (§4.3). pub const POD_DOMAIN: &[u8] = b"wai:stitched-pod-input\x01"; /// The domain of a stitched claim's signature (§5.3). @@ -379,6 +385,8 @@ pub struct PodInput { /// The measurement terms: ad-presentation §8's fields. pub requirement: Requirement, pub ads: Vec, + /// The consent signal of a record of version 2 (§4.7); `None` for version 1. + pub consent: Option, pub signer: [u8; 32], pub sig: [u8; 64], } @@ -387,7 +395,7 @@ impl PodInput { /// Its bytes before `sig` (§4.1): what its digest covers. pub fn body(&self) -> Vec { let mut o = POD_MAGIC.to_vec(); - o.push(VERSION); + o.push(if self.consent.is_some() { POD_VERSION_CONSENT } else { VERSION }); o.push(self.carriage); o.extend_from_slice(&self.session); short(&mut o, &self.avail_id); @@ -427,6 +435,15 @@ impl PodInput { o.push(a.price_source); o.extend_from_slice(&a.evidence); } + if let Some(c) = &self.consent { + long(&mut o, c.gpp.as_bytes()); + assert!(c.gpp_sid.len() <= u8::MAX as usize); + o.push(c.gpp_sid.len() as u8); + for s in &c.gpp_sid { + o.extend_from_slice(&s.to_le_bytes()); + } + o.push(c.child_directed); + } o.extend_from_slice(&self.signer); o } @@ -467,7 +484,11 @@ impl PodInput { pub fn parse(b: &[u8]) -> Result { let mut r = Rd { b, p: 0 }; let rec = (|| -> Option { - if r.d::<4>()? != POD_MAGIC || r.u8()? != VERSION { + if r.d::<4>()? != POD_MAGIC { + return None; + } + let version = r.u8()?; + if !matches!(version, VERSION | POD_VERSION_CONSENT) { return None; } let carriage = r.u8()?; @@ -499,6 +520,15 @@ impl PodInput { evidence: r.d()?, }); } + let consent = if version == POD_VERSION_CONSENT { + // The GPP string is held as bytes until the rules read it as text (§4.7). + let gpp = String::from_utf8(r.long()?).ok()?; + let n = r.u8()? as usize; + let gpp_sid = (0..n).map(|_| r.u16()).collect::>>()?; + Some(Consent { gpp, gpp_sid, child_directed: r.u8()? }) + } else { + None + }; let signer = r.d()?; let sig = r.d()?; Some(PodInput { @@ -515,6 +545,7 @@ impl PodInput { frame, requirement, ads, + consent, signer, sig, }) @@ -566,6 +597,13 @@ impl PodInput { return false; } } + // Rule 10: a record of version 2's consent signal holds live-ads §8.6's rules, and a + // shared record, which every viewer is shown, carries no viewer's GPP string (§4.7). + if let Some(c) = &self.consent + && (!c.well_formed() || (self.shared() && !c.gpp.is_empty())) + { + return false; + } true } } @@ -805,6 +843,8 @@ pub struct RecordFacts { /// region's rectangles, none for the full picture. pub share_bp: u16, pub region: Option>, + /// When accepted: the consent signal of a record of version 2 (§4.7). + pub consent: Option, } /// A claim, as a verification states it (§7.5). @@ -919,6 +959,7 @@ pub fn verify_through(input: &Input, attestation: bool) -> Verification { stated_spend_micros: None, share_bp: 0, region: None, + consent: None, }; let mut quartiles_stated = None; let judged = (|| -> Result { @@ -960,6 +1001,7 @@ pub fn verify_through(input: &Input, attestation: bool) -> Verification { Ok(rec) => { f.share_bp = rec.frame.share_bp(); f.region = rec.frame.region().map(|r| r.region); + f.consent = rec.consent.clone(); accepted.push(Some(rec)); } Err(e) => { @@ -1176,6 +1218,7 @@ mod tests { price_source: price_source::PRICING, evidence: [0; 32], }], + consent: None, signer: [0; 32], sig: [0; 64], } @@ -1205,6 +1248,27 @@ mod tests { assert!(with(&|x| x.frame = Frame { kind: 3, width: 1920, height: 1080, corner: 4, window_w: 1440, window_h: 810, gap: 0, aspect: 0 }).is_ok()); } + #[test] + fn a_record_of_version_two_carries_its_consent_signal() { + let gpp = "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN"; + let r = PodInput { consent: Some(Consent { gpp: gpp.into(), gpp_sid: vec![2], child_directed: 0 }), ..record() }.sign(&key(1)); + let b = r.to_bytes(); + assert_eq!(b[4], POD_VERSION_CONSENT); + assert_eq!(PodInput::parse(&b), Ok(r.clone())); + assert_eq!(r.check_signature(), Ok(())); + assert_eq!(record().to_bytes()[4], VERSION, "a record with no consent signal keeps version 1 and its bytes"); + let mut altered = r.clone(); + altered.consent.as_mut().expect("a signal").child_directed = 1; + assert_eq!(PodInput::parse(&altered.to_bytes()).map(|x| x.check_signature()), Ok(Err(Reason::BadSignature)), "the signal is signed"); + let with = |c: Consent, session: [u8; 16]| PodInput::parse(&PodInput { consent: Some(c), session, ..record() }.sign(&key(1)).to_bytes()); + let bad = Err(Reason::PodInputMalformed); + assert_eq!(with(Consent { gpp: "DBACNYA~~1YNN".into(), gpp_sid: vec![2], child_directed: 0 }, [7; 16]), bad); + assert_eq!(with(Consent { gpp: String::new(), gpp_sid: vec![2], child_directed: 0 }, [7; 16]), bad); + assert_eq!(with(Consent { gpp: gpp.into(), gpp_sid: vec![2], child_directed: 2 }, [7; 16]), bad); + assert_eq!(with(Consent { gpp: gpp.into(), gpp_sid: vec![2], child_directed: 0 }, [0; 16]), bad, "a shared record carries no viewer's string"); + assert!(with(Consent { gpp: String::new(), gpp_sid: vec![], child_directed: 1 }, [0; 16]).is_ok(), "a shared break of a child-directed stream"); + } + #[test] fn a_price_is_cpm_scaled_to_one_impression() { // 25.00 per thousand in a currency of two decimals is 2.5 minor units, 2 500 000 µ. diff --git a/wai/wai-rs/src/stitched_presentation/corpus.rs b/wai/wai-rs/src/stitched_presentation/corpus.rs index 7c96cae26..ca1b9e4f8 100644 --- a/wai/wai-rs/src/stitched_presentation/corpus.rs +++ b/wai/wai-rs/src/stitched_presentation/corpus.rs @@ -26,6 +26,7 @@ use super::*; use crate::live_ads::presentation::{geometry, require, rule, span_flag, token_type, CreativeObject}; use crate::live_ads::presentation_corpus::{ISSUER_D, ISSUER_N, ISSUER_NAME, RFC9578_PKI}; use crate::quantum_ops::GrantRef; +use crate::wai_video_ssai::terms::Consent; use crate::wai_video_ssai::{decide, pod_hash, AdCandidate}; fn sha(parts: &[&[u8]]) -> [u8; 32] { @@ -251,6 +252,8 @@ struct RecordDraft { frame: Frame, requirement: Requirement, ads: Vec, + /// The consent signal of a record of version 2 (§4.7); none for version 1. + consent: Option, } impl RecordDraft { @@ -269,6 +272,7 @@ impl RecordDraft { frame: self.frame, requirement: self.requirement.clone(), ads: self.ads.clone(), + consent: self.consent.clone(), signer: [0; 32], sig: [0; 64], } @@ -422,6 +426,7 @@ fn hls_stitched() -> Vector { frame: Frame::FULL, requirement: video(), ads, + consent: None, }; let ads1 = vec![ pod_ad(0, &m_a, "srv-v1-0", "srv-v1-0", "UID-A", &a, 2_500_000, price_source::PRICING, [0; 32]), @@ -530,6 +535,7 @@ fn hls_interstitial() -> Vector { pod_ad(0, &im(0), &u1, "ad-i1", "UID-I1", &i1, 4_200_000, price_source::WINNING_BID, sha(&[bid.as_bytes()])), pod_ad(1, &im(1), &u2, "ad-i2", "UID-I2", &i2, 2_000_000, price_source::PRICING, [0; 32]), ], + consent: None, }; let records = vec![ (rec(11).signed(2), "viewer 11's interstitial: ad-i1 at a winning bid's price, its bid document bound by digest, and ad-i2 at the response's CPM 20.00 USD", ok), @@ -588,6 +594,7 @@ fn squeezeback() -> Vector { frame, requirement: video(), ads: vec![pod_ad(0, &m, "srv-sq-1", "srv-sq-1", "UID-S", &s, 1_200_000, price_source::PRICING, [0; 32])], + consent: None, }; let share = frame.share_bp(); let d = |p: u8, spans: Vec| ClaimDraft { share_bp: share, ..ClaimDraft::new(&m, "srv-sq-1", &mpd, p, &s, spans) }; @@ -638,6 +645,7 @@ fn double_box() -> Vector { frame, requirement: video(), ads: vec![pod_ad(0, &m, "srv-db-1", "srv-db-1", "", &dc, 900_000, price_source::STATED, [0; 32])], + consent: None, }; let share = frame.share_bp(); let d = |p: u8, g: u8, spans: Vec| ClaimDraft { share_bp: share, geometry: g, ..ClaimDraft::new(&m, "srv-db-1", &playlist, p, &dc, spans) }; @@ -702,6 +710,7 @@ fn form1_prefetch() -> Vector { pod_ad(0, &dr("f1-ad-0", 1_200_000), "srv-f1-0", "ad-f1", "", &f1, 3_000_000, price_source::STATED, [0; 32]), pod_ad(1, &dr("f1-ad-1", 1_215_000), "srv-f1-1", "ad-f2", "", &f2, price2, price_source::STATED, [0; 32]), ], + consent: None, }; let mut other = rec(42, 2_100_000); other.ads.iter_mut().for_each(|a| a.marker[0] ^= 0x80); @@ -725,8 +734,72 @@ fn form1_prefetch() -> Vector { out } +/// The example GPP string the GPP string's text gives for a URL-based service, with its +/// applicable section, 2. WAI does not decode it. +const GPP: &str = "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN"; + +fn consent() -> Vector { + let ok = Ok(()); + let (a, b) = (c_a(), c_b()); + let p51 = stitched_playlist("break-600", 600_000, &[Stitched { id: "v51-ad-0", start_ms: 600_000, ad_id: "srv-v51-0", creative: a.clone() }]); + let p52 = stitched_playlist("break-600", 600_000, &[Stitched { id: "v52-ad-0", start_ms: 600_000, ad_id: "srv-v52-0", creative: b.clone() }]); + let v51 = vast(&[VastAd { id: "ad-a", serving: "srv-v51-0", universal: "UID-A", duration_ms: 15_000, cpm: Some("25.00") }]); + let v52 = vast(&[VastAd { id: "ad-b", serving: "srv-v52-0", universal: "UID-B", duration_ms: 15_000, cpm: Some("18.00") }]); + let dr = |id: &str| Marker::DateRange { id: id.as_bytes().to_vec(), start: date(600_000).into_bytes() }; + let rec = |session: [u8; 16], response: &str, ad: PodAd, c: Consent| RecordDraft { + carriage: carriage::HLS_DATERANGE, + session, + avail_id: "break-600", + avail_ms: 15_000, + format: response_format::VAST, + response: sha(&[response.as_bytes()]), + kind: id_kind::SERVING_ID, + id_attr: "X-AD-ID", + currency: *b"USD", + frame: Frame::FULL, + requirement: video(), + ads: vec![ad], + consent: Some(c), + }; + let ad = |m: &str, id: &str, uid: &str, c: &Creative, price: u64| pod_ad(0, &dr(m), id, id, uid, c, price, price_source::PRICING, [0; 32]); + let with = |gpp: &str, sid: &[u16], child: u8| Consent { gpp: gpp.into(), gpp_sid: sid.to_vec(), child_directed: child }; + let r51 = rec(session(51), &v51, ad("v51-ad-0", "srv-v51-0", "UID-A", &a, 2_500_000), with(GPP, &[2], 0)); + let r52 = rec(session(52), &v52, ad("v52-ad-0", "srv-v52-0", "UID-B", &b, 1_800_000), with("", &[], 1)); + // Viewer 53's record, its GPP string's last section changed after the signer signed it. + let mut r53 = rec(session(53), &v51, ad("v53-ad-0", "srv-v51-0", "UID-A", &a, 2_500_000), with(GPP, &[2], 0)).signed(1); + let at = r53.windows(4).position(|w| w == b"1YNN").expect("the string's last section"); + r53[at + 2] = b'Y'; + let shared = rec([0; 16], &v51, ad("v5s-ad-0", "srv-v51-0", "UID-A", &a, 2_500_000), with(GPP, &[2], 0)); + let sections = rec(session(54), &v51, ad("v54-ad-0", "srv-v51-0", "UID-A", &a, 2_500_000), with("", &[2], 0)); + let records = vec![ + (r51.signed(1), "viewer 51's break, a record of version 2: the GPP string viewer 51's player gave, its applicable section 2, and a request not directed to children", ok), + (r52.signed(1), "viewer 52's break, a record of version 2: no GPP string given, and a child-directed request", ok), + (r53, "viewer 53's record with the last section of its GPP string changed after the signer signed it", Err(Reason::BadSignature)), + (shared.signed(1), "a shared record, which every viewer is shown, carrying one viewer's GPP string", Err(Reason::PodInputMalformed)), + (sections.signed(1), "a record stating section 2 as applicable, with no GPP string", Err(Reason::PodInputMalformed)), + ]; + let claims = vec![ + (ClaimDraft::new(&dr("v51-ad-0"), "srv-v51-0", &p51, 51, &a, whole(&a)).bytes(), "viewer 51, ad-a: in view and audible from start to end", ok), + (ClaimDraft::new(&dr("v52-ad-0"), "srv-v52-0", &p52, 52, &b, whole(&b)).bytes(), "viewer 52, ad-b: in view and audible from start to end", ok), + (ClaimDraft::new(&dr("v53-ad-0"), "srv-v51-0", &p51, 53, &a, whole(&a)).bytes(), "viewer 53's claim of the ad of its altered record, which names no decision", Err(Reason::ClaimNoDecision)), + ]; + let mut out = Vector { + name: "consent", + about: "two viewers' personalised HLS playlists, each with one stitched ad, and a pod-input record of version 2 per viewer carrying the consent signal its decision was made under: one a GPP string with its applicable section, one a child-directed request with no string; a record whose GPP string was changed after signing, a shared record carrying a viewer's string, and a record stating a section with no string", + manifests: vec![], + responses: vec![], + receipts: vec![], + bound: vec![], + records, + claims, + }; + out.manifests = vec![(out.file("viewer51.m3u8"), p51), (out.file("viewer52.m3u8"), p52)]; + out.responses = vec![(out.file("viewer51.vast.xml"), v51), (out.file("viewer52.vast.xml"), v52)]; + out +} + fn vectors() -> Vec { - vec![hls_stitched(), hls_interstitial(), squeezeback(), double_box(), form1_prefetch()] + vec![hls_stitched(), hls_interstitial(), squeezeback(), double_box(), form1_prefetch(), consent()] } // ---- running and JSON ---- @@ -793,6 +866,10 @@ pub fn facts_json(v: &Verification) -> Map { m.insert("stated_spend_micros".into(), json!(r.stated_spend_micros)); m.insert("share_bp".into(), json!(r.share_bp)); m.insert("region".into(), r.region.as_ref().map_or(Value::Null, |rs| json!(rs.iter().map(|x| [x.x, x.y, x.w, x.h]).collect::>()))); + // A record of version 2 states its consent signal (§4.7). + if let Some(c) = &r.consent { + m.insert("consent".into(), c.to_json()); + } } Value::Object(m) }) @@ -861,7 +938,7 @@ pub fn generate() -> Vec<(String, String)> { for r in Reason::ALL.iter().filter(|r| !matches!(r, Reason::BadKey)) { assert!(seen.contains(r.code()), "no record, claim or ad gives {}", r.code()); } - let players: Map = [1u8, 2, 3, 11, 12, 21, 22, 23, 24, 25, 26, 31, 32, 41].iter().map(|&i| (i.to_string(), json!(hex(&player_pk(i))))).collect(); + let players: Map = [1u8, 2, 3, 11, 12, 21, 22, 23, 24, 25, 26, 31, 32, 41, 51, 52, 53, 54].iter().map(|&i| (i.to_string(), json!(hex(&player_pk(i))))).collect(); let doc = json!({ "about": "extensions/stitched-presentation.md §10: streams with ads another party stitched or interstitials it served, the manifests the players read, the ad servers' responses, each break's signed pod-input record and the claims the players signed; each record's and claim's verdict, each ad's presentations and quartiles, and the spend that counts by currency", "creatives": creatives().iter().map(creative_json).collect::>(), diff --git a/wai/wai-rs/src/wai_video_ssai.rs b/wai/wai-rs/src/wai_video_ssai.rs index b5aa3a7a4..6907b9ed0 100644 --- a/wai/wai-rs/src/wai_video_ssai.rs +++ b/wai/wai-rs/src/wai_video_ssai.rs @@ -34,6 +34,12 @@ //! decision, the impression, the pod hash, the Merkle root, the billable spend //! and the identity domain, and sign under different domains. A stitched //! receipt's bytes are unchanged. +//! +//! A form-2 receipt of version 2 also binds its request terms ([`terms`], +//! extensions/live-ads.md §8.6, §8.7): the consent signal the decision was made under and +//! the supply chain each of its impressions was sold through. It signs under the form's domain of +//! version 2, so no receipt of version 1 is one of version 2, and every receipt of version 1 +//! keeps its bytes. use ed25519_dalek::{Signer, SigningKey}; @@ -52,6 +58,12 @@ const DOMAIN_RECEIPT_ID: &[u8] = b"wai:video-ssai-receipt-id\x01"; const DOMAIN_POD: &[u8] = b"wai:video-ssai-pod\x01"; const DOMAIN_IMP: &[u8] = b"wai:video-ssai-imp\x01"; +/// The request terms of a form-2 receipt of version 2 (extensions/live-ads.md §8.6, §8.7). +/// Rust-only: its constants must not become macros in include/wai.h. +/// cbindgen:ignore +pub mod terms; +use terms::RequestTerms; + /// Hash of the ordered candidate pod — the decision input, bound so the /// deterministic decision is auditable against fixed candidates. @@ -442,6 +454,9 @@ impl SsaiReceipt { /// The signing domain of a form-2 receipt (live-ads §8.4). Its first bytes are no /// stitched payload's, so no signature of one form is a signature of the other. const DOMAIN_BREAK_RECEIPT: &[u8] = b"wai:video-ssai-break-receipt\x01"; +/// The signing domain of a form-2 receipt of version 2 (live-ads §8.4), which binds its +/// request terms. +const DOMAIN_BREAK_RECEIPT_TERMS: &[u8] = b"wai:video-ssai-break-receipt\x02"; /// The asset list a form-2 receipt binds (live-ads §7.2, §7.3). Rust-only: its constants /// must not become macros in include/wai.h. @@ -484,6 +499,10 @@ pub enum BreakReceiptFault { BadSignature, /// `receipt_invalid`: a rule of §8.5 other than the signature. Invalid, + /// `consent_invalid`: §8.5 rule 6, the consent signal of a receipt of version 2 (§8.6). + ConsentInvalid, + /// `supply_chain_invalid`: §8.5 rule 6, the supply chain of a receipt of version 2 (§8.7). + SupplyChainInvalid, } /// A `wai.video.ssai` receipt of form 2: one viewer's impressions in one break of a @@ -504,6 +523,9 @@ pub struct SsaiBreakReceipt { pub signer_pubkey: [u8; 32], pub signer_id: String, pub sig: [u8; 64], + /// A receipt of version 2's request terms (§8.6, §8.7); `None` for a receipt of + /// version 1. + pub terms: Option, } /// Σ price over billable impressions, or `None` past `u64`. @@ -520,7 +542,7 @@ impl SsaiBreakReceipt { /// The signing payload (live-ads §8.4). pub fn signing_payload(&self) -> Vec { let b = &self.binding; - let mut o = DOMAIN_BREAK_RECEIPT.to_vec(); + let mut o = if self.terms.is_some() { DOMAIN_BREAK_RECEIPT_TERMS } else { DOMAIN_BREAK_RECEIPT }.to_vec(); o.extend_from_slice(&b.room); o.extend_from_slice(&b.cue); o.extend_from_slice(&b.session); @@ -551,6 +573,9 @@ impl SsaiBreakReceipt { for i in &self.impressions { o.extend_from_slice(&i.leaf()); } + if let Some(t) = &self.terms { + t.write(&mut o); + } o.extend_from_slice(&self.signer_pubkey); let id = self.signer_id.as_bytes(); o.extend_from_slice(&(id.len() as u32).to_be_bytes()); @@ -558,8 +583,8 @@ impl SsaiBreakReceipt { o } - /// Seal one viewer's impressions in one break. The Merkle root and the billable - /// spend are computed here from the impressions. + /// Seal one viewer's impressions in one break, a receipt of version 1. The Merkle root + /// and the billable spend are computed here from the impressions. #[allow(clippy::too_many_arguments)] pub fn seal( signer: &SigningKey, @@ -571,6 +596,25 @@ impl SsaiBreakReceipt { joules_micro: u64, energy_provenance: Option, grant: GrantRef, + ) -> SsaiBreakReceipt { + Self::seal_with_terms(signer, signer_id, binding, pod_hash, impressions, work_decide_ops, joules_micro, energy_provenance, grant, None) + } + + /// Seal as [`SsaiBreakReceipt::seal`] does, a receipt of version 2 when `terms` is given + /// (§8.6, §8.7): it binds the consent signal and the supply chain. Nothing is checked + /// here; [`SsaiBreakReceipt::check`] holds a receipt to its rules. + #[allow(clippy::too_many_arguments)] + pub fn seal_with_terms( + signer: &SigningKey, + signer_id: impl Into, + binding: BreakBinding, + pod_hash: [u8; 32], + impressions: Vec, + work_decide_ops: u64, + joules_micro: u64, + energy_provenance: Option, + grant: GrantRef, + terms: Option, ) -> SsaiBreakReceipt { let leaves: Vec<[u8; 32]> = impressions.iter().map(Impression::leaf).collect(); let mut r = SsaiBreakReceipt { @@ -586,6 +630,7 @@ impl SsaiBreakReceipt { signer_pubkey: signer.verifying_key().to_bytes(), signer_id: signer_id.into(), sig: [0u8; 64], + terms, }; r.sig = signer.sign(&r.signing_payload()).to_bytes(); r @@ -618,6 +663,15 @@ impl SsaiBreakReceipt { if legacy || !figure_and_class_cohere(self.joules_micro, self.energy_provenance.as_ref()) { return Err(BreakReceiptFault::Invalid); } + if let Some(t) = &self.terms { + if !t.consent.well_formed() { + return Err(BreakReceiptFault::ConsentInvalid); + } + // §8.7 rule 4: one entry for each impression; rules 1 to 3: each chain stated. + if t.supply_chains.len() != self.impressions.len() || t.supply_chains.iter().flatten().any(|c| !c.well_formed()) { + return Err(BreakReceiptFault::SupplyChainInvalid); + } + } Ok(()) } @@ -692,6 +746,11 @@ impl SsaiBreakReceipt { "funds_ceiling": self.grant.funds_ceiling, }), ); + if let Some(t) = &self.terms { + m.insert("version".into(), json!(2)); + m.insert("consent".into(), t.consent.to_json()); + m.insert("supply_chains".into(), t.chains_to_json()); + } m.insert("signer_pubkey".into(), json!(hex(&self.signer_pubkey))); m.insert("signer_id".into(), json!(self.signer_id)); m.insert("sig".into(), json!(hex(&self.sig))); @@ -709,6 +768,12 @@ impl SsaiBreakReceipt { _ => return None, }; let mut want: Vec<&str> = BREAK_RECEIPT_MEMBERS.to_vec(); + // A receipt of version 2 has its `version`, 2, and its request terms; one of version 1 + // has none of them. + let v2 = o.contains_key("version"); + if v2 { + want.extend(["version", "consent", "supply_chains"]); + } match label { Some("OnChipCounter") => want.extend(["energy_uncertainty", "energy_counter_filtering"]), Some("CalibratedInstrument") => want.extend(["energy_uncertainty", "energy_calibration_ref"]), @@ -717,9 +782,15 @@ impl SsaiBreakReceipt { if o.len() != want.len() || !want.iter().all(|k| o.contains_key(*k)) { return None; } - if o.get("form")?.as_u64()? != 2 { + if o.get("form")?.as_u64()? != 2 || (v2 && o.get("version")?.as_u64()? != 2) { return None; } + let terms = if v2 { + let supply_chains = RequestTerms::chains_from_json(o.get("supply_chains")?)?; + Some(RequestTerms { consent: terms::Consent::from_json(o.get("consent")?)?, supply_chains }) + } else { + None + }; let uncertainty = || -> Option { let u = o.get("energy_uncertainty")?.as_object()?; if u.len() != 2 { @@ -794,6 +865,7 @@ impl SsaiBreakReceipt { signer_pubkey: unhex(o.get("signer_pubkey")?)?, signer_id: o.get("signer_id")?.as_str()?.to_string(), sig: unhex(o.get("sig")?)?, + terms, }) } } @@ -1086,4 +1158,99 @@ mod tests { let r = break_receipt([5, 0], None, None, 0); crate::sig::assert_refuses_forgeries(&r, |r, k, s| (r.signer_pubkey, r.sig) = (k, s), |r| r.signing_payload(), |r| r.verify()); } + + // ---- form 2, version 2 (live-ads §8.6, §8.7) ---- + + /// The terms of [`terms_receipt`]'s two impressions, whose candidates answered two + /// requests: one sent to an SSP, one to an exchange. + fn request_terms() -> RequestTerms { + use terms::*; + let chain = |asi: &str, sid: &str| SupplyChain { + ver: "1.0".into(), + complete: 1, + nodes: vec![SupplyNode { asi: asi.into(), sid: sid.into(), hp: 1, rid: None, name: None, domain: None, sellers: Some([4; 32]) }], + authorised: Some(Authorised { file: APP_ADS_TXT.into(), domain: "channel.example".into(), sha256: [5; 32] }), + }; + RequestTerms { + consent: Consent { gpp: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN".into(), gpp_sid: vec![2], child_directed: 0 }, + supply_chains: vec![Some(chain("ssp.example", "pub-1")), Some(chain("exchange.example", "pub-2"))], + } + } + + fn terms_receipt(t: RequestTerms) -> SsaiBreakReceipt { + let mut imps = decide(&pod(), 2, 30_000, 80_000); + imps[0].quartiles_reached = 5; + SsaiBreakReceipt::seal_with_terms(&key(9), "did:key:decider", binding(80_000), pod_hash(&pod()), imps, 3, 0, None, room_grant(None), Some(t)) + } + + #[test] + fn a_receipt_of_version_two_binds_its_terms_and_reads_back() { + let r = terms_receipt(request_terms()); + assert_eq!(r.check(), Ok(())); + let j = r.to_json(); + assert_eq!(j["version"], serde_json::json!(2)); + assert_eq!(SsaiBreakReceipt::from_json(&j).as_ref(), Some(&r)); + let v1 = SsaiBreakReceipt { terms: None, ..r.clone() }; + assert!(r.signing_payload().starts_with(b"wai:video-ssai-break-receipt\x02"), "version 2 signs under its own domain"); + assert!(v1.signing_payload().starts_with(b"wai:video-ssai-break-receipt\x01")); + assert_eq!(v1.check(), Err(BreakReceiptFault::BadSignature), "a receipt of version 2 stripped of its terms is not a receipt of version 1"); + let mut no_chain = request_terms(); + no_chain.supply_chains = vec![None, None]; + let r2 = terms_receipt(no_chain); + assert_eq!(r2.check(), Ok(())); + assert_eq!(SsaiBreakReceipt::from_json(&r2.to_json()).as_ref(), Some(&r2)); + assert_eq!(r2.to_json()["supply_chains"], serde_json::json!([null, null])); + } + + #[test] + fn a_consent_signal_altered_after_signing_breaks_the_receipt() { + let r = terms_receipt(request_terms()); + let mut j = r.to_json(); + j["consent"]["gpp"] = serde_json::json!("DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YYN"); + assert_eq!(SsaiBreakReceipt::from_json(&j).expect("it reads").check(), Err(BreakReceiptFault::BadSignature)); + let mut j = r.to_json(); + j["consent"]["child_directed"] = serde_json::json!(1); + assert_eq!(SsaiBreakReceipt::from_json(&j).expect("it reads").check(), Err(BreakReceiptFault::BadSignature)); + let mut j = r.to_json(); + j["supply_chains"][0]["nodes"][0]["sid"] = serde_json::json!("pub-3"); + assert_eq!(SsaiBreakReceipt::from_json(&j).expect("it reads").check(), Err(BreakReceiptFault::BadSignature)); + // Each chain is bound to its impression: the two swapped is another receipt. + let mut j = r.to_json(); + let (a, b) = (j["supply_chains"][0].clone(), j["supply_chains"][1].clone()); + j["supply_chains"][0] = b; + j["supply_chains"][1] = a; + assert_eq!(SsaiBreakReceipt::from_json(&j).expect("it reads").check(), Err(BreakReceiptFault::BadSignature)); + } + + #[test] + fn the_terms_rules_refuse_and_the_carriage_is_exact() { + let mut t = request_terms(); + t.consent.gpp_sid = vec![2, 2]; + assert_eq!(terms_receipt(t).check(), Err(BreakReceiptFault::ConsentInvalid)); + let mut t = request_terms(); + t.supply_chains[1].as_mut().expect("a chain").nodes[0].asi = "https://exchange.example".into(); + assert_eq!(terms_receipt(t).check(), Err(BreakReceiptFault::SupplyChainInvalid), "each chain holds §8.7's rules"); + for n in [0, 1, 3] { + let mut t = request_terms(); + t.supply_chains.resize(n, None); + assert_eq!(terms_receipt(t).check(), Err(BreakReceiptFault::SupplyChainInvalid), "{n} chains for two impressions"); + } + let good = terms_receipt(request_terms()).to_json(); + for drop in ["consent", "supply_chains"] { + let mut j = good.clone(); + j.as_object_mut().unwrap().remove(drop); + assert!(SsaiBreakReceipt::from_json(&j).is_none(), "a receipt of version 2 without {drop} does not read"); + } + let mut j = good.clone(); + j["version"] = serde_json::json!(3); + assert!(SsaiBreakReceipt::from_json(&j).is_none()); + for not_chains in [serde_json::json!(null), serde_json::json!({}), serde_json::json!([1])] { + let mut j = good.clone(); + j["supply_chains"] = not_chains; + assert!(SsaiBreakReceipt::from_json(&j).is_none(), "supply_chains is an array of chains and nulls"); + } + let mut v1 = break_receipt([5, 0], None, None, 0).to_json(); + v1.as_object_mut().unwrap().insert("consent".into(), good["consent"].clone()); + assert!(SsaiBreakReceipt::from_json(&v1).is_none(), "a receipt of version 1 carries no consent signal"); + } } diff --git a/wai/wai-rs/src/wai_video_ssai/terms.rs b/wai/wai-rs/src/wai_video_ssai/terms.rs new file mode 100644 index 000000000..9669a9478 --- /dev/null +++ b/wai/wai-rs/src/wai_video_ssai/terms.rs @@ -0,0 +1,744 @@ +//! The request terms a form-2 receipt of version 2 binds (extensions/live-ads.md §8.6, +//! §8.7): the consent signal its decision was made under, a Global Privacy Platform string +//! with the sections that apply and a child-directed flag, and the supply chain each of its +//! impressions was sold through, each seller named by its advertising system's domain and +//! its seller id, with references to the sellers files and to the authorised-sellers file +//! the chain is checked against. +//! +//! The signal and the chain are the ones the published texts define: the GPP string and its +//! section ids (OpenRTB 2.6 `Regs.gpp`, `Regs.gpp_sid`), the COPPA flag (`Regs.coppa`), the +//! `SupplyChain` object and its nodes (§3.2.25, §3.2.26), a sellers file (*Sellers.json* 1.0) +//! and an authorised-sellers file (*ads.txt* 1.1, *app-ads.txt* 1.0). WAI carries them and +//! binds them into what the decider signs; it defines no scheme of its own beside them, and +//! it does not decode the GPP string's sections. +//! +//! The stitched pod-input record (extensions/stitched-presentation.md §4.7) carries the same +//! consent signal in its own layout, held to the same rules ([`Consent::well_formed`]). + +use serde::de::{self, Deserializer, IgnoredAny, MapAccess, Visitor}; +use serde_json::{json, Map, Value}; +use sha2::{Digest, Sha256}; + +/// The most bytes of a GPP string a consent signal carries (§8.6). +pub const MAX_GPP_BYTES: usize = 65_535; +/// The most section ids a consent signal carries (§8.6). +pub const MAX_SECTIONS: usize = 16; +/// The most nodes a supply chain carries (§8.7). +pub const MAX_NODES: usize = 32; +/// The deepest a sellers file's arrays and objects nest, the file's own object counted as the +/// first (§8.7). +pub const MAX_SELLERS_DEPTH: usize = 64; +/// An authorised-sellers file of a site (*ads.txt* 1.1 §3.1). +pub const ADS_TXT: &str = "ads.txt"; +/// An authorised-sellers file of an app, at its developer's domain (*app-ads.txt* 1.0). +pub const APP_ADS_TXT: &str = "app-ads.txt"; + +/// The consent signal a decision was made under (§8.6). +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Consent { + /// The GPP string the player gave, as it gave it; empty when it gave none. + pub gpp: String, + /// The string's sections the player gave as applicable, in the order given. + pub gpp_sid: Vec, + /// 1 when the request is directed to children, 0 otherwise. Held as read, so that a + /// value other than 0 or 1 is refused by [`Consent::well_formed`]. + pub child_directed: u8, +} + +/// Is `b` a GPP string by §8.6 rule 1: at most [`MAX_GPP_BYTES`] bytes of the URL-safe +/// characters the GPP string's text lists, and, when not empty, every part between its `~` +/// delimiters present? +pub fn gpp_ok(b: &[u8]) -> bool { + b.len() <= MAX_GPP_BYTES + && b.iter().all(|&c| c.is_ascii_alphanumeric() || matches!(c, b'-' | b'_' | b'.' | b'~')) + && (b.is_empty() || b.split(|&c| c == b'~').all(|p| !p.is_empty())) +} + +impl Consent { + /// §8.6's rules. + pub fn well_formed(&self) -> bool { + let distinct = self.gpp_sid.iter().enumerate().all(|(k, s)| !self.gpp_sid[..k].contains(s)); + gpp_ok(self.gpp.as_bytes()) + && self.gpp_sid.len() <= MAX_SECTIONS + && self.gpp_sid.iter().all(|&s| s >= 1) + && distinct + && (!self.gpp.is_empty() || self.gpp_sid.is_empty()) + && self.child_directed <= 1 + } + + /// The signed encoding (§8.6), big-endian as the receipt's other fields. + pub fn write(&self, o: &mut Vec) { + put_text(o, &self.gpp); + o.extend_from_slice(&(self.gpp_sid.len() as u64).to_be_bytes()); + for s in &self.gpp_sid { + o.extend_from_slice(&s.to_be_bytes()); + } + o.push(self.child_directed); + } + + pub fn to_json(&self) -> Value { + json!({"gpp": self.gpp, "gpp_sid": self.gpp_sid, "child_directed": self.child_directed}) + } + + /// Read the JSON carriage: exactly its members, each of its type. `None` is + /// `receipt_malformed`; the rules are [`Consent::well_formed`]'s. + pub fn from_json(v: &Value) -> Option { + let o = exactly(v, &["gpp", "gpp_sid", "child_directed"])?; + Some(Consent { + gpp: o.get("gpp")?.as_str()?.to_string(), + gpp_sid: o.get("gpp_sid")?.as_array()?.iter().map(|s| u16::try_from(s.as_u64()?).ok()).collect::>()?, + child_directed: u8::try_from(o.get("child_directed")?.as_u64()?).ok()?, + }) + } +} + +/// One node of a supply chain (§8.7): OpenRTB 2.6 §3.2.26's members but `ext`, and the +/// reference to its advertising system's sellers file. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SupplyNode { + /// The advertising system's canonical domain. + pub asi: String, + /// The seller's or reseller's account id in that system. + pub sid: String, + /// Whether the node is in the flow of payment: 1 or 0. + pub hp: u8, + pub rid: Option, + pub name: Option, + pub domain: Option, + /// SHA-256 of the sellers file at `https:///sellers.json`, as the decider held it. + pub sellers: Option<[u8; 32]>, +} + +/// The authorised-sellers file a chain is checked against (§8.7). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Authorised { + /// [`ADS_TXT`] or [`APP_ADS_TXT`]. + pub file: String, + /// The domain whose root holds the file. + pub domain: String, + /// SHA-256 of the file, as the decider held it. + pub sha256: [u8; 32], +} + +/// The supply chain of a request (§8.7): OpenRTB 2.6 §3.2.25's `SupplyChain` but `ext`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SupplyChain { + pub ver: String, + /// 1 when the chain reaches back to the owner of the inventory, 0 otherwise. + pub complete: u8, + /// From the first seller to the one that sent the request. + pub nodes: Vec, + pub authorised: Option, +} + +/// Is `s` a domain name by §8.7: 1 to 253 bytes, two labels or more separated by `.`, each +/// 1 to 63 ASCII letters, digits and `-`, not beginning or ending with `-`? +pub fn domain_ok(s: &str) -> bool { + let b = s.as_bytes(); + let mut labels = 0; + let all = b.split(|&c| c == b'.').all(|l| { + labels += 1; + !l.is_empty() && l.len() <= 63 && l.iter().all(|&c| c.is_ascii_alphanumeric() || c == b'-') && l[0] != b'-' && l[l.len() - 1] != b'-' + }); + !b.is_empty() && b.len() <= 253 && all && labels >= 2 +} + +/// A text of §8.7: 1 to 255 bytes. +fn text_ok(s: &str) -> bool { + !s.is_empty() && s.len() <= 255 +} + +/// A version of the object, `major.minor`: digits, `.`, digits, at most 15 bytes. +fn ver_ok(s: &str) -> bool { + let digits = |p: &str| !p.is_empty() && p.bytes().all(|c| c.is_ascii_digit()); + s.len() <= 15 && s.split_once('.').is_some_and(|(a, b)| digits(a) && digits(b)) +} + +impl SupplyChain { + /// §8.7's rules. + pub fn well_formed(&self) -> bool { + let nodes_ok = self.nodes.iter().all(|n| { + domain_ok(&n.asi) + && text_ok(&n.sid) + && n.hp <= 1 + && n.rid.as_deref().is_none_or(text_ok) + && n.name.as_deref().is_none_or(text_ok) + && n.domain.as_deref().is_none_or(domain_ok) + }); + let authorised_ok = self.authorised.as_ref().is_none_or(|a| (a.file == ADS_TXT || a.file == APP_ADS_TXT) && domain_ok(&a.domain)); + ver_ok(&self.ver) && self.complete <= 1 && !self.nodes.is_empty() && self.nodes.len() <= MAX_NODES && nodes_ok && authorised_ok + } + + /// The signed encoding (§8.7) of a chain, after its leading 1. + fn write(&self, o: &mut Vec) { + put_text(o, &self.ver); + o.push(self.complete); + o.extend_from_slice(&(self.nodes.len() as u64).to_be_bytes()); + for n in &self.nodes { + put_text(o, &n.asi); + put_text(o, &n.sid); + o.push(n.hp); + for f in [&n.rid, &n.name, &n.domain] { + match f { + Some(s) => { + o.push(1); + put_text(o, s); + } + None => o.push(0), + } + } + match &n.sellers { + Some(d) => { + o.push(1); + o.extend_from_slice(d); + } + None => o.push(0), + } + } + match &self.authorised { + Some(a) => { + o.push(1); + put_text(o, &a.file); + put_text(o, &a.domain); + o.extend_from_slice(&a.sha256); + } + None => o.push(0), + } + } + + pub fn to_json(&self) -> Value { + let nodes: Vec = self + .nodes + .iter() + .map(|n| { + json!({ + "asi": n.asi, + "sid": n.sid, + "hp": n.hp, + "rid": n.rid, + "name": n.name, + "domain": n.domain, + "sellers": n.sellers.map(|d| hex(&d)), + }) + }) + .collect(); + json!({ + "ver": self.ver, + "complete": self.complete, + "nodes": nodes, + "authorised": self.authorised.as_ref().map(|a| json!({"file": a.file, "domain": a.domain, "sha256": hex(&a.sha256)})), + }) + } + + /// Read the JSON carriage: exactly its members, each of its type. `None` is + /// `receipt_malformed`; the rules are [`SupplyChain::well_formed`]'s. + pub fn from_json(v: &Value) -> Option { + let o = exactly(v, &["ver", "complete", "nodes", "authorised"])?; + let opt_text = |n: &Map, k: &str| -> Option> { + match n.get(k)? { + Value::Null => Some(None), + Value::String(s) => Some(Some(s.clone())), + _ => None, + } + }; + let nodes = o + .get("nodes")? + .as_array()? + .iter() + .map(|n| { + let n = exactly(n, &["asi", "sid", "hp", "rid", "name", "domain", "sellers"])?; + Some(SupplyNode { + asi: n.get("asi")?.as_str()?.to_string(), + sid: n.get("sid")?.as_str()?.to_string(), + hp: u8::try_from(n.get("hp")?.as_u64()?).ok()?, + rid: opt_text(n, "rid")?, + name: opt_text(n, "name")?, + domain: opt_text(n, "domain")?, + sellers: match n.get("sellers")? { + Value::Null => None, + d => Some(unhex32(d)?), + }, + }) + }) + .collect::>>()?; + let authorised = match o.get("authorised")? { + Value::Null => None, + a => { + let a = exactly(a, &["file", "domain", "sha256"])?; + Some(Authorised { file: a.get("file")?.as_str()?.to_string(), domain: a.get("domain")?.as_str()?.to_string(), sha256: unhex32(a.get("sha256")?)? }) + } + }; + Some(SupplyChain { ver: o.get("ver")?.as_str()?.to_string(), complete: u8::try_from(o.get("complete")?.as_u64()?).ok()?, nodes, authorised }) + } +} + +/// What a receipt of version 2 binds beside the decision (§8.6, §8.7). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RequestTerms { + pub consent: Consent, + /// One entry for each impression, in the impressions' order: the chain its ad was sold + /// through, `None` when none is stated for it. A pod's candidates can answer several + /// requests, each with its own chain. + pub supply_chains: Vec>, +} + +impl RequestTerms { + /// The signed encoding (§8.4): the consent signal, then the chains' count and each chain, + /// 0 for none or 1 and the chain. + pub fn write(&self, o: &mut Vec) { + self.consent.write(o); + o.extend_from_slice(&(self.supply_chains.len() as u64).to_be_bytes()); + for c in &self.supply_chains { + match c { + Some(c) => { + o.push(1); + c.write(o); + } + None => o.push(0), + } + } + } + + /// Read `supply_chains`' JSON carriage: an array of chains and `null`s. `None` is + /// `receipt_malformed`. + pub fn chains_from_json(v: &Value) -> Option>> { + v.as_array()? + .iter() + .map(|c| match c { + Value::Null => Some(None), + c => SupplyChain::from_json(c).map(Some), + }) + .collect() + } + + /// `supply_chains`' JSON carriage. + pub fn chains_to_json(&self) -> Value { + Value::Array(self.supply_chains.iter().map(|c| c.as_ref().map_or(Value::Null, SupplyChain::to_json)).collect()) + } +} + +/// Why a receipt's supply chain fails against the files a verifier holds (§9.3 step 14). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SupplyFault { + /// `supply_file_malformed`: a file the chain names does not read. + FileMalformed, + /// `supply_seller_unlisted`: a node's sellers file lists no seller of its `sid`. + Unlisted, + /// `supply_seller_unauthorised`: the authorised-sellers file does not authorise a node. + Unauthorised, +} + +/// What a verification states of one node (§9.4). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NodeFacts { + /// The verifier held the node's sellers file. + pub sellers_checked: bool, + /// The `seller_type` of its seller in that file, as written, when it has one. + pub seller_type: Option, + /// The relationship of the record that authorises it, in capitals, when the + /// authorised-sellers file was held. + pub relationship: Option, +} + +/// What a verification states of a chain (§9.4). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ChainFacts { + pub nodes: Vec, + /// The verifier held the authorised-sellers file the chain names. + pub authorised_checked: bool, +} + +/// A seller of a sellers file: its `seller_id` and, when it has one, its `seller_type`. +pub type Seller = (String, Option); + +/// Read a sellers file (§8.7). It reads when it is UTF-8 with no byte order mark, and a JSON +/// text (RFC 8259) whose arrays and objects nest at most [`MAX_SELLERS_DEPTH`] deep, whose +/// value is an object with a `sellers` array of objects, each with a string `seller_id` and, +/// when it has one, a string `seller_type`, neither object holding one of those names twice; +/// the member names of those objects and those strings hold no escape of a lone surrogate. +/// Everything else is read by the grammar alone: its numbers are never converted and its +/// strings never decoded, so a number of any size, or an escape of a lone surrogate there, +/// reads. `None` when it does not read so. +pub fn read_sellers(file: &[u8]) -> Option> { + // The grammar is checked over the whole text, so the text is UTF-8 first: a string the + // reader skips is not decoded. + let text = std::str::from_utf8(file).ok()?; + if !nests_within(file, MAX_SELLERS_DEPTH) { + return None; + } + // A byte order mark is not JSON whitespace, so a file that begins with one does not parse. + serde_json::from_str::(text).ok().map(|f| f.0) +} + +/// Do the arrays and objects of the text `b` nest at most `limit` deep, the outermost counted +/// as the first? A bracket inside a string is not counted. On a text that is not JSON the +/// answer does not matter: such a text does not read either way. +fn nests_within(b: &[u8], limit: usize) -> bool { + let (mut depth, mut in_string, mut escaped) = (0usize, false, false); + for &c in b { + if in_string { + if escaped { + escaped = false; + } else if c == b'\\' { + escaped = true; + } else if c == b'"' { + in_string = false; + } + continue; + } + match c { + b'"' => in_string = true, + b'[' | b'{' => { + depth += 1; + if depth > limit { + return false; + } + } + b']' | b'}' => depth = depth.saturating_sub(1), + _ => {} + } + } + true +} + +/// A sellers file's object, read by [`read_sellers`]'s rules: its `sellers`, and every other +/// member skipped by the grammar alone. +struct SellersFile(Vec); + +/// One seller of a sellers file: its `seller_id`, its `seller_type` when it has one, and every +/// other member skipped by the grammar alone. +struct SellerObject(Seller); + +impl<'de> serde::Deserialize<'de> for SellersFile { + fn deserialize>(d: D) -> Result { + struct File; + impl<'de> Visitor<'de> for File { + type Value = SellersFile; + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str("a sellers file's object") + } + fn visit_map>(self, mut m: A) -> Result { + let mut sellers: Option> = None; + // A member name is read as a string, so one holding an escape of a lone + // surrogate does not read. + while let Some(name) = m.next_key::()? { + if name == "sellers" { + if sellers.is_some() { + return Err(de::Error::duplicate_field("sellers")); + } + sellers = Some(m.next_value()?); + } else { + m.next_value::()?; + } + } + let sellers = sellers.ok_or_else(|| de::Error::missing_field("sellers"))?; + Ok(SellersFile(sellers.into_iter().map(|s| s.0).collect())) + } + } + d.deserialize_map(File) + } +} + +impl<'de> serde::Deserialize<'de> for SellerObject { + fn deserialize>(d: D) -> Result { + struct One; + impl<'de> Visitor<'de> for One { + type Value = SellerObject; + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str("a seller") + } + fn visit_map>(self, mut m: A) -> Result { + let (mut id, mut kind): (Option, Option) = (None, None); + while let Some(name) = m.next_key::()? { + // Each is read as a string: another type, or an escape of a lone + // surrogate, does not read. + let slot = match name.as_str() { + "seller_id" => &mut id, + "seller_type" => &mut kind, + _ => { + m.next_value::()?; + continue; + } + }; + if slot.is_some() { + return Err(de::Error::custom(format!("a seller has two members {name}"))); + } + *slot = Some(m.next_value::()?); + } + Ok(SellerObject((id.ok_or_else(|| de::Error::missing_field("seller_id"))?, kind))) + } + } + d.deserialize_map(One) + } +} + +/// A data record of an authorised-sellers file: the advertising system's domain, the +/// account id, and the relationship, as written. +pub type Record = (String, String, String); + +/// Read an authorised-sellers file (§8.7): its data records, in order. `None` when it is not +/// UTF-8. A line is ended by LF, CR LF or CR; a `#` and the rest of its line are a comment; a +/// line is trimmed of spaces and tabs; a line whose part before its first `=` is not empty +/// and holds no `,`, space or tab is a variable record; any other line, the part from its +/// first `;` dropped, is a data record when it is three or four fields separated by `,`, each +/// not empty once trimmed of spaces and tabs. Every other line is passed over. +pub fn read_authorised(file: &[u8]) -> Option> { + let text = std::str::from_utf8(file).ok()?; + let text = text.replace("\r\n", "\n").replace('\r', "\n"); + let trim = |s: &str| s.trim_matches(|c| c == ' ' || c == '\t').to_string(); + let mut out = Vec::new(); + for line in text.split('\n') { + let line = trim(line.split('#').next().unwrap_or("")); + if line.is_empty() { + continue; + } + if let Some((name, _)) = line.split_once('=') + && !name.is_empty() + && !name.contains([',', ' ', '\t']) + { + continue; + } + let data = line.split(';').next().unwrap_or(""); + let fields: Vec = data.split(',').map(trim).collect(); + if (3..=4).contains(&fields.len()) && fields.iter().all(|f| !f.is_empty()) { + out.push((fields[0].clone(), fields[1].clone(), fields[2].clone())); + } + } + Some(out) +} + +fn sha256(b: &[u8]) -> [u8; 32] { + Sha256::digest(b).into() +} + +/// Check a chain against the files a verifier holds (§9.3 step 14): each node whose sellers +/// file is held, in order, then the authorised-sellers file when it is held. +pub fn check_chain(chain: &SupplyChain, files: &[Vec]) -> Result { + let held = |d: &[u8; 32]| files.iter().find(|f| sha256(f) == *d); + let mut nodes = Vec::with_capacity(chain.nodes.len()); + for n in &chain.nodes { + let mut f = NodeFacts { sellers_checked: false, seller_type: None, relationship: None }; + if let Some(file) = n.sellers.as_ref().and_then(held) { + let sellers = read_sellers(file).ok_or(SupplyFault::FileMalformed)?; + let (_, kind) = sellers.iter().find(|(id, _)| *id == n.sid).ok_or(SupplyFault::Unlisted)?; + f.sellers_checked = true; + f.seller_type = kind.clone(); + } + nodes.push(f); + } + let mut authorised_checked = false; + if let Some(file) = chain.authorised.as_ref().and_then(|a| held(&a.sha256)) { + let records = read_authorised(file).ok_or(SupplyFault::FileMalformed)?; + for (k, (n, f)) in chain.nodes.iter().zip(nodes.iter_mut()).enumerate() { + let direct_only = k == 0 && chain.complete == 1; + let rel = records + .iter() + .filter(|(system, account, _)| system.eq_ignore_ascii_case(&n.asi) && *account == n.sid) + .map(|(_, _, r)| r.to_ascii_uppercase()) + .find(|r| r == "DIRECT" || (!direct_only && r == "RESELLER")) + .ok_or(SupplyFault::Unauthorised)?; + f.relationship = Some(rel); + } + authorised_checked = true; + } + Ok(ChainFacts { nodes, authorised_checked }) +} + +impl ChainFacts { + /// What a verification states of a chain (§9.4), as a corpus writes it. + pub fn to_json(&self, chain: &SupplyChain) -> Value { + let nodes: Vec = chain + .nodes + .iter() + .zip(&self.nodes) + .map(|(n, f)| json!({"asi": n.asi, "sid": n.sid, "sellers_checked": f.sellers_checked, "seller_type": f.seller_type, "relationship": f.relationship})) + .collect(); + json!({"complete": chain.complete, "nodes": nodes, "authorised_checked": self.authorised_checked}) + } +} + +fn put_text(o: &mut Vec, s: &str) { + o.extend_from_slice(&(s.len() as u32).to_be_bytes()); + o.extend_from_slice(s.as_bytes()); +} + +fn exactly<'a>(v: &'a Value, members: &[&str]) -> Option<&'a Map> { + let o = v.as_object()?; + (o.len() == members.len() && members.iter().all(|k| o.contains_key(*k))).then_some(o) +} + +fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() +} + +fn unhex32(v: &Value) -> Option<[u8; 32]> { + let s = v.as_str()?; + if s.len() != 64 || !s.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) { + return None; + } + let mut out = [0u8; 32]; + for (i, o) in out.iter_mut().enumerate() { + *o = u8::from_str_radix(&s[2 * i..2 * i + 2], 16).ok()?; + } + Some(out) +} + +#[cfg(test)] +mod tests { + use super::*; + + const GPP: &str = "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN"; + + fn consent() -> Consent { + Consent { gpp: GPP.into(), gpp_sid: vec![2], child_directed: 0 } + } + + fn node(asi: &str, sid: &str, sellers: Option<&[u8]>) -> SupplyNode { + SupplyNode { asi: asi.into(), sid: sid.into(), hp: 1, rid: None, name: None, domain: None, sellers: sellers.map(sha256) } + } + + #[test] + fn a_consent_signal_reads_back_and_each_rule_refuses() { + let c = consent(); + assert!(c.well_formed()); + assert_eq!(Consent::from_json(&c.to_json()), Some(c.clone())); + assert!(Consent { gpp: String::new(), gpp_sid: vec![], child_directed: 1 }.well_formed(), "no string given, a child-directed request"); + let broken = [ + Consent { gpp: "DBACNYA~~1YNN".into(), ..consent() }, + Consent { gpp: "DBACNYA~".into(), ..consent() }, + Consent { gpp: "DBAC NYA".into(), ..consent() }, + Consent { gpp: "DBAC+NYA".into(), ..consent() }, + Consent { gpp_sid: vec![0], ..consent() }, + Consent { gpp_sid: vec![2, 2], ..consent() }, + Consent { gpp_sid: (1..=17).collect(), ..consent() }, + Consent { gpp: String::new(), ..consent() }, + Consent { child_directed: 2, ..consent() }, + ]; + for b in broken { + assert!(!b.well_formed(), "{b:?} breaks a rule of §8.6"); + } + assert!(Consent::from_json(&json!({"gpp": GPP, "gpp_sid": [70000], "child_directed": 0})).is_none(), "a section id past u16 does not read"); + assert!(Consent::from_json(&json!({"gpp": GPP, "gpp_sid": [2], "child_directed": 0, "x": 1})).is_none(), "another member does not read"); + } + + #[test] + fn a_chain_reads_back_and_each_rule_refuses() { + let c = SupplyChain { + ver: "1.0".into(), + complete: 1, + nodes: vec![SupplyNode { rid: Some("req-1".into()), name: Some("A Channel".into()), domain: Some("channel.example".into()), ..node("ssp.example", "pub-1", Some(b"x")) }, node("exchange.example", "ssp-7", None)], + authorised: Some(Authorised { file: APP_ADS_TXT.into(), domain: "channel.example".into(), sha256: [3; 32] }), + }; + assert!(c.well_formed()); + assert_eq!(SupplyChain::from_json(&c.to_json()), Some(c.clone())); + let with = |f: &dyn Fn(&mut SupplyChain)| { + let mut x = c.clone(); + f(&mut x); + x.well_formed() + }; + assert!(!with(&|x| x.ver = "1".into())); + assert!(!with(&|x| x.complete = 2)); + assert!(!with(&|x| x.nodes.clear())); + assert!(!with(&|x| x.nodes = vec![node("ssp.example", "s", None); MAX_NODES + 1])); + assert!(!with(&|x| x.nodes[0].asi = "https://ssp.example".into())); + assert!(!with(&|x| x.nodes[0].asi = "localhost".into())); + assert!(!with(&|x| x.nodes[0].asi = "-ssp.example".into())); + assert!(!with(&|x| x.nodes[0].sid = String::new())); + assert!(!with(&|x| x.nodes[0].hp = 2)); + assert!(!with(&|x| x.nodes[0].rid = Some(String::new()))); + assert!(!with(&|x| x.nodes[0].domain = Some("channel".into()))); + assert!(!with(&|x| x.authorised.as_mut().expect("one").file = "sellers.json".into())); + } + + #[test] + fn the_authorised_sellers_reader_follows_the_text() { + let file = b"# a comment\r\nOWNERDOMAIN=channel.example\nssp.example, pub-1, DIRECT, f08c47fec0942fa0 # its id\r\n\texchange.example ,ssp-7,reseller;ext=1\n\nbroken line\nCONTACT=ops@channel.example\nwholesale.example, seat-5, RESELLER\rbridge.example, br-1, DIRECT, f08c47fec0942fa0, extra\n# bridge.example, br-1, DIRECT\nbridge.example, , DIRECT\n"; + let r = read_authorised(file).expect("UTF-8"); + assert_eq!( + r, + vec![ + ("ssp.example".to_string(), "pub-1".to_string(), "DIRECT".to_string()), + ("exchange.example".to_string(), "ssp-7".to_string(), "reseller".to_string()), + ("wholesale.example".to_string(), "seat-5".to_string(), "RESELLER".to_string()), + ] + ); + assert!(read_authorised(&[0xff, 0xfe]).is_none()); + } + + #[test] + fn the_sellers_reader_reads_the_rest_by_the_grammar_alone() { + let one = |s: &str| read_sellers(s.as_bytes()); + let a = Some(vec![("a".to_string(), None)]); + // Read: what a number holds, an escape of a lone surrogate, or a depth past the + // reference parser's own limit, in a member the reader does not read. + assert_eq!(one(r#"{"x":1e400,"sellers":[{"seller_id":"a"}]}"#), a, "a number is not converted"); + let digits = "9".repeat(5_000); + assert_eq!(one(&format!(r#"{{"n":{digits},"sellers":[{{"seller_id":"a","m":-{digits}.5e-99999}}]}}"#)), a); + assert_eq!(one(r#"{"note":"\ud800","sellers":[{"seller_id":"a","ext":{"k":"\udfff\ud800x"}}]}"#), a, "a skipped string is not decoded"); + let deep = |n: usize| format!(r#"{{"ext":{}{},"sellers":[{{"seller_id":"a"}}]}}"#, "[".repeat(n), "]".repeat(n)); + assert_eq!(read_sellers(deep(MAX_SELLERS_DEPTH - 1).as_bytes()), a, "64 deep, the file's object the first"); + assert_eq!(read_sellers(deep(MAX_SELLERS_DEPTH).as_bytes()), None, "65 deep"); + assert_eq!(read_sellers(deep(10_000).as_bytes()), None); + assert_eq!(one(r#"{"s":"[[[[","sellers":[{"seller_id":"]\"{"}]}"#), Some(vec![("]\"{".to_string(), None)]), "brackets in strings do not nest"); + // A string read decodes its escapes, a pair of surrogates among them. + assert_eq!(one(r#"{"sellers":[{"seller_id":"pub-4001","seller_type":"📺"}]}"#), Some(vec![("pub-4001".to_string(), Some("\u{1f4fa}".to_string()))])); + assert_eq!(one(r#"{"x-📺":1,"sellers":[]}"#), Some(vec![])); + // Not read: an escape of a lone surrogate in what is read, a name twice, a constant + // RFC 8259 does not have, a byte order mark, a text that is not UTF-8. + for bad in [ + r#"{"sellers":[{"seller_id":"\ud800"}]}"#, + r#"{"sellers":[{"seller_id":"pub-\ud8005001"}]}"#, + r#"{"sellers":[{"seller_id":"a","seller_type":"\udc00"}]}"#, + r#"{"\ud800":1,"sellers":[{"seller_id":"a"}]}"#, + r#"{"sellers":[{"seller_id":"a","\udfff":1}]}"#, + r#"{"sellers":[{"seller_id":"a"}],"sellers":[{"seller_id":"a"}]}"#, + r#"{"sellers":[{"seller_id":"a","seller_id":"a"}]}"#, + r#"{"sellers":[{"seller_id":"a","seller_type":"PUBLISHER","seller_type":"PUBLISHER"}]}"#, + r#"{"x":NaN,"sellers":[{"seller_id":"a"}]}"#, + r#"{"x":-Infinity,"sellers":[{"seller_id":"a"}]}"#, + r#"{"sellers":[{"seller_id":1}]}"#, + r#"{"sellers":[{"seller_id":"a","seller_type":null}]}"#, + r#"{"sellers":[{"seller_type":"PUBLISHER"}]}"#, + r#"{"sellers":{"seller_id":"a"}}"#, + r#"[{"sellers":[]}]"#, + r#"{"x":01,"sellers":[]}"#, + "{\"x\":\"a\tb\",\"sellers\":[]}", + "\u{feff}{\"sellers\":[]}", + ] { + assert_eq!(one(bad), None, "{bad} does not read"); + } + assert_eq!(read_sellers(b"{\"n\":\"\xe9\",\"sellers\":[]}"), None, "not UTF-8, even in a string the reader skips"); + } + + #[test] + fn a_chain_is_checked_against_the_files_held() { + let ssp = br#"{"version":"1.0","sellers":[{"seller_id":"pub-1","seller_type":"PUBLISHER","name":"A Channel","domain":"channel.example"}]}"#; + let ex = br#"{"version":"1.0","sellers":[{"seller_id":"ssp-7","seller_type":"INTERMEDIARY","name":"An SSP","domain":"ssp.example"}]}"#; + let ads = b"ssp.example, pub-1, DIRECT\nexchange.example, ssp-7, RESELLER\n"; + let chain = SupplyChain { + ver: "1.0".into(), + complete: 1, + nodes: vec![node("ssp.example", "pub-1", Some(ssp)), node("Exchange.Example", "ssp-7", Some(ex))], + authorised: Some(Authorised { file: APP_ADS_TXT.into(), domain: "channel.example".into(), sha256: sha256(ads) }), + }; + let all = vec![ssp.to_vec(), ex.to_vec(), ads.to_vec()]; + let f = check_chain(&chain, &all).expect("every node checks"); + assert!(f.authorised_checked && f.nodes.iter().all(|n| n.sellers_checked)); + assert_eq!(f.nodes[1].relationship.as_deref(), Some("RESELLER")); + assert_eq!(f.nodes[0].seller_type.as_deref(), Some("PUBLISHER")); + let none = check_chain(&chain, &[]).expect("nothing held, nothing checked"); + assert!(!none.authorised_checked && none.nodes.iter().all(|n| !n.sellers_checked && n.relationship.is_none())); + let mut unlisted = chain.clone(); + unlisted.nodes[0].sid = "pub-2".into(); + assert_eq!(check_chain(&unlisted, &all), Err(SupplyFault::Unlisted)); + let reseller_first = b"ssp.example, pub-1, RESELLER\nexchange.example, ssp-7, RESELLER\n"; + let mut c = chain.clone(); + c.authorised.as_mut().expect("one").sha256 = sha256(reseller_first); + assert_eq!(check_chain(&c, &[reseller_first.to_vec()]), Err(SupplyFault::Unauthorised), "the first node of a complete chain is the owner's own account"); + c.complete = 0; + assert!(check_chain(&c, &[reseller_first.to_vec()]).is_ok(), "an incomplete chain's first node may be a reseller's"); + let mut bad = chain.clone(); + bad.nodes[0].sellers = Some(sha256(ads)); + assert_eq!(check_chain(&bad, &all), Err(SupplyFault::FileMalformed), "an authorised-sellers file is not a sellers file"); + } +} diff --git a/wai/wai-rs/tests/live_ads.rs b/wai/wai-rs/tests/live_ads.rs index b63beb219..ca2a4b5c8 100644 --- a/wai/wai-rs/tests/live_ads.rs +++ b/wai/wai-rs/tests/live_ads.rs @@ -12,21 +12,22 @@ fn wai_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") } -fn committed() -> BTreeMap { +/// The committed corpus's files, by name, as bytes: a supply file need not be UTF-8. +fn committed() -> BTreeMap> { let dir = wai_dir().join("live-conformance/ads"); std::fs::read_dir(&dir) .expect("the committed corpus") .flatten() .map(|e| (e.file_name().to_string_lossy().into_owned(), e.path())) .filter(|(n, _)| n != "README.md") - .map(|(n, p)| (n, std::fs::read_to_string(p).expect("a text file"))) + .map(|(n, p)| (n, std::fs::read(p).expect("a committed file"))) .collect() } #[test] fn the_committed_corpus_regenerates_from_its_seeds() { let files = committed(); - let generated: BTreeMap = corpus::generate().into_iter().collect(); + let generated: BTreeMap> = corpus::generate().into_iter().collect(); let extra: Vec<&String> = files.keys().filter(|k| !generated.contains_key(*k)).collect(); let missing: Vec<&String> = generated.keys().filter(|k| !files.contains_key(*k)).collect(); let differ: Vec<&String> = generated.iter().filter(|(k, v)| files.get(*k).is_some_and(|f| f != *v)).map(|(k, _)| k).collect();