Guias

Guias · Parte 7 de 8

Ancore seus lançamentos a partir da CI

Cada lançamento que você distribui é um conjunto de bytes: tarballs, wheels, imagens de contêiner, um intervalo de commits. Ancorar uma Prova de Existência (PoE) Label 309 para esses bytes transforma «acredite na nossa palavra de que este build é o que publicamos» em «aqui está uma transação Cardano que atesta isso». Você calcula o hash de cada artefato para obter uma folha, dobra as folhas em uma única raiz de Merkle e publica essa única raiz na cadeia sob o rótulo de metadados 309. A partir daí, qualquer pessoa que tenha a referência da transação pode provar que um artefato existia na hora do seu bloco ou antes, apenas a partir da cadeia pública, sem conta e sem confiar no seu pipeline ou no seu fornecedor.

Os bytes do artefato nunca saem do runner. A CLI calcula o hash localmente e publica apenas digests, portanto isso é seguro para repositórios privados e builds de código fechado: o que vai para a cadeia é um hash de comprimento fixo, nunca o seu código.

A ferramenta central: cardanowall attest

Tudo nesta página passa por um único comando da CLI cardanowall, independente do gateway (crate cardanowall-cli no crates.io, com binários pré-compilados nos lançamentos do label-309-cli). attest é o ponto de entrada pensado para CI: calcula o hash das suas entradas, cota e publica um registro através de um gateway e aguarda o estado do ciclo de vida que você indicar.

Aponte-a para qualquer gateway Label 309 com uma URL base e uma chave de API com escopo de publicação e, em seguida, entregue a ela algo cujo hash calcular:

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 e --api-key também são lidos de CARDANOWALL_BASE_URL e CARDANOWALL_API_KEY, de modo que ambos somem do comando na CI, onde são definidos a partir do seu cofre de segredos.

Três formas de escolher o que ancorar

Defina exatamente uma entrada; o modo decorre dela.

Arquivos. --paths aceita um caminho literal ou um padrão glob, repetível. Cada folha é o SHA-256 dos bytes de um arquivo. A seleção é deduplicada e ordenada byte a byte por caminho relativo normalizado, de modo que a mesma árvore de trabalho sempre produz a mesma raiz, independentemente da ordem em que a shell expandiria um glob. Coloque o glob entre aspas para que a sua shell não o expanda antes:

cardanowall attest --paths 'dist/**/*.tar.gz' --paths 'dist/**/*.whl'

Commits. --commits aceita um intervalo git rev-list; cada folha é o SHA-256 de um objeto de commit bruto, do mais antigo ao mais recente. Isso ancora a procedência do próprio histórico. Requer o histórico git completo no runner, pois um clone raso não consegue resolver o intervalo:

cardanowall attest --commits v1.0.0..v1.1.0

Digests pré-calculados. --leaf aceita um digest de 64 hex que você calculou em outro lugar, repetível e mantido na ordem dos argumentos. Use-o para ancorar algo que a CLI nunca vê como arquivo, como o digest de uma imagem OCI:

cardanowall attest --leaf 9f86d0818840…0a08   # a 64-hex digest, e.g. an image digest

Uma única folha publica um registro de um só item; várias folhas publicam um registro de Merkle cuja raiz é ancorada na cadeia, com a lista de folhas enviada para que cada item possa depois obter um certificado de inclusão.

O manifesto e o recibo

No modo de arquivos, attest escreve um poe-manifest.json determinístico ao lado da sua saída (renomeie-o com --manifest-out). O manifesto registra a associação nome-hash de cada arquivo que ancorou, e as mesmas entradas sempre produzem bytes de manifesto idênticos. Adicione --anchor-manifest para dobrar o SHA-256 do manifesto como folha final, de modo que o vínculo entre nomes de arquivo e hashes passe a fazer parte, ele próprio, daquilo a que a raiz se compromete.

--receipt-out escreve um recibo JSON versionado que carrega o registro, a cotação, a transação e o instantâneo da espera. Guarde-o como a prova do seu build: é tudo de que um verify posterior precisa para encontrar e conferir a âncora. Armazene-o como artefato do fluxo de trabalho, anexe-o ao lançamento ou faça commit dele junto ao changelog.

Espera, estado pendente e reexecuções

Por padrão, attest espera a transação cruzar o limiar de confirmação (--wait confirmed); --wait submitted retorna assim que ela alcança a rede. A espera tem um prazo (--timeout, 600 segundos por padrão). Se o prazo vencer, a saída e o recibo são escritos mesmo assim e o processo termina com 3 (pendente): a publicação não se perde, continua no gateway, e você pode reconferi-la mais tarde com o recibo. Um teto --max-usd recusa a publicação (código de saída 1, antes de qualquer envio) quando a cotação ultrapassa o seu limite, de modo que um pico de preço nunca pode cobrar de surpresa um pipeline.

As reexecuções são seguras por construção. attest não envia nenhum cabeçalho de idempotência por padrão; em vez disso, o gateway deduplica registros idênticos byte a byte, de modo que reexecutar o mesmo build nunca ancora uma segunda vez nem cobra em dobro. Ancore o mesmo dist/ de novo e a segunda execução repete o primeiro registro sem custo.

GitHub Actions

A action cardanowall/poe-attest envolve a mesma CLI para os fluxos de trabalho do GitHub. É de código aberto e fixada na cadeia de suprimentos: ela incorpora os digests SHA-256 do lançamento da CLI que executa e verifica tanto o arquivo baixado quanto o binário extraído antes de cada execução, de modo que um asset de lançamento trocado não consiga passar.

Guarde dois segredos no repositório, GATEWAY_URL (a URL base do plano de dados do seu gateway, terminando em /api/v1) e GATEWAY_API_KEY (uma chave com escopo de publicação), e então ancore seus assets de lançamento ao publicá-lo:

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

O passo escreve o recibo, imprime um resumo com a transação e um link de verificação, e expõe saídas (tx, record-id, verify-url e outras) para passos posteriores.

Assinar o fluxo com uma identidade de CI dedicada

Para tornar as âncoras de um pipeline atribuíveis e localizáveis, assine cada registro com uma semente de identidade. O gateway passa então a indexar o registro pela chave pública de quem assina, de modo que qualquer pessoa pode listar todo o histórico de um pipeline a partir do feed de registros do gateway com ?signer=<public-key>. A assinatura continua opcional, e os verificadores nunca a exigem.

Use uma identidade dedicada e descartável por pipeline, nunca uma semente pessoal. Guarde-a como um segredo protegido por ambiente para que apenas execuções desse ambiente possam lê-la:

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

A semente é mascarada nos logs e passada à CLI apenas por stdin, nunca na linha de comando, e nenhum segredo jamais chega ao gateway: o cálculo do hash e a assinatura acontecem localmente, e apenas o registro e dados públicos são publicados.

Nunca ancore a partir de pull_request_target

Esse gatilho expõe os segredos do seu repositório a código vindo de pull requests bifurcados. Ancore apenas a partir de eventos que você controla, como release, push ou workflow_dispatch. O job mínimo acima precisa apenas de contents: read; adicione contents: write somente se também anexar o recibo ao lançamento.

A action oferece mais entradas do que as mostradas aqui, entre elas um certificado de inclusão por folha, a anexação de assets ao lançamento, um teto de preço e a política de tempo limite. Consulte o README da action para o conjunto completo.

GitLab CI/CD

No GitLab, o mesmo wrapper é distribuído como um componente de CI/CD. O job roda dentro da própria imagem de contêiner da CLI, fixada por versão e digest, então nada é instalado em tempo de execução — e, como em toda esta página, qualquer gateway Label 309 funciona, hospedado ou autogerenciado:

include:
  - component: gitlab.com/cardanowall/poe-attest/attest@1
    inputs:
      gateway-url: https://your-gateway.example/api/v1
      paths: |
        dist/**/*.tar.gz
        dist/**/*.whl

Adicione os segredos como variáveis de CI/CD em Settings → CI/CD → Variables, mascaradas e protegidas: CARDANOWALL_API_KEY (a chave com escopo de publicação) e, apenas se você assinar, CARDANOWALL_SEED — definida no próprio projeto, nunca em um grupo: a herança assinaria em silêncio as âncoras de cada projeto filho com uma única identidade. Por padrão, o job roda apenas em pipelines de tags protegidas, de modo que uma referência desprotegida nunca pode gastar o seu saldo; sobrescreva a entrada rules para ancorar em outros eventos.

Os resultados voltam como um relatório dotenv: um job posterior que referencie o job de atestação com needs: lê diretamente a transação, o link de verificação e o restante das dezoito variáveis POE_*:

announce:
  needs: [poe-attest]
  script:
    - echo "anchored in $POE_TX"
    - echo "verify at $POE_VERIFY_URL"

O componente oferece mais entradas do que as mostradas aqui, entre elas certificados de inclusão, um teto de preço, tags de runner e a política de tempo limite. Consulte o README do componente para o conjunto completo.

Outros sistemas de CI

A mesma CLI roda em qualquer lugar. Use a imagem de contêiner ghcr.io/cardanowall/label-309-cli (cujo entrypoint é cardanowall) ou um binário pré-compilado da página de lançamentos.

Qualquer outro runner, com o binário no 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.

O que você precisa de um gateway

Publicar coloca uma transação na Cardano, e isso custa uma taxa, então attest precisa de um gateway pelo qual enviar. Qualquer gateway Label 309 serve: um operador hospedado, ou o seu próprio gateway auto-hospedado (o label-309-gateway de código aberto, um binário Rust mais Postgres). Da CI, você só precisa de duas coisas dele: uma URL base do plano de dados e uma chave de API com escopo de publicação (poe:create), lastreada por saldo pré-pago.

O gateway é dono da carteira Cardano financiada e paga a taxa a partir do seu próprio modelo de saldo. Sua CI não guarda nenhuma chave de carteira nem fundos na cadeia. O pior que uma chave de API vazada pode fazer é gastar o saldo pré-pago dessa conta em mais âncoras; ela não pode mover fundos, ler o seu conteúdo nem assinar em seu nome. Rotacione-a ou revogue-a a qualquer momento.

Verificar a âncora

Essa ancoragem só vale alguma coisa porque qualquer pessoa pode conferi-la sem você. Dada a referência da transação vinda do recibo, a verificação roda de forma autônoma contra a cadeia pública e um explorador à sua escolha, sem conta e sem gateway:

cardanowall verify <tx-hash>

Ela resolve a transação, valida estruturalmente o registro, confere qualquer assinatura, confirma que o registro está consolidado e devolve um veredito como seu código de saída, de modo que se encaixa em uma verificação posterior com a mesma limpeza com que attest se encaixa no lado da publicação. Para confirmar um artefato contra a sua âncora, calcule o hash do arquivo e compare, ou, para um registro de Merkle, construa um certificado de inclusão que prende um artefato à raiz publicada. O modelo completo do verificador está em Verificação.

A prova sobrevive ao pipeline

Uma âncora Label 309 são simples metadados sob o rótulo 309, não um recibo de fornecedor. Muito depois de o runner ter sumido, o registry ter sido rotacionado e o sistema de CI ser apenas uma lembrança, a transação ainda atesta que seus artefatos existiam na hora do seu bloco. Qualquer pessoa pode verificá-la a partir da cadeia pública, sem conta e sem confiar em quem a publicou.