ガイド · 全8部中 第7部
CI からリリースをアンカーする
出荷するリリースはどれも、バイト列の集まりです。tarball、wheel、コンテナイメージ、コミットの範囲。これらのバイト列に Label 309 存在証明(Proof of Existence, PoE)をアンカーすると、「このビルドが公開したものだと信じてください」が「これがそれを証言する Cardano トランザクションです」に変わります。各アーティファクトのハッシュを計算してリーフにし、リーフを 1 つの Merkle ルートへ畳み込み、その 1 つのルートをメタデータラベル 309 のもとでチェーンに発行します。それ以降、トランザクション参照を持つ人はだれでも、あるアーティファクトがそのブロック時刻かそれ以前に存在していたことを、公開チェーンだけから証明できます。アカウントも不要で、あなたのパイプラインやベンダーを信頼する必要もありません。
アーティファクトのバイト列がランナーを出ることは決してありません。CLI はローカルでハッシュを計算し、ダイジェストだけを発行するため、プライベートリポジトリやクローズドソースのビルドでも安全です。チェーンに載るのは固定長のハッシュであって、あなたのコードではありません。
中核となるツール: cardanowall attest
このページのすべては、ゲートウェイに依存しない cardanowall CLI の 1 つのコマンドを通じて動きます(crates.io のクレート cardanowall-cli。label-309-cli のリリースにビルド済みバイナリがあります)。attest は CI 向けに整えられた入口です。入力のハッシュを計算し、ゲートウェイ経由で見積もりを取ってレコードを 1 件発行し、あなたが指定したライフサイクル状態まで待機します。
ベース URL と発行スコープの API キーで任意の Label 309 ゲートウェイに向け、ハッシュを計算する対象を渡してください。
export CARDANOWALL_API_KEY="…" # a publish-scoped key from your gateway
cardanowall attest \
--paths 'dist/*' \
--base-url https://your-gateway.example/api/v1 \
--wait confirmed \
--receipt-out poe-receipt.json--base-url と --api-key は CARDANOWALL_BASE_URL と CARDANOWALL_API_KEY からも読み取られるため、それらがシークレットストアから設定される CI では、両方ともコマンドから省けます。
アンカーする対象を選ぶ 3 つの方法
入力はちょうど 1 つだけ設定します。モードはそれに従って決まります。
ファイル。 --paths はリテラルパスまたは glob パターンを取り、繰り返し指定できます。各リーフはファイルのバイト列の SHA-256 です。選択集合は重複除去され、正規化した相対パスでバイト単位にソートされるため、同じ作業ツリーは、シェルが glob を展開する順序に関係なく、常に同じルートを生成します。シェルが先に展開しないよう、glob は引用符で囲んでください。
cardanowall attest --paths 'dist/**/*.tar.gz' --paths 'dist/**/*.whl'コミット。 --commits は git rev-list の範囲を取り、各リーフは生のコミットオブジェクトの SHA-256 で、古いものから順に並びます。これは履歴そのものの来歴をアンカーします。浅いクローンでは範囲を解決できないため、ランナー上に完全な git 履歴が必要です。
cardanowall attest --commits v1.0.0..v1.1.0事前計算済みのダイジェスト。 --leaf は、あなたが別の場所で計算した 64 桁 16 進のダイジェストを取り、繰り返し指定でき、引数の順序が保たれます。OCI イメージダイジェストのように、CLI がファイルとして目にすることのないものをアンカーするのに使います。
cardanowall attest --leaf 9f86d0818840…0a08 # a 64-hex digest, e.g. an image digestリーフが 1 つなら単一項目のレコードを発行し、複数のリーフなら、ルートがチェーンにアンカーされる Merkle レコードを発行します。このときリーフ一覧がアップロードされるので、各項目は後で包含証明書を得られます。
マニフェストとレシート
ファイルモードでは、attest は出力の隣に決定論的な poe-manifest.json を書き出します(--manifest-out で名前を変更できます)。マニフェストはアンカーした各ファイルの「名前からハッシュへ」の対応を記録し、同じ入力は常にバイト単位で同一のマニフェストバイト列を生成します。--anchor-manifest を加えると、マニフェストの SHA-256 を最後のリーフとして畳み込み、ファイル名とハッシュの結び付き自体も、ルートがコミットする対象の一部になります。
--receipt-out は、レコード、見積もり、トランザクション、待機スナップショットを載せた、バージョン付き JSON レシートを書き出します。ビルドの証拠として保管してください。後の verify がアンカーを見つけて検証するのに必要なものは、これですべてです。ワークフローのアーティファクトとして保存するか、リリースに添付するか、changelog の隣にコミットしてください。
待機、保留、そして再実行
既定では、attest はトランザクションが確認しきい値を越えるまで待機します(--wait confirmed)。--wait submitted は、ネットワークに到達した時点で戻ります。待機には期限があります(--timeout、既定は 600 秒)。期限が過ぎても出力とレシートは書き出され、プロセスは 3(保留)で終了します。発行は失われず、ゲートウェイ上で継続するので、後でレシートを使って再確認できます。見積もりがあなたの上限を超えると、--max-usd の上限が発行を拒否し(アップロードの前に終了コード 1)、価格の急騰がパイプラインに不意の請求を課すことは決してありません。
再実行は構造上、安全です。attest は既定でべき等性ヘッダーを送りません。代わりにゲートウェイがバイト単位で同一のレコードを重複除去するため、同じビルドを再実行しても二度目のアンカーは行われず、二重に課金されることもありません。同じ dist/ を再びアンカーすると、二度目の実行は最初のレコードを無料で再生します。
GitHub Actions
cardanowall/poe-attest アクションは、GitHub ワークフロー向けに同じ CLI をラップします。オープンソースで、サプライチェーンの観点で固定されています。実行する CLI リリースの SHA-256 ダイジェストを埋め込み、毎回の実行前にダウンロードしたアーカイブと展開したバイナリの両方を検証するため、すり替えられたリリース資産は通り抜けられません。
リポジトリに 2 つのシークレットを保存します。GATEWAY_URL(/api/v1 で終わる、ゲートウェイのデータプレーンのベース URL)と GATEWAY_API_KEY(発行スコープのキー)です。そのうえで、公開時にリリース資産をアンカーします。
name: anchor-release
on:
release:
types: [published]
permissions:
contents: read
jobs:
attest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: cardanowall/poe-attest@v1
with:
gateway-url: ${{ secrets.GATEWAY_URL }}
api-key: ${{ secrets.GATEWAY_API_KEY }}
paths: |
dist/**/*.tar.gz
dist/**/*.whlこのステップはレシートを書き出し、トランザクションと検証リンクを含むサマリーを表示し、後続のステップ向けに出力(tx、record-id、verify-url など)を公開します。
専用の CI アイデンティティでストリームに署名する
パイプラインのアンカーを帰属可能かつ発見可能にするには、各レコードをアイデンティティシードで署名します。するとゲートウェイは署名者の公開鍵でレコードをインデックス化するので、だれでも ?signer=<public-key> を使って、ゲートウェイのレコードフィードからパイプラインの全履歴を一覧できます。署名は任意のままで、検証者がそれを要求することはありません。
パイプラインごとに専用の使い捨てアイデンティティを使い、個人のシードは決して使わないでください。環境で保護されたシークレットとして保存し、その環境からの実行だけが読めるようにします。
jobs:
attest:
runs-on: ubuntu-latest
environment: release-signing
steps:
- uses: actions/checkout@v7
- uses: cardanowall/poe-attest@v1
with:
gateway-url: ${{ secrets.GATEWAY_URL }}
api-key: ${{ secrets.GATEWAY_API_KEY }}
seed: ${{ secrets.CI_SIGNING_SEED }}
paths: dist/**/*.tar.gzシードはログでマスクされ、コマンドラインではなく stdin だけを通じて CLI に渡されます。いかなるシークレットもゲートウェイに到達することはありません。ハッシュ計算と署名はローカルで行われ、発行されるのはレコードと公開データだけです。
pull_request_target からは決してアンカーしない
このトリガーは、フォークされたプルリクエストのコードにリポジトリのシークレットを露出させます。release、push、workflow_dispatch
など、あなたが制御できるイベントからのみアンカーしてください。上記の最小ジョブに必要なのは
contents: read だけです。レシートをリリースにも添付する場合にのみ contents: write
を加えてください。
このアクションには、ここで示した以上の入力があります。リーフごとの包含証明書、リリース資産への添付、価格上限、タイムアウトのポリシーなどです。完全な一覧は アクションの README を参照してください。
GitLab CI/CD
GitLab では、同じラッパーが CI/CD コンポーネントとして提供されます。ジョブは CLI 自身のコンテナイメージ(バージョンとダイジェストで固定)の中で実行されるため、実行時に何もインストールされません。そして、このページのどこでも同じように、ホスト型でもセルフホスト型でも、任意の Label 309 ゲートウェイが使えます。
include:
- component: gitlab.com/cardanowall/poe-attest/attest@1
inputs:
gateway-url: https://your-gateway.example/api/v1
paths: |
dist/**/*.tar.gz
dist/**/*.whlシークレットは Settings → CI/CD → Variables で CI/CD 変数として、masked と protected を付けて登録します。CARDANOWALL_API_KEY(発行スコープのキー)と、署名する場合に限り CARDANOWALL_SEED です。シードはグループではなく必ずプロジェクト自体に設定してください。グループに置くと、継承によってすべての子プロジェクトのアンカーがひとつのアイデンティティで黙って署名されてしまいます。デフォルトでは、ジョブは保護タグのパイプラインでのみ実行されるため、保護されていない ref があなたの残高を消費することはありません。他のイベントでアンカーしたい場合は、入力 rules を上書きしてください。
結果は dotenv レポートとして返ります。needs: でアテストジョブを参照する後続ジョブは、トランザクション、検証リンク、そして 18 個の POE_* 変数の残りを直接読み取れます。
announce:
needs: [poe-attest]
script:
- echo "anchored in $POE_TX"
- echo "verify at $POE_VERIFY_URL"このコンポーネントには、ここで示した以上の入力があります。包含証明書、価格上限、ランナータグ、タイムアウトのポリシーなどです。完全な一覧は コンポーネントの README を参照してください。
その他の CI システム
同じ CLI はどこでも動きます。コンテナイメージ ghcr.io/cardanowall/label-309-cli(その entrypoint は cardanowall)か、リリースページのビルド済みバイナリを使ってください。
その他のランナーでも、バイナリが PATH にあれば動きます。
export CARDANOWALL_BASE_URL="https://your-gateway.example/api/v1"
export CARDANOWALL_API_KEY="$YOUR_CI_SECRET"
cardanowall attest \
--paths 'dist/*' \
--wait confirmed \
--receipt-out poe-receipt.json
# exit 0 = reached the wait target; 3 = pending (publish continues on the gateway);
# 1 = refused (for example over --max-usd) or failed.ゲートウェイに必要なもの
発行は Cardano にトランザクションを載せる操作で、手数料がかかります。そのため attest は、送信先となるゲートウェイを必要とします。どの Label 309 ゲートウェイでも構いません。ホスト型の運用者でも、あなた自身のセルフホストのゲートウェイ(オープンソースの label-309-gateway。Rust バイナリ 1 つと Postgres)でも同じです。CI から必要なのは、そこから得る 2 つだけです。データプレーンのベース URL と、前払い残高で裏打ちされた発行スコープ(poe:create)の API キーです。
ゲートウェイは資金を用意した Cardano ウォレットを保有し、自身の残高モデルから手数料を支払います。あなたの CI はウォレットの鍵もチェーン上の資金も保持しません。漏洩した API キーにできる最悪のことは、そのアカウントの前払い残高をさらなるアンカーに費やすことだけです。資金を動かすことも、あなたのコンテンツを読むことも、あなたの名で署名することもできません。いつでもローテーションや失効ができます。
アンカーを検証する
このアンカーに価値があるのは、まさにだれもがあなたなしで確認できるからです。レシートにあるトランザクション参照があれば、検証は公開チェーンとあなたが選んだエクスプローラーに対して単独で実行され、アカウントもゲートウェイも要りません。
cardanowall verify <tx-hash>トランザクションを解決し、レコードを構造的に検証し、署名があれば確認し、レコードが確定していることを確かめ、結論を終了コードとして返します。そのため、attest が発行側にきれいに収まるのと同じように、下流のチェックにもきれいに収まります。あるアーティファクトをそのアンカーと突き合わせるには、ファイルのハッシュを計算して比較します。Merkle レコードなら、包含証明書を作り、1 つのアーティファクトを公開済みのルートに結び付けます。検証者モデルの全体は検証にあります。
証明はパイプラインより長く残る
Label 309 のアンカーは、ラベル 309 のもとの素朴なメタデータであって、ベンダーのレシートではありません。ランナーが消え、レジストリがローテーションされ、CI システムが記憶になってからもずっと、そのトランザクションは、あなたのアーティファクトがそのブロック時刻に存在していたことを証言し続けます。だれでも公開チェーンからそれを検証でき、アカウントも、発行者への信頼も要りません。