가이드

가이드 · 전체 8부 중 7부

CI에서 릴리스 앵커링하기

당신이 출시하는 모든 릴리스는 바이트의 묶음입니다. tarball, wheel, 컨테이너 이미지, 커밋 범위. 이 바이트들에 Label 309 존재 증명(Proof of Existence, PoE)을 앵커링하면 "이 빌드가 우리가 게시한 것이라는 우리 말을 믿으십시오"가 "여기 그것을 증언하는 Cardano 트랜잭션이 있습니다"로 바뀝니다. 각 아티팩트의 해시를 계산해 리프로 만들고, 리프들을 하나의 Merkle 루트로 접은 뒤, 그 하나의 루트를 메타데이터 라벨 309 아래 체인에 게시합니다. 그때부터 트랜잭션 참조를 가진 사람은 누구나, 어떤 아티팩트가 그 블록 시각 또는 그 이전에 존재했음을 오직 공개 체인만으로 증명할 수 있습니다. 계정도 필요 없고, 당신의 파이프라인이나 벤더를 신뢰할 필요도 없습니다.

아티팩트의 바이트는 결코 러너를 떠나지 않습니다. CLI는 로컬에서 해시를 계산하고 다이제스트만 게시하므로, 비공개 저장소와 비공개 소스 빌드에서도 안전합니다. 체인에 올라가는 것은 고정 길이 해시이지, 당신의 코드가 아닙니다.

핵심 도구: cardanowall attest

이 페이지의 모든 것은 게이트웨이에 독립적인 cardanowall CLI의 한 명령으로 실행됩니다(crates.io의 크레이트 cardanowall-cli이며, label-309-cli 릴리스에 사전 빌드된 바이너리가 있습니다). attest는 CI에 맞춰진 진입점입니다. 입력의 해시를 계산하고, 게이트웨이를 통해 견적을 받아 레코드 하나를 게시한 뒤, 당신이 요청한 수명 주기 상태까지 대기합니다.

베이스 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-keyCARDANOWALL_BASE_URLCARDANOWALL_API_KEY에서도 읽으므로, 이 값들이 시크릿 저장소에서 설정되는 CI에서는 둘 다 명령에서 빠집니다.

앵커링할 대상을 고르는 세 가지 방법

입력은 정확히 하나만 설정하십시오. 모드는 그로부터 결정됩니다.

파일. --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

리프가 하나면 단일 항목 레코드를 게시하고, 여럿이면 루트가 체인에 앵커링되는 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 다이제스트를 내장하고, 매 실행 전에 내려받은 아카이브와 추출된 바이너리를 모두 검증하므로, 바꿔치기된 릴리스 자산은 통과하지 못합니다.

저장소에 시크릿 두 개를 저장하십시오. 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에서 masked와 protected를 켠 CI/CD 변수로 등록하십시오. 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 바이너리 하나에 Postgres)든 마찬가지입니다. CI에서 필요한 것은 그로부터 딱 두 가지, 데이터 플레인 베이스 URL과 선불 잔액으로 뒷받침되는 게시 범위(poe:create) API 키입니다.

게이트웨이는 자금이 채워진 Cardano 지갑을 보유하고 자신의 잔액 모델에서 수수료를 지불합니다. 당신의 CI는 지갑 키도, 체인상의 자금도 보유하지 않습니다. 유출된 API 키가 할 수 있는 최악은 그 계정의 선불 잔액을 앵커링에 더 쓰는 것뿐입니다. 자금을 옮기거나, 당신의 콘텐츠를 읽거나, 당신을 대신해 서명할 수는 없습니다. 언제든 교체하거나 폐기하십시오.

앵커 검증하기

이 앵커링이 값어치가 있는 것은, 바로 누구나 당신 없이도 그것을 확인할 수 있기 때문입니다. 영수증에 담긴 트랜잭션 참조가 있으면, 검증은 공개 체인과 당신이 고른 익스플로러를 상대로 독립적으로 실행되며, 계정도 게이트웨이도 필요 없습니다.

cardanowall verify <tx-hash>

트랜잭션을 해석하고, 레코드를 구조적으로 검증하며, 서명이 있으면 확인하고, 레코드가 확정되었음을 확인한 뒤, 결론을 종료 코드로 반환합니다. 그래서 attest가 게시 쪽에 들어맞는 것만큼이나 깔끔하게 하류 검사에도 들어맞습니다. 어떤 아티팩트를 그 앵커와 대조하려면 파일의 해시를 계산해 비교하고, Merkle 레코드라면 포함 증명서를 만들어 하나의 아티팩트를 게시된 루트에 고정하십시오. 검증자 모델 전체는 검증에 있습니다.

증명은 파이프라인보다 오래 남습니다

Label 309 앵커는 라벨 309 아래의 평범한 메타데이터일 뿐, 벤더 영수증이 아닙니다. 러너가 사라지고, 레지스트리가 교체되고, CI 시스템이 기억이 된 뒤로도 한참 동안, 그 트랜잭션은 당신의 아티팩트가 그 블록 시각에 존재했음을 계속 증언합니다. 누구나 공개 체인에서 그것을 검증할 수 있으며, 계정도, 그것을 게시한 이에 대한 신뢰도 필요 없습니다.