はじめに

この記事は CyberAgent Developers Advent Calendar 2024 5日目の記事です。

AI事業本部でソフトウェアエンジニアをしている太田と申します。プライバシーラボというチームでユーザーのプライバシーを守った広告配信の手法の検証、開発をしております。昨年、PrivacySandboxAPIの内の一つであるProtectedAudienceAPI(以下PA API)について記事を掲載いたしました。その後PrivacySandboxAPIの多くはGeneral Available(GA)となり、iOS以外のChromeで利用可能になっています。

PA APIはユーザーのブラウザ上で広告配信のためのオークションを実行する仕組みを提供しているのですが、ユーザーの端末(多くはモバイル端末です)のCPU性能には限度があったり、オークションに必要な情報をアドテクベンダーのサーバーから取得するネットワークレイテンシーが存在することから、広告が表示されるまでの時間が長くなってしまうという課題が存在しました。

この課題を解決するためにBidding Auction Service(B&A Service)が提案されています。これはTrusted Execution Environment(TEE)と呼ばれる環境をクラウドサーバー上に構築し、その上で広告オークションを実行する仕組みです。ブラウザのオンデバイスオークション用に実装したjavascriptのコードがほぼそのまま動くようになっています。また、TEE自体の安全性を守るために以下のような工夫がなされています。

  • TEEが構築される環境はクラウドサーバーの中でも一定の基準を満たした環境であり、クラウドプロバイダーやTEEを利用するアドテクベンダーも実行中のBidding Auction Serviceの内部の状態を見ることができない
  • ローカルデータに保存されたオークションに必要なデータは暗号化されてTEEに送られる
  • TEE自体はオープンソースとなっている

最近このB&A Serviceを利用した入札がChromeの100%の端末で利用できるようになりました。そこで、前置きが長くなりましたがB&A Serviceを使った開発を始めるまでのガイドをご紹介いたします。

前提

  • アドテクやRTB入札に関する基本的な知識はあるものとします
  • PA APIの流れや大まかな概要は改めて説明しません。開発者ガイドや、Explainerを読んで理解しているものとします。

B&A Serviceの全体像

B&A Serviceの全体像はHigh level designに記載されているコンポーネント図を見るのがわかりやすいです。

SellerとBuyerはそれぞれTEE上で動作する以下のサーバーを持ちます

  • Seller Stack
    • SellerFrontEnd Service(SFE)
    • AuctionService
  • Buyer Stack
    • BuyerFrontEndService(BFE)
    • BiddingService

AuctionServiceとBiddingServiceはそれぞれオンデバイスオークションのときにScoreAd関数とgenerateBid関数を実行するサーバーです。

FrontEndサービスは外部環境とのAuctionService/BiddingServiceとの間に立ち、Request/Responseのオーケストレーションをしたり、後述のKey Value Serviceからリアルタイムデータを取得してAuctionService/BiddingServiceへのRPC callの引数に追加したりします。

Key Value Service

PA APIではtrusted bidding signalsという仕組みを使って入札時にリアルタイムデータを取得する仕組みが存在します。InterestGroup(IG)の中にtrusted bidding signals keysというkeyとなる文字列とtrusted bidding singnals urlを設定しておくことで、オークション時にtrusted bidding signals urlに対してtrusted bidding signals keysをクエリパラメーターとしたgetリクエストが送信されます。アドテクベンダーはtrusted bidding signalsサーバーとしてkeyを受け取ったときにjsonを返すkey valueサーバーを実装しておくことで、リアルタイムデータを入札時に使うことができます。

この仕組みはSeller, Buyer共に利用することができ、オンデバイスオークションのときはオプショナルだったので前回の記事のときは言及しておりませんでした。B&A serviceをローカルで起動するときには少なくともリクエストを受け取れるサーバー自体を起動しておく必要があるようです。このサーバーがレスポンスとして要求されるフォーマットは以下を参照してください。

https://github.com/WICG/turtledove/blob/main/FLEDGE.md#31-fetching-real-time-data-from-a-trusted-server

Buyer

早速Buyer Stackを動かして入札ロジックを実行させていきます。

B&A ServiceではRunning servers locallyというドキュメントが公開されており、これに従うことでローカルで動作するB&A Serviceを構築することが可能です。ただし、2024/11/30時点ではx86アーキテクチャのlinux系のシェルスクリプトを前提としているようで、macでは動かないので注意が必要です。筆者はubuntuで環境を作っています(クラウド上にインスタンスを建てるのでも良いと思います)

Build

まずは、Buyer StackをBuildします。Build stackに記載されているコマンドを実行するとDocker上でBuyer Stackの構築が始まります。環境にもよりますが、これには数時間かかります。(筆者の8コア16スレッドのCPUでは3-4時間程度かかったと記憶しています)

その後、Start stackのコマンドからBFEとBiddingServiceを起動します。

BFEを起動するには   ./tools/debug/start_bfeを実行するのですが、Buyer用のKey Value ServiceのURLを設定しておく必要があります。環境変数 BUYER_KV_SERVER_ADDRに設定することで反映されます。

export BUYER_KV_SERVER_ADDR=http://localhost:9000/getvalues # 環境に応じて変更してください ./tools/debug/start_bfe

BiddingServiceを起動するには入札ロジックを実装したjavascriptの保存してあるURLを設定しておく必要があります。環境変数 BIDDING_JS_URLに設定することで反映されます。

# あらたにconsoleを立ち上げる
export BIDDING_JS_URL=http://localhost:9000/bidding-logic.js # 環境に応じて変更してください
./tools/debug/start_bidding

ここで指定する入札ロジックの内容は前回の記事とほぼ同様で問題ないですが、一点だけ修正が必要な箇所があります。前回の記事の実装では以下のようにIGに保存されているrenderUrlを参照している箇所がありましたが、B&A ServiceではrenderUrlが取得できないようになっています。renderUrlは様々なクエリパラメーターが設定されるなど長くなりがちで、ユーザーのデバイスからTEE環境に送信されるときにpayloadのサイズが大きくなる原因になるからです

render: group.ads[0].renderUrl

そこでad_render_ids を代わりにinterest groupからTEEに渡すことができます。buyerはこのidをつかってrenderUrlを生成しても良いですし、Key Value Serviceからad_render_idとrenderUrlのmapを取得してgenerateBid内でrenderUrlを復元しても良いでしょう。

入札ロジックの実行

次に起動したBuyer Stack上で入札ロジックを実行してみます。実はBuyerの入札ロジックを実行するだけなら、オンデバイスオークションより簡単です。Buyer Stackさえ動いていればIGの中身などはBFEへのrequestの引数に設定すれば良いので、ブラウザでIGにjoinする必要などもないからです。テストモードなのでbiddingLogicURLもlocalhostで問題ないです。

入札ロジックを実行するときはSellerに代わってBFEにgrpcのリクエストを送る必要があり、Secure Invoke Toolというtoolが用意されています。このtoolのおかげでBuyerはSellerの変わりにgrpcの引数を保存したinputファイルを用意しておくだけで十分です。このtoolの実行方法はPlaintext requestに記載されており、この例ではinputファイルの形式をplaintext形式で保存しておくことになっています。しかし、protobufからplaintextにシリアライズするには当然protocから生成した何かしらのライブラリが必要です。

Sevcure Invoke Toolを一回でも起動しようとするとb&a service api用のprotoをもとにしたcppファイルがbazel-bin/api/配下に生成されているのですが、シリアライズするためにC++の環境を用意するのは少し面倒です(シリアライズをサポートする方法が用意されているのかもしれませんが少し見た感じでは見つかりませんでした)、かといって手作業でprotobufと互換性のあるplaintextを作るのは少し大変です(https://qiita.com/yugui/items/678a7abd86906270b5c2)

Secure Invoke ToolのREADMEを参照すると、Sending ProtectedAudience GetBidsRawRequest to BFE as JSONにjsonを使う例とjsonにシリアライズされたファイルの例があることがわかります。このjsonファイルの必要な箇所を直せばオークションロジックを実行することができます。特にinterest_groupsの中身はユーザーのデバイスに実際に登録されるIGの替わりになるので、generateBidが値を返せるように設定してください。

オークションロジックの呼び出しに成功すると、入札結果のデータが以下のフォーマットに従って標準出力に表示されます。

https://github.com/privacysandbox/bidding-auction-servers/blob/e9e21191ea175685b0282e7b5b32f7c5e067e892/api/bidding_auction_servers.proto#L1093

Seller

Seller Stackも同様に起動して実行します。

Build

Build stackを実行するとSeller stackのbuildが始まります。Buyer Stackのときと同様、数時間かかります。

その後、Start stackのコマンドからSFEとAuctionServiceを起動します。

SFEを起動する  ./tools/debug/start_sfeの実行前に、環境変数 KEY_VALUE_SIGNALS_ADDRSELLER_ORIGIN_DOMAINを設定する必要があります。KEY_VALUE_SIGNALS_ADDRはSeller用のKey Value Serviceです。レスポンスのフォーマットはこちらを参照してください。

# 環境に応じて変更してください
export KEY_VALUE_SIGNALS_ADDR=http://localhost:9001/getvalues export SELLER_ORIGIN_DOMAIN=http://localhost:9000 ./tools/debug/start_sfe

AuctionServiceを起動するにはSSPのscoreAd関数を実装したjavascriptの保存してあるURLを設定しておく必要があります。環境変数 AUCTION_JS_URLに設定することで反映されます。

# あらたにconsoleを立ち上げる
export AUCTION_JS_URL=http://localhost:9000/score-ad.js # 環境に応じて変更してください
./tools/debug/start_auction

オークションの実行

Buyerのときと同様にsecure_invoke toolを使ってSellerにリクエストを送ることができます。正しく設定できているとSFEがBFEとAuctionServiceにリクエストを送ってくれ、B&A Service全体を通したオークションプロセスが実行されます。Secure Invoker Toolに渡すinputファイルの例はこちらにあり、Sellerがオークションを始めるのに必要なconfigurationとユーザーのデバイスに保存されているIGの情報をマージしたようなフォーマットになっています。これをinputファイルとして使うためにいくつか修正箇所があります。

  • ./tools/debug/start_sfeの中のBUYER_SERVER_ADDRS_JSONに以下のような値が設定されています。この中のhttps://example-buyer-origin.comをinputファイルの"buyer_list"の配列に設定してください。Seller stackは入札リクエストを送るBuyerを起動時に指定します。これはBUYER_SERVER_ADDRS_JSONで設定されます。inputファイルのIGに関する情報にはIG ownerであるBuyerのURLが含まれているのですが、Sellerが指定したBuyerの持つIGだけがB&A Sericeの入札に使われます。

BUYER_SERVER_ADDRS_JSON="${BUYER_SERVER_ADDRS_JSON:-$(cat << EOF
{
  "https://example-buyer-origin.com": {
    "url": "127.0.0.1:${BFE_PORT}",
    "cloudPlatform": "LOCAL"
  }
}
EOF
)}"

  • "per_buyer_config"に設定するkey( Placeholder-Should-Match-With-The-Keys-In-BUYER_SERVER_HOSTS-In-SFE となっています)も、同様にBuyerのURLに変更します。
  • "seller"の値を先ほど設定したSELLER_ORIGIN_DOMAIN に変更します。
  • "raw_protected_audience_input.raw_buyer_input"のkeyの値を上記で設定したBuyerのURLに変更します。
    • ここに設定できる"interest_groups"はユーザーのデバイスに設定されるIGの情報です。Buyer stackを実行するときにもinputファイルに設定していました。Secure Invoke ToolでSeller stackを実行するときはブラウザから送られてくるIGの情報をinputファイルとして渡してあげることで、Seller stackからBuyer stackにIGの情報が流れていきます。

成功すると

{
    "adRenderUrl":"https://example.com",
    "interestGroupName":"IG_NAME",
    "interestGroupOwner":"https://example-buyer-origin.com",
    "score":1,
    "bid":1,
    "biddingGroups": {
        "https://example-buyer-origin.com": {
            "index":[0]
        }
    }
}

といった出力になります。

 

おわりに

ここまででB&A Serviceを使ったオークションを一通り実行できるようになりました。この記事がB&A Serviceの検証をされる皆様の助けになれば幸いです。

次回はブラウザとintegrationさせたE2Eのオークションの動作確認をする予定です。