Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- The CLI subcommand `verify attestation` now supports `.slsa.attestation`
files. When verifying an artifact, both `.publish.attestation` and
`.slsa.attestation` files are used (if present).

## [0.0.21]

### Changed
Expand All @@ -18,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#82](https://github.com/trailofbits/pypi-attestations/pull/82))

### Added

- The CLI has a new subcommand `verify pypi`, which takes a URL to a
PyPI distribution (either a wheel or a source distribution) and a
GitHub/GitLab repository. The command verifies the distribution by
Expand Down
14 changes: 3 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,20 +134,12 @@ pypi-attestations inspect dist/pypi_attestations-*.whl.publish.attestation

### Verifying a PEP 740 Attestation

> [!NOTE]
> The example below uses an email with `--identity`, but actual PyPI
> attestations will be signed with a machine identity corresponding to the
> workflow that generated the attestation. The format of that identity

```bash
pypi-attestations verify attestation --staging \
--identity william@yossarian.net \
test/assets/rfc8785-0.1.2-py3-none-any.whl
pypi-attestations verify attestation \
--identity https://github.com/trailofbits/pypi-attestations/.github/workflows/release.yml@refs/tags/v0.0.19 \
test/assets/pypi_attestations-0.0.19.tar.gz
```

The attestation present in the test has been generated using the staging
environment of Sigstore and signed by the identity `william@yossarian.net`.

### Verifying a PyPI package
> [!NOTE]
> The URL must be a direct link to the distribution artifact hosted by PyPI.
Expand Down
50 changes: 26 additions & 24 deletions src/pypi_attestations/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import json
import logging
import typing
from collections import defaultdict
from pathlib import Path
from tempfile import TemporaryDirectory

Expand Down Expand Up @@ -385,35 +386,36 @@ def _verify_attestation(args: argparse.Namespace) -> None:
"""Verify the files passed as argument."""
pol = policy.Identity(identity=args.identity)

# Validate that both the attestations and files exist
# Validate that the files exist
_validate_files(args.files, should_exist=True)
_validate_files(
(Path(f"{file_path}.publish.attestation") for file_path in args.files),
should_exist=True,
)

inputs: list[Path] = []
for file_path in args.files:
inputs.append(file_path)

for input in inputs:
attestation_path = Path(f"{input}.publish.attestation")
try:
attestation = Attestation.model_validate_json(attestation_path.read_text())
except ValidationError as validation_error:
_die(f"Invalid attestation ({attestation_path}): {validation_error}")
# artifact -> [attestation1, attestation2, ...]
files_with_attestations: dict[Path, list[Path]] = defaultdict(list)
for f in args.files:
for attestation_file in (Path(f"{f}.publish.attestation"), Path(f"{f}.slsa.attestation")):
if attestation_file.exists():
files_with_attestations[f].append(attestation_file)
if not files_with_attestations[f]:
_die(f"Couldn't find attestations for file {f}")

for file_path, attestations in files_with_attestations.items():
for attestation_path in attestations:
try:
attestation = Attestation.model_validate_json(attestation_path.read_text())
except ValidationError as validation_error:
_die(f"Invalid attestation ({attestation_path}): {validation_error}")

try:
dist = Distribution.from_file(input)
except ValidationError as e:
_die(f"Invalid Python package distribution: {e}")
try:
dist = Distribution.from_file(file_path)
except ValidationError as e:
_die(f"Invalid Python package distribution: {e}")

try:
attestation.verify(pol, dist, staging=args.staging)
except VerificationError as verification_error:
_die(f"Verification failed for {input}: {verification_error}")
try:
attestation.verify(pol, dist, staging=args.staging)
except VerificationError as verification_error:
_die(f"Verification failed for {file_path}: {verification_error}")

_logger.info(f"OK: {attestation_path}")
_logger.info(f"OK: {attestation_path}")


def _verify_pypi(args: argparse.Namespace) -> None:
Expand Down
Binary file added test/assets/pypi_attestations-0.0.19.tar.gz
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"envelope":{"signature":"MEYCIQC+Yjc/GP2HATHRmlyN85BGBJdDgOWm/GVMDuoU6ESX/gIhAIyakdnRMW84PJVQJxpPp3zxwA9IdP64teBnkeFl71OH","statement":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicHlwaV9hdHRlc3RhdGlvbnMtMC4wLjE5LnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiI5YmIxYWRkMDRiMWI0ZTE4MmJlNmIwYjgwOTMxNTkzZjdhMjkxZWI0OWQ2OWI0ZmQ3MjhhNWQ0Y2JjZGM0YmQzIn19XSwicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vZG9jcy5weXBpLm9yZy9hdHRlc3RhdGlvbnMvcHVibGlzaC92MSIsInByZWRpY2F0ZSI6bnVsbH0="},"verification_material":{"certificate":"MIIG/jCCBoSgAwIBAgIUB9+WbpxQR+oVugVlR2Qp01owfvEwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjQxMjA0MjMxNDAxWhcNMjQxMjA0MjMyNDAxWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEga+LYX2vTD7c3fY1t/ppcVWnYsu4dJmLZgGlHTh3gdP10PGSxuOqfbMnW4PgkJNlbujG6D5XnNyVBiihXp/SEaOCBaMwggWfMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUJUX7ijmrEJ4ZgNxWVoJSi2JWKOcwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wbgYDVR0RAQH/BGQwYoZgaHR0cHM6Ly9naXRodWIuY29tL3RyYWlsb2ZiaXRzL3B5cGktYXR0ZXN0YXRpb25zLy5naXRodWIvd29ya2Zsb3dzL3JlbGVhc2UueW1sQHJlZnMvdGFncy92MC4wLjE5MDkGCisGAQQBg78wAQEEK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20wFQYKKwYBBAGDvzABAgQHcmVsZWFzZTA2BgorBgEEAYO/MAEDBCgwODgwMmVmZTFmOGU1ZmVjNGFkODQyZDZiOGNlOTc2NTYwOTJlZTcyMBUGCisGAQQBg78wAQQEB3JlbGVhc2UwKwYKKwYBBAGDvzABBQQddHJhaWxvZmJpdHMvcHlwaS1hdHRlc3RhdGlvbnMwHwYKKwYBBAGDvzABBgQRcmVmcy90YWdzL3YwLjAuMTkwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMHAGCisGAQQBg78wAQkEYgxgaHR0cHM6Ly9naXRodWIuY29tL3RyYWlsb2ZiaXRzL3B5cGktYXR0ZXN0YXRpb25zLy5naXRodWIvd29ya2Zsb3dzL3JlbGVhc2UueW1sQHJlZnMvdGFncy92MC4wLjE5MDgGCisGAQQBg78wAQoEKgwoMDg4MDJlZmUxZjhlNWZlYzRhZDg0MmQ2YjhjZTk3NjU2MDkyZWU3MjAdBgorBgEEAYO/MAELBA8MDWdpdGh1Yi1ob3N0ZWQwQAYKKwYBBAGDvzABDAQyDDBodHRwczovL2dpdGh1Yi5jb20vdHJhaWxvZmJpdHMvcHlwaS1hdHRlc3RhdGlvbnMwOAYKKwYBBAGDvzABDQQqDCgwODgwMmVmZTFmOGU1ZmVjNGFkODQyZDZiOGNlOTc2NTYwOTJlZTcyMCEGCisGAQQBg78wAQ4EEwwRcmVmcy90YWdzL3YwLjAuMTkwGQYKKwYBBAGDvzABDwQLDAk3NzIyNDc0MjMwLgYKKwYBBAGDvzABEAQgDB5odHRwczovL2dpdGh1Yi5jb20vdHJhaWxvZmJpdHMwFwYKKwYBBAGDvzABEQQJDAcyMzE0NDIzMHAGCisGAQQBg78wARIEYgxgaHR0cHM6Ly9naXRodWIuY29tL3RyYWlsb2ZiaXRzL3B5cGktYXR0ZXN0YXRpb25zLy5naXRodWIvd29ya2Zsb3dzL3JlbGVhc2UueW1sQHJlZnMvdGFncy92MC4wLjE5MDgGCisGAQQBg78wARMEKgwoMDg4MDJlZmUxZjhlNWZlYzRhZDg0MmQ2YjhjZTk3NjU2MDkyZWU3MjAXBgorBgEEAYO/MAEUBAkMB3JlbGVhc2UwZAYKKwYBBAGDvzABFQRWDFRodHRwczovL2dpdGh1Yi5jb20vdHJhaWxvZmJpdHMvcHlwaS1hdHRlc3RhdGlvbnMvYWN0aW9ucy9ydW5zLzEyMTY5OTg5Nzg3L2F0dGVtcHRzLzEwFgYKKwYBBAGDvzABFgQIDAZwdWJsaWMwgYoGCisGAQQB1nkCBAIEfAR6AHgAdgDdPTBqxscRMmMZHhyZZzcCokpeuN48rf+HinKALynujgAAAZOT87EeAAAEAwBHMEUCIQDT5XCp5Zk2YLgHSwKh7T13nBcpaEkqSA9FENdlc79RWgIgKhBG+Wxyj/J1Y871dOuuGFl++LhPrZPa3SVnOSgWn10wCgYIKoZIzj0EAwMDaAAwZQIxAJKeTgj+ghc3peD01ARd9aIVsEpDGdbOazKA2uaQNvSLOuduyJkLlNUNKN8QaNwpsQIwTpZ64vtjwC2SZAqb21Bn4qV7tLBJmIWx1lUe0QlVyix1180TkCZ0BVcfoD4xICKI","transparency_entries":[{"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZWFjNTY1OWQ4NDZhMTYxMzdhNDFiMzQxM2E5MTE4ZjFiODYxNDFhNmJiMWIxNTRhYWZhNWZhNGY2NTJjYWM0NCJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImY5NzkwZThhODdkMDE4OThkMDBkMmNlMWNhNmNlNDJiZmM2Y2U1OWZhYWEzMWJhYTBhYjY0MDY0YTY5NjgwMDAifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVZQ0lRQytZamMvR1AySEFUSFJtbHlOODVCR0JKZERnT1dtL0dWTUR1b1U2RVNYL2dJaEFJeWFrZG5STVc4NFBKVlFKeHBQcDN6eHdBOUlkUDY0dGVCbmtlRmw3MU9IIiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VjdmFrTkRRbTlUWjBGM1NVSkJaMGxWUWprclYySndlRkZTSzI5V2RXZFdiRkl5VVhBd01XOTNablpGZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwUmVFMXFRVEJOYWsxNFRrUkJlRmRvWTA1TmFsRjRUV3BCTUUxcVRYbE9SRUY0VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVm5ZU3RNV1ZneWRsUkVOMk16WmxreGRDOXdjR05XVjI1WmMzVTBaRXB0VEZwblIyd0tTRlJvTTJka1VERXdVRWRUZUhWUGNXWmlUVzVYTkZCbmEwcE9iR0oxYWtjMlJEVlliazU1VmtKcGFXaFljQzlUUldGUFEwSmhUWGRuWjFkbVRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVktWVmczQ21scWJYSkZTalJhWjA1NFYxWnZTbE5wTWtwWFMwOWpkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMkpuV1VSV1VqQlNRVkZJTDBKSFVYZFpiMXBuWVVoU01HTklUVFpNZVRsdVlWaFNiMlJYU1hWWk1qbDBURE5TZVZsWGJITmlNbHBwWVZoU2VncE1NMEkxWTBkcmRGbFlVakJhV0U0d1dWaFNjR0l5TlhwTWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhR015VlhWbFZ6RnpDbEZJU214YWJrMTJaRWRHYm1ONU9USk5RelIzVEdwRk5VMUVhMGREYVhOSFFWRlJRbWMzT0hkQlVVVkZTekpvTUdSSVFucFBhVGgyWkVjNWNscFhOSFVLV1ZkT01HRlhPWFZqZVRWdVlWaFNiMlJYU2pGak1sWjVXVEk1ZFdSSFZuVmtRelZxWWpJd2QwWlJXVXRMZDFsQ1FrRkhSSFo2UVVKQloxRklZMjFXY3dwYVYwWjZXbFJCTWtKbmIzSkNaMFZGUVZsUEwwMUJSVVJDUTJkM1QwUm5kMDF0Vm0xYVZFWnRUMGRWTVZwdFZtcE9SMFpyVDBSUmVWcEVXbWxQUjA1c0NrOVVZekpPVkZsM1QxUktiRnBVWTNsTlFsVkhRMmx6UjBGUlVVSm5OemgzUVZGUlJVSXpTbXhpUjFab1l6SlZkMHQzV1V0TGQxbENRa0ZIUkhaNlFVSUtRbEZSWkdSSVNtaGhWM2gyV20xS2NHUklUWFpqU0d4M1lWTXhhR1JJVW14ak0xSm9aRWRzZG1KdVRYZElkMWxMUzNkWlFrSkJSMFIyZWtGQ1FtZFJVZ3BqYlZadFkzazVNRmxYWkhwTU0xbDNUR3BCZFUxVWEzZFBkMWxMUzNkWlFrSkJSMFIyZWtGQ1EwRlJkRVJEZEc5a1NGSjNZM3B2ZGt3elVuWmhNbFoxQ2t4dFJtcGtSMngyWW01TmRWb3liREJoU0ZacFpGaE9iR050VG5aaWJsSnNZbTVSZFZreU9YUk5TRUZIUTJselIwRlJVVUpuTnpoM1FWRnJSVmxuZUdjS1lVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVEROU2VWbFhiSE5pTWxwcFlWaFNla3d6UWpWalIydDBXVmhTTUZwWVRqQlpXRkp3WWpJMWVncE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYVmxWekZ6VVVoS2JGcHVUWFprUjBadVkzazVNazFETkhkTWFrVTFDazFFWjBkRGFYTkhRVkZSUW1jM09IZEJVVzlGUzJkM2IwMUVaelJOUkVwc1dtMVZlRnBxYUd4T1YxcHNXWHBTYUZwRVp6Qk5iVkV5V1dwb2FscFVhek1LVG1wVk1rMUVhM2xhVjFVelRXcEJaRUpuYjNKQ1owVkZRVmxQTDAxQlJVeENRVGhOUkZka2NHUkhhREZaYVRGdllqTk9NRnBYVVhkUlFWbExTM2RaUWdwQ1FVZEVkbnBCUWtSQlVYbEVSRUp2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJaRWhLYUdGWGVIWmFiVXB3WkVoTmRtTkliSGRoVXpGb0NtUklVbXhqTTFKb1pFZHNkbUp1VFhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUkZGUmNVUkRaM2RQUkdkM1RXMVdiVnBVUm0xUFIxVXhXbTFXYWs1SFJtc0tUMFJSZVZwRVdtbFBSMDVzVDFSak1rNVVXWGRQVkVwc1dsUmplVTFEUlVkRGFYTkhRVkZSUW1jM09IZEJVVFJGUlhkM1VtTnRWbTFqZVRrd1dWZGtlZ3BNTTFsM1RHcEJkVTFVYTNkSFVWbExTM2RaUWtKQlIwUjJla0ZDUkhkUlRFUkJhek5PZWtsNVRrUmpNRTFxVFhkTVoxbExTM2RaUWtKQlIwUjJla0ZDQ2tWQlVXZEVRalZ2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJaRWhLYUdGWGVIWmFiVXB3WkVoTmQwWjNXVXRMZDFsQ1FrRkhSSFo2UVVJS1JWRlJTa1JCWTNsTmVrVXdUa1JKZWsxSVFVZERhWE5IUVZGUlFtYzNPSGRCVWtsRldXZDRaMkZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1NMUo1V1Zkc2MySXlXbWxoV0ZKNlRETkNOV05IYTNSWldGSXdXbGhPTUZsWVVuQmlNalY2VEhrMWJtRllVbTlrVjBsMlpESTVlV0V5V25OaU0yUjZDa3d6U214aVIxWm9ZekpWZFdWWE1YTlJTRXBzV201TmRtUkhSbTVqZVRreVRVTTBkMHhxUlRWTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjhLVFVSbk5FMUVTbXhhYlZWNFdtcG9iRTVYV214WmVsSm9Xa1JuTUUxdFVUSlphbWhxV2xSck0wNXFWVEpOUkd0NVdsZFZNMDFxUVZoQ1oyOXlRbWRGUlFwQldVOHZUVUZGVlVKQmEwMUNNMHBzWWtkV2FHTXlWWGRhUVZsTFMzZFpRa0pCUjBSMmVrRkNSbEZTVjBSR1VtOWtTRkozWTNwdmRrd3laSEJrUjJneENsbHBOV3BpTWpCMlpFaEthR0ZYZUhaYWJVcHdaRWhOZG1OSWJIZGhVekZvWkVoU2JHTXpVbWhrUjJ4MlltNU5kbGxYVGpCaFZ6bDFZM2s1ZVdSWE5Yb0tUSHBGZVUxVVdUVlBWR2MxVG5wbk0wd3lSakJrUjFaMFkwaFNla3g2UlhkR1oxbExTM2RaUWtKQlIwUjJla0ZDUm1kUlNVUkJXbmRrVjBwellWZE5kd3BuV1c5SFEybHpSMEZSVVVJeGJtdERRa0ZKUldaQlVqWkJTR2RCWkdkRVpGQlVRbkY0YzJOU1RXMU5Xa2hvZVZwYWVtTkRiMnR3WlhWT05EaHlaaXRJQ21sdVMwRk1lVzUxYW1kQlFVRmFUMVE0TjBWbFFVRkJSVUYzUWtoTlJWVkRTVkZFVkRWWVEzQTFXbXN5V1V4blNGTjNTMmczVkRFemJrSmpjR0ZGYTNFS1UwRTVSa1ZPWkd4ak56bFNWMmRKWjB0b1FrY3JWM2g1YWk5S01WazROekZrVDNWMVIwWnNLeXRNYUZCeVdsQmhNMU5XYms5VFoxZHVNVEIzUTJkWlNRcExiMXBKZW1vd1JVRjNUVVJoUVVGM1dsRkplRUZLUzJWVVoyb3JaMmhqTTNCbFJEQXhRVkprT1dGSlZuTkZjRVJIWkdKUFlYcExRVEoxWVZGT2RsTk1DazkxWkhWNVNtdE1iRTVWVGt0T09GRmhUbmR3YzFGSmQxUndXalkwZG5ScWQwTXlVMXBCY1dJeU1VSnVOSEZXTjNSTVFrcHRTVmQ0TVd4VlpUQlJiRllLZVdsNE1URTRNRlJyUTFvd1FsWmpabTlFTkhoSlEwdEpDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsifV19fQ==","inclusionPromise":{"signedEntryTimestamp":"MEQCIBCltBioLJc+JvwwmgQ1BYgIOE5RTG+4e7X3zQRGa6ZHAiA46xAH8obqC29dw2T8gwyllNzqr2tfG8iMWLnFKuIORQ=="},"inclusionProof":{"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n31550402\ny0mvJokg0K9X78/zSkzLfaoYE1EUnA0l4CTQQXV1g4o=\n\n— rekor.sigstore.dev wNI9ajBFAiBegciq2ELY5ENvqNVWFm9cEfG5dKBwBegOEaoDVWLdVwIhAJAOwu2VWIQojdyGzsV/t4MRCOkduH9SVKL8yoK9qMc2\n"},"hashes":["Ys0Xl1UkpNskWt6zZj4IF0V4C0hQnG2rcYT7yITCgxs=","SZr1mNOrcyCB9fdcJOMiVFgh1vv3F1JOn4Iro9CbeI0=","PcaivMMXf/sp7holRDnainZ7CqkhfGbumRzqHBLORyo=","QmGjL5m3QfWxTeS1DSKdcZi1Cb/kXHez6zUQJFM+6No=","RzzP82vxkFlwbK42drRrLKdQO7cU4TutI9RH4GZWqmc=","T180VQg42xooybP3Nj9QuoOWlDWbbaQ4n4S2wWmwsSc=","hABDIJpRLjWOq3nQy0+WmZvudV2qvqHNysU41bEne1Q=","pDqPuFyZgsegAtT2zcnZSet9YAZExTulHeEunQ5/LRg=","Q+viUQ+8PPr3Ck1WOMcjETEmAMGVbNDvGlSy6G/aFAI=","R7hO1X+KgSw8Oojd8i2+G3BzBYztkRBE6LpYSXPg33U=","oOecFfN3YqDOkbijS/ej1WF5Da/Gt/AZNhbwE9uoOE8=","4lUF0YOu9XkIDXKXA0wMSzd6VeDY3TZAgmoOeWmS2+Y=","gf+9m552B3PnkWnO0o4KdVvjcT3WVHLrCbf1DoVYKFw="],"logIndex":"31550401","rootHash":"y0mvJokg0K9X78/zSkzLfaoYE1EUnA0l4CTQQXV1g4o=","treeSize":"31550402"},"integratedTime":"1733354041","kindVersion":{"kind":"dsse","version":"0.0.1"},"logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"logIndex":"153454663"}]},"version":1}
Loading
Loading