Skip to content

Commit 0d69787

Browse files
authored
Add high-level APIs to sign and verify Python artifacts (#8)
* Add high-level APIs to sign and verify Python artifacts * Add a verification test that doesn't mock the sigstore verifier * Update sigstore dependency * Fix type error * Raise own exception class for verification errors
1 parent 963fdda commit 0d69787

File tree

7 files changed

+263
-146
lines changed

7 files changed

+263
-146
lines changed

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,42 @@ python -m pip install pypi-attestation-models
1818

1919
See the full API documentation [here].
2020

21+
22+
### Signing and verification
23+
Use these APIs to create a PEP 740-compliant `Attestation` object by signing a Python artifact
24+
(i.e: sdist or wheel files), and to verify an `Attestation` object against a Python artifact.
25+
26+
```python
27+
from pathlib import Path
28+
29+
from pypi_attestation_models import Attestation, AttestationPayload
30+
from sigstore.oidc import Issuer
31+
from sigstore.sign import SigningContext
32+
from sigstore.verify import Verifier, policy
33+
34+
artifact_path = Path("test_package-0.0.1-py3-none-any.whl")
35+
36+
# Sign a Python artifact
37+
issuer = Issuer.production()
38+
identity_token = issuer.identity_token()
39+
signing_ctx = SigningContext.production()
40+
with signing_ctx.signer(identity_token, cache=True) as signer:
41+
attestation = AttestationPayload.from_dist(artifact_path).sign(signer)
42+
43+
print(attestation.model_dump_json())
44+
45+
# Verify an attestation against a Python artifact
46+
attestation_path = Path("test_package-0.0.1-py3-none-any.whl.attestation")
47+
attestation = Attestation.model_validate_json(attestation_path.read_bytes())
48+
verifier = Verifier.production()
49+
policy = policy.Identity(identity="example@gmail.com", issuer="https://accounts.google.com")
50+
attestation.verify(verifier, policy, attestation_path)
51+
52+
```
53+
54+
### Low-level model conversions
55+
These conversions assume that any Sigstore Bundle used as an input was created
56+
by signing an `AttestationPayload` object.
2157
```python
2258
from pathlib import Path
2359
from pypi_attestation_models import pypi_to_sigstore, sigstore_to_pypi, Attestation

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ classifiers = [
1515
"Programming Language :: Python :: 3",
1616
"License :: OSI Approved :: Apache Software License",
1717
]
18-
dependencies = ["cryptography", "pydantic", "sigstore==3.0.0rc1"]
18+
dependencies = ["cryptography", "pydantic", "sigstore==3.0.0rc2"]
1919
requires-python = ">=3.9"
2020

2121
[project.optional-dependencies]
@@ -73,11 +73,11 @@ target-version = "py39"
7373

7474
[tool.ruff.lint]
7575
select = ["ALL"]
76-
# ANN102 is deprecated
76+
# ANN101 and ANN102 are deprecated
7777
# D203 and D213 are incompatible with D211 and D212 respectively.
7878
# COM812 and ISC001 can cause conflicts when using ruff as a formatter.
7979
# See https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules.
80-
ignore = ["ANN102", "D203", "D213", "COM812", "ISC001"]
80+
ignore = ["ANN101", "ANN102", "D203", "D213", "COM812", "ISC001"]
8181

8282
[tool.ruff.lint.per-file-ignores]
8383

src/pypi_attestation_models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
ConversionError,
99
InvalidAttestationError,
1010
TransparencyLogEntry,
11+
VerificationError,
1112
VerificationMaterial,
1213
pypi_to_sigstore,
1314
sigstore_to_pypi,
@@ -19,6 +20,7 @@
1920
"ConversionError",
2021
"InvalidAttestationError",
2122
"TransparencyLogEntry",
23+
"VerificationError",
2224
"VerificationMaterial",
2325
"pypi_to_sigstore",
2426
"sigstore_to_pypi",

src/pypi_attestation_models/_impl.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
if TYPE_CHECKING:
2323
from pathlib import Path # pragma: no cover
2424

25+
from sigstore.sign import Signer # pragma: no cover
26+
from sigstore.verify import Verifier # pragma: no cover
27+
from sigstore.verify.policy import VerificationPolicy # pragma: no cover
28+
2529

2630
class ConversionError(ValueError):
2731
"""The base error for all errors during conversion."""
@@ -35,6 +39,14 @@ def __init__(self: InvalidAttestationError, msg: str) -> None:
3539
super().__init__(f"Could not convert input Attestation: {msg}")
3640

3741

42+
class VerificationError(ValueError):
43+
"""The PyPI Attestation failed verification."""
44+
45+
def __init__(self: VerificationError, msg: str) -> None:
46+
"""Initialize an `VerificationError`."""
47+
super().__init__(f"Verification failed: {msg}")
48+
49+
3850
TransparencyLogEntry = NewType("TransparencyLogEntry", dict[str, Any])
3951

4052

@@ -72,6 +84,21 @@ class Attestation(BaseModel):
7284
is the raw bytes of the signing operation over the attestation payload.
7385
"""
7486

87+
def verify(self, verifier: Verifier, policy: VerificationPolicy, dist: Path) -> None:
88+
"""Verify against an existing Python artifact.
89+
90+
On failure, raises:
91+
- `InvalidAttestationError` if the attestation could not be converted to
92+
a Sigstore Bundle.
93+
- `VerificationError` if the attestation could not be verified.
94+
"""
95+
payload_to_verify = AttestationPayload.from_dist(dist)
96+
bundle = pypi_to_sigstore(self)
97+
try:
98+
verifier.verify_artifact(bytes(payload_to_verify), bundle, policy)
99+
except sigstore.errors.VerificationError as err:
100+
raise VerificationError(str(err)) from err
101+
75102

76103
class AttestationPayload(BaseModel):
77104
"""Attestation Payload object as defined in PEP 740."""
@@ -94,6 +121,11 @@ def from_dist(cls, dist: Path) -> AttestationPayload:
94121
digest=sha256(dist.read_bytes()).hexdigest(),
95122
)
96123

124+
def sign(self, signer: Signer) -> Attestation:
125+
"""Create a PEP 740 attestation by signing this payload."""
126+
sigstore_bundle = signer.sign_artifact(bytes(self))
127+
return sigstore_to_pypi(sigstore_bundle)
128+
97129
def __bytes__(self: AttestationPayload) -> bytes:
98130
"""Convert to bytes using a canonicalized JSON representation (from RFC8785)."""
99131
return rfc8785.dumps(self.model_dump())
@@ -110,7 +142,7 @@ def sigstore_to_pypi(sigstore_bundle: Bundle) -> Attestation:
110142
version=1,
111143
verification_material=VerificationMaterial(
112144
certificate=b64encode(certificate).decode("ascii"),
113-
transparency_entries=[sigstore_bundle.log_entry._to_dict_rekor()], # noqa: SLF001
145+
transparency_entries=[TransparencyLogEntry(sigstore_bundle.log_entry._to_dict_rekor())], # noqa: SLF001
114146
),
115147
message_signature=b64encode(signature).decode("ascii"),
116148
)
Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,48 @@
11
{
22
"version": 1,
33
"verification_material": {
4-
"certificate": "MIIC1zCCAl2gAwIBAgIUZk9ToGFUJexy+/rxwIF8BB+3C5YwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjQwNDI5MTY1MTMzWhcNMjQwNDI5MTcwMTMzWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEM5H5f7A4HutVTfKFimTd2UbTzgUOY7rph9GKqgsZ7ChAp8FGJbrrgn6o+nprUEFKqEaIi+fWQJvR+RJkoQcWMKOCAXwwggF4MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUnrc9nJ2dxJd1a5sCFj/P+y3MuhQwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wLAYDVR0RAQH/BCIwIIEeZmFjdW5kby50dWVzY2FAdHJhaWxvZmJpdHMuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABjyrE0sQAAAQDAEcwRQIhAORhP1HaCcD4QK8+8VcNL46W0AAk6cIDUAH3SV4stJUVAiBafyw+FTpgvoTU+2U7QCyjlQZ5J2dPpVqv9Up3vV2GTDAKBggqhkjOPQQDAwNoADBlAjEAzoA4cMHxHCEXA80ahwJUSz/1kYotTXRNzeWU69SyaZE7Po+vZ5/ANfKvbCv9s19rAjABbw/INkA4dGKWEDNtSjnloZuH5N9aPBOV425+iKCe2bmf9cYVlFvCbGmHiEg/r5k=",
4+
"certificate": "MIIC1jCCAlygAwIBAgIUTDMXIHMGbNF+Sm0qoQoj35hY3zkwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjQwNTAzMTQwNTU0WhcNMjQwNTAzMTQxNTU0WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqv2vvFAD66IdGSg/+JbB/nYfook1FqpmM773o9MdVZktl1LkUWAU8SzaZhSso/7qVriyH/S8km0HqTVMzuZ+SaOCAXswggF3MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU5TQvo55q5OXkVFmYDsR93Neffq0wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wLAYDVR0RAQH/BCIwIIEeZmFjdW5kby50dWVzY2FAdHJhaWxvZmJpdHMuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBiQYKKwYBBAHWeQIEAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABjz7Gm3oAAAQDAEYwRAIgGkFpx5q3NzmgBIPywysdADDRRRPM/xsa8Fkfva+chKECIAu5HgO2eKsdc9pohmgn/modVcJ1Q5Muou9d4l1c5fXyMAoGCCqGSM49BAMDA2gAMGUCMHschAnWt88W4cu35dEv0MJ72s3BZsudUQzZ8dtg0xBlF3uwdDoprNfbA2tM5piDlAIxAMBg5Dqx0BV/9Rp/QMLhb+aGqZm7n7E5GkXJpHA8TySZamdyYuRlEiPf4cj7x/ruyw==",
55
"transparency_entries": [
66
{
7-
"logIndex": "89569370",
7+
"logIndex": "90818200",
88
"logId": {
99
"keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="
1010
},
1111
"kindVersion": {
1212
"kind": "hashedrekord",
1313
"version": "0.0.1"
1414
},
15-
"integratedTime": "1714409493",
15+
"integratedTime": "1714745154",
1616
"inclusionPromise": {
17-
"signedEntryTimestamp": "MEUCIQC29r0j8BCu5Zye9dvFaVBmCFzHIDBDDWH+LqcZ8aTAyAIgas85UjhG4jrVwqBr/U/nQ86vwOWQnHKjGnd/bdnQafQ="
17+
"signedEntryTimestamp": "MEUCIQCyiwcUzPS8I8WKRuLGelfRdlmx/AtM6TwMmyvK8utURwIgDKtqe3gaZe8bbVupw3HJm4nzvpYAZHaHmv/cCbJRYlo="
1818
},
1919
"inclusionProof": {
20-
"logIndex": "85405939",
21-
"rootHash": "CdYMKt8P8arrU1iilSLHbPDwMzhWgUbA6xvb1DSZktc=",
22-
"treeSize": "85405941",
20+
"logIndex": "86654769",
21+
"rootHash": "rpJ6l1p+pM8A+nMvMT80o0+qrCozIEfbG6qa0psYB0U=",
22+
"treeSize": "86654770",
2323
"hashes": [
24-
"btr1R1ZOzi0Kqk+vdfHrDcC1zasNMvXaNehse4NeMu8=",
25-
"8IgEN8pEU3WrVRCsbbCkFHeamV5xoyN1OByng98lPow=",
26-
"i6x3rMfR1HZCafUIGTyYgvtfwjF9zUe9Q5MPKNdWhS8=",
27-
"kE6NsBxRpnT9Q/DgLDqQELBaor5pThUMmHIRuKapA7c=",
28-
"Y55DSeWN5DUnIuvK5RRsaF4b35EtjKasFpV2n2LfrA0=",
29-
"UeVeMpyn/bEywYJvThS5PltcrELznbc/OFTARixCNUQ=",
30-
"MaMJLiuvSigqcgOZ6BulADPyhWaoYE1C+sGj/twciwM=",
31-
"YrLY+ujALEUYcIeyq2ri+QBsl7Sxh+frMg7GlVcIvZ0=",
32-
"27GLifvQI1aATtmSwJQGmbKXDoBpa0R5Q8fQUuX3/kQ=",
33-
"xhLV7tE4kPldhHSKGpGuBkcxIUSpEvNntVSzM+5raW4=",
34-
"btR5C6cRDz4AcGce5sOqhKIlEYTQ29AJLmv7L3kPyDI=",
35-
"TuI0yJQvmNIs3J9pfzPMu2aYibKQ0MUpGgkcmjsS30g=",
36-
"TxcrLS66touilnjU30hIZ8JkbH6bfnBbD6pQ5OoIpXs=",
24+
"9X9+Ewnt1Jv3SVUsUbtRTb8QezxTGW71hfmYyj4urho=",
25+
"XMz5WH5iwOITJcBdVsAUj+h1uxgH8EPXezgGg4lNP5M=",
26+
"LLawJ9A3X4PEGMwFRwVbwhUxkHMJj9wFemKagvJlDFw=",
27+
"7PXuijLdBg+hW8HdjY+IyuFgLXvlTKOKqHRFTMJB4Ko=",
28+
"er0Z148pal4gYGSVfQ/rvzPKTc1EIDd7pYQjHd7RtKk=",
29+
"ZmLzbyYsyxNDvtbH/T00VHzMFhW8WBS9lgPH2rlNFMY=",
30+
"DH+I5x/ZvX3i2Ysc2divZ/6MK1e3ppSmNtUTg+CQwEw=",
31+
"nrCOZ+D41yjNI2zgMlBLN0LHOn5OP4sJYffOuoPgSJg=",
32+
"vQtr96qBr+8s4Up0YK2ibgbVOYyLK4e8zsjHOyMnOM4=",
33+
"4GdrZvm4dTLD8P8KPOQx8Op0UOT7XbRS1WG43uXseJU=",
34+
"fp4aY4dEhrMBmS4ex9s/lm8UPsk42eWg77zd+uJn0F4=",
35+
"2aaLz8XcvX3I3Ihft0W701fVKICZLYtBIxRbPmdkcZ0=",
3736
"sjohk/3DQIfXTgf/5XpwtdF7yNbrf8YykOMHr1CyBYQ=",
3837
"98enzMaC+x5oCMvIZQA5z8vu2apDMCFvE/935NfuPw8="
3938
],
4039
"checkpoint": {
41-
"envelope": "rekor.sigstore.dev - 2605736670972794746\n85405941\nCdYMKt8P8arrU1iilSLHbPDwMzhWgUbA6xvb1DSZktc=\n\n— rekor.sigstore.dev wNI9ajBGAiEAkEVEjIsNbuvKywuzjow1tyD1IkIpHu/bCVK73fSpBzECIQD5ctzIS0Rp3cC3PF0ZcFEP8ObC2KJqojg4hfKjFxxy9A==\n"
40+
"envelope": "rekor.sigstore.dev - 2605736670972794746\n86654770\nrpJ6l1p+pM8A+nMvMT80o0+qrCozIEfbG6qa0psYB0U=\n\n— rekor.sigstore.dev wNI9ajBFAiBXlxCjY0PMu4XUVJa/auC+EwEJws9xXfEbiYRM5uIjpgIhAKRduPCMlSRhNCQeGUifB2nAkKJDGlOJa75mkYYrrVMK\n"
4241
}
4342
},
44-
"canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJjNGU5MmU5ZWNjODI4YmVmMmFhN2RiYTFkZThhYzk4MzUxMWY3NTMyYTBkZjExYzc3MGQzOTA5OWEyNWNmMjAxIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJSGlXY2JRY2szTzE2K3dGR3pyR09sYWVWRkpodkNwT1EwajZJd0ZtUnp0YUFpQWJWL3NOSWg0OFJQSHdIdm9IZklDcFA3Y29seGpjbUx6WEhsU2pleGEvUVE9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTXhla05EUVd3eVowRjNTVUpCWjBsVldtczVWRzlIUmxWS1pYaDVLeTl5ZUhkSlJqaENRaXN6UXpWWmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFJkMDVFU1RWTlZGa3hUVlJOZWxkb1kwNU5hbEYzVGtSSk5VMVVZM2ROVkUxNlYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZOTlVnMVpqZEJORWgxZEZaVVprdEdhVzFVWkRKVllsUjZaMVZQV1RkeWNHZzVSMHNLY1dkeldqZERhRUZ3T0VaSFNtSnljbWR1Tm04cmJuQnlWVVZHUzNGRllVbHBLMlpYVVVwMlVpdFNTbXR2VVdOWFRVdFBRMEZZZDNkblowWTBUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZ1Y21NNUNtNUtNbVI0U21ReFlUVnpRMFpxTDFBcmVUTk5kV2hSZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDB4QldVUldVakJTUVZGSUwwSkRTWGRKU1VWbFdtMUdhbVJYTld0aWVUVXdaRmRXZWxreVJrRmtTRXBvWVZkNGRscHRTbkJrU0UxMVdUSTVkQXBOUTJ0SFEybHpSMEZSVVVKbk56aDNRVkZGUlVjeWFEQmtTRUo2VDJrNGRsbFhUbXBpTTFaMVpFaE5kVm95T1haYU1uaHNURzFPZG1KVVFYSkNaMjl5Q2tKblJVVkJXVTh2VFVGRlNVSkNNRTFITW1nd1pFaENlazlwT0haWlYwNXFZak5XZFdSSVRYVmFNamwyV2pKNGJFeHRUblppVkVOQ2FXZFpTMHQzV1VJS1FrRklWMlZSU1VWQloxSTRRa2h2UVdWQlFqSkJUakE1VFVkeVIzaDRSWGxaZUd0bFNFcHNiazUzUzJsVGJEWTBNMnA1ZEM4MFpVdGpiMEYyUzJVMlR3cEJRVUZDYW5seVJUQnpVVUZCUVZGRVFVVmpkMUpSU1doQlQxSm9VREZJWVVOalJEUlJTemdyT0ZaalRrdzBObGN3UVVGck5tTkpSRlZCU0ROVFZqUnpDblJLVlZaQmFVSmhabmwzSzBaVWNHZDJiMVJWS3pKVk4xRkRlV3BzVVZvMVNqSmtVSEJXY1hZNVZYQXpkbFl5UjFSRVFVdENaMmR4YUd0cVQxQlJVVVFLUVhkT2IwRkVRbXhCYWtWQmVtOUJOR05OU0hoSVEwVllRVGd3WVdoM1NsVlRlaTh4YTFsdmRGUllVazU2WlZkVk5qbFRlV0ZhUlRkUWJ5dDJXalV2UVFwT1prdDJZa04yT1hNeE9YSkJha0ZDWW5jdlNVNXJRVFJrUjB0WFJVUk9kRk5xYm14dlduVklOVTQ1WVZCQ1QxWTBNalVyYVV0RFpUSmliV1k1WTFsV0NteEdka05pUjIxSWFVVm5MM0kxYXowS0xTMHRMUzFGVGtRZ1EwVlNWRWxHU1VOQlZFVXRMUzB0TFFvPSJ9fX19"
43+
"canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJmMTQ2ZmY4NWMxMGZjMTg4ODM0MDVjMWQ5Mzc0NjIzZWI3YzI5ZjRlYTRiYTYyYzZmNWUyYzZmMTc5M2ZiZDEwIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUUMvd1ZYcTlBeWExQW1JZlgzZmVoSUZTbkN1Q0tzNGhWTks1eGJ3ckVtV1d3SWdHMzhtcmtsOHhmNG1SYWhmYmNIckVTdFZYYjg3enFySVFoT1BUOVJTcFdFPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTXhha05EUVd4NVowRjNTVUpCWjBsVlZFUk5XRWxJVFVkaVRrWXJVMjB3Y1c5UmIyb3pOV2haTTNwcmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFJkMDVVUVhwTlZGRjNUbFJWTUZkb1kwNU5hbEYzVGxSQmVrMVVVWGhPVkZVd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZ4ZGpKMmRrWkJSRFkyU1dSSFUyY3ZLMHBpUWk5dVdXWnZiMnN4Um5Gd2JVMDNOek1LYnpsTlpGWmFhM1JzTVV4clZWZEJWVGhUZW1GYWFGTnpieTgzY1ZaeWFYbElMMU00YTIwd1NIRlVWazE2ZFZvclUyRlBRMEZZYzNkblowWXpUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlUxVkZGMkNtODFOWEUxVDFoclZrWnRXVVJ6VWprelRtVm1abkV3ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDB4QldVUldVakJTUVZGSUwwSkRTWGRKU1VWbFdtMUdhbVJYTld0aWVUVXdaRmRXZWxreVJrRmtTRXBvWVZkNGRscHRTbkJrU0UxMVdUSTVkQXBOUTJ0SFEybHpSMEZSVVVKbk56aDNRVkZGUlVjeWFEQmtTRUo2VDJrNGRsbFhUbXBpTTFaMVpFaE5kVm95T1haYU1uaHNURzFPZG1KVVFYSkNaMjl5Q2tKblJVVkJXVTh2VFVGRlNVSkNNRTFITW1nd1pFaENlazlwT0haWlYwNXFZak5XZFdSSVRYVmFNamwyV2pKNGJFeHRUblppVkVOQ2FWRlpTMHQzV1VJS1FrRklWMlZSU1VWQloxSTNRa2hyUVdSM1FqRkJUakE1VFVkeVIzaDRSWGxaZUd0bFNFcHNiazUzUzJsVGJEWTBNMnA1ZEM4MFpVdGpiMEYyUzJVMlR3cEJRVUZDYW5vM1IyMHpiMEZCUVZGRVFVVlpkMUpCU1dkSGEwWndlRFZ4TTA1NmJXZENTVkI1ZDNselpFRkVSRkpTVWxCTkwzaHpZVGhHYTJaMllTdGpDbWhMUlVOSlFYVTFTR2RQTW1WTGMyUmpPWEJ2YUcxbmJpOXRiMlJXWTBveFVUVk5kVzkxT1dRMGJERmpOV1pZZVUxQmIwZERRM0ZIVTAwME9VSkJUVVFLUVRKblFVMUhWVU5OU0hOamFFRnVWM1E0T0ZjMFkzVXpOV1JGZGpCTlNqY3ljek5DV25OMVpGVlJlbG80WkhSbk1IaENiRVl6ZFhka1JHOXdjazVtWWdwQk1uUk5OWEJwUkd4QlNYaEJUVUpuTlVSeGVEQkNWaTg1VW5BdlVVMU1hR0lyWVVkeFdtMDNiamRGTlVkcldFcHdTRUU0VkhsVFdtRnRaSGxaZFZKc0NrVnBVR1kwWTJvM2VDOXlkWGwzUFQwS0xTMHRMUzFGVGtRZ1EwVlNWRWxHU1VOQlZFVXRMUzB0TFFvPSJ9fX19"
4544
}
4645
]
4746
},
48-
"message_signature": "MEQCIHiWcbQck3O16+wFGzrGOlaeVFJhvCpOQ0j6IwFmRztaAiAbV/sNIh48RPHwHvoHfICpP7colxjcmLzXHlSjexa/QQ=="
47+
"message_signature": "MEUCIQC/wVXq9Aya1AmIfX3fehIFSnCuCKs4hVNK5xbwrEmWWwIgG38mrkl8xf4mRahfbcHrEStVXb87zqrIQhOPT9RSpWE="
4948
}

0 commit comments

Comments
 (0)