Internals and Technical Details for PEP 740 on PyPI

This page documents some of the internals and technical details behind PyPI’s implementation of PEP 740.

Important

If you’re a user of PyPI, you probably want the attestation user docs instead.

Signing identities

A signing identity is a stable, human-readable identifier associated with a public key. This identifier is used to perform semantic mappings for the purpose of verification, e.g. to say “Alice signed for foo,” rather than “Key 0x1234... signed for foo.”

In traditional signing schemes, this is typically a “key identifier,” such as a truncated hash of the key itself. In X.509-based PKIs it can be the certificate’s subject or other identifying material (such as a domain name or email address).

As specified in PEP 740, signing identities for attestations are Trusted Publisher identities. In practice, this means that the identity expected to sign a distribution’s attestation is expected to match the Trusted Publisher that published the package.

For example, for a GitHub-based Trusted Publisher, the identity might be https://github.com/pypa/sampleproject/blob/main/.github/workflows/release.yml, i.e. pypa/sampleproject on GitHub, publishing from a workflow defined on the main branch in the file release.yml.

Attestation types

The “scope” of the signing identity varies with the different attestation types that can be uploaded to PyPI.

PyPI Publish Attestation

A PyPI Publish Attestation is intended to attest to the Trusted Publisher itself. Therefore, the identity used is exactly the identity of the Trusted Publisher itself.

For example, using the GitHub-based Trusted Publisher above, the expected signing identity will be exactly https://github.com/pypa/sampleproject/blob/main/.github/workflows/release.yml.

SLSA Provenance

SLSA Provenance is intended to more generally trace a software artifact back to its source.

Because of this, the identity used to verify a SLSA Provenance attestation is slightly looser than for a PyPI Publish Attestation: any identity under https://github.com/pypa/sampleproject is accepted, not just ones corresponding to the release.yml workflow.

This is intended to reflect common CI/CD pipeline patterns: release.yml is not itself necessarily responsible for producing the distribution that gets published, and so SLSA Provenance can’t be assumed to be tightly bound to it.

Consequently, downstream consumers/verifiers of SLSA Provenance attestations may wish to further evaluate the attestation payload and signing identity on a local policy basis.

Attestation object internals

This section is intended as a high-level walkthrough of a PEP 740 attestation object.

First: here is our contrived attestation object, which we’ve pulled from a release of sampleproject:

http GET https://pypi.org/integrity/sampleproject/v4.0.0/sampleproject-4.0.0-py3-none-any.whl/provenance Accept:application/json \
  | jq '.attestation_bundles[0].attestations[0]'

yields:

{
  "envelope": {
    "signature": "MEQCIHAIF5F/e7GC6Ks9xmhP4JZcIOhLiX+tPXlD7wTPsCSVAiAPYs6cCAXYMZ3FqSlxfQ3Fx1GyrzqHawW+TaBUgRHu8A==",
    "statement": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoic2FtcGxlcHJvamVjdC00LjAuMC1weTMtbm9uZS1hbnkud2hsIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImMyM2U0NDdlYTkwZDc5NmQxZTY0NWMzNWM0YjJkZTEyNTA0MGFkZDEyYTg0NTgyNTU0NmY5MWM5M2YzOTFiNmIifX1dLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9kb2NzLnB5cGkub3JnL2F0dGVzdGF0aW9ucy9wdWJsaXNoL3YxIiwicHJlZGljYXRlIjpudWxsfQ=="
  },
  "verification_material": {
    "certificate": "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",
    "transparency_entries": [
      {
        "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiMDMyYzUwMGI4MjYzY2U0ZDg2ZTA4ZWEzMWEyZDY4NzZjZGI5YjQ5Yzg4MDUyZGM2OTYxNTk4NmQxMzQ0NzY4MyJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjE3NTYxNzdmZDZlZTI1YjQxMjM4NjdmN2MyZTkyMzRlYWQ0NDU1MGRiYmRiMjU5Yjk0ZTllYjRiNzVmZDRkNWQifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVRQ0lIQUlGNUYvZTdHQzZLczl4bWhQNEpaY0lPaExpWCt0UFhsRDd3VFBzQ1NWQWlBUFlzNmNDQVhZTVozRnFTbHhmUTNGeDFHeXJ6cUhhd1crVGFCVWdSSHU4QT09IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VkdlZFTkRRbWxwWjBGM1NVSkJaMGxVUm1GcEsxQkVTMkZyTVhoQk1VaE1jVEJ0YzJ0eGFFUldOWHBCUzBKblozRm9hMnBQVUZGUlJFRjZRVE1LVFZKVmQwVjNXVVJXVVZGTFJYZDRlbUZYWkhwa1J6bDVXbE0xYTFwWVdYaElha0ZqUW1kT1ZrSkJUVlJHV0U1d1dqTk9NR0l6U214TVYyeDFaRWRXZVFwaVYxWnJZVmRHTUZwVVFXVkdkekI1VGtSRmVFMUVXWGxOYWswelRVUmtZVVozTUhsT1JFVjRUVVJaZVUxcVVUTk5SR1JoVFVGQmQxZFVRVlJDWjJOeENtaHJhazlRVVVsQ1FtZG5jV2hyYWs5UVVVMUNRbmRPUTBGQlVtSjRNVVp6WlRKTWJqQXdUMjQxWVVaaFRDdHNTRTVIUmxsTVlYRmxTMFJrZFhCc1drUUtVRXBUSzNjeVVHcFpaazVRVERCbkwyNDBjMFJYUlZGR1dtWjVTVVY0UlZkTGRXeGFNa2RMVG5wQll6QXJVMjFWYnpSSlJsTkVRME5DVlZGM1JHZFpSQXBXVWpCUVFWRklMMEpCVVVSQloyVkJUVUpOUjBFeFZXUktVVkZOVFVGdlIwTkRjMGRCVVZWR1FuZE5SRTFDTUVkQk1WVmtSR2RSVjBKQ1ZDOTFVMFZKQ2xodFVYcDFVbXR3Y0ZkWWNsUkxWbXRtV2taS1lucEJaa0puVGxaSVUwMUZSMFJCVjJkQ1ZHWXdLMjVRVm1sUlVteDJiVzh5VDJ0dlZtRk1SMHhvYUdzS1VIcENhRUpuVGxaSVVrVkNRV1k0UlZaNlFsWm9iRTV2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZMGhzZDFsVE9YcFpWekYzWWtkV2R3cGpiVGx4V2xkT01FeDVOVzVoV0ZKdlpGZEpkbVF5T1hsaE1scHpZak5rZWt3elNteGlSMVpvWXpKVmRXVlhNWE5SU0Vwc1dtNU5kbUZIVm1oYVNFMTJDbUpYUm5CaWFrRTFRbWR2Y2tKblJVVkJXVTh2VFVGRlFrSkRkRzlrU0ZKM1kzcHZka3d6VW5aaE1sWjFURzFHYW1SSGJIWmliazExV2pKc01HRklWbWtLWkZoT2JHTnRUblppYmxKc1ltNVJkVmt5T1hSTlFrbEhRMmx6UjBGUlVVSm5OemgzUVZGSlJVSklRakZqTW1kM1RtZFpTMHQzV1VKQ1FVZEVkbnBCUWdwQmQxRnZUbXBKZUZwVVVUVk9lbEpxV1ZSSk1Wa3lWVEZOZWtVelRucE9hMXBYV1RGUFJGcHBXVlJPYkZwRWFHeE9lazB5V1dwT2JWbDZRVlpDWjI5eUNrSm5SVVZCV1U4dlRVRkZSVUpCWkZOYVYzaHNXVmhPYkUxRFFVZERhWE5IUVZGUlFtYzNPSGRCVVZWRlJXNUNOV05IUlhaak1rWjBZMGQ0YkdOSVNuWUtZVzFXYW1SRVFXUkNaMjl5UW1kRlJVRlpUeTlOUVVWSFFrRTVlVnBYV25wTU1taHNXVmRTZWt3eU1XaGhWelIzVDNkWlMwdDNXVUpDUVVkRWRucEJRZ3BEUVZGMFJFTjBiMlJJVW5kamVtOTJURE5TZG1FeVZuVk1iVVpxWkVkc2RtSnVUWFZhTW13d1lVaFdhV1JZVG14amJVNTJZbTVTYkdKdVVYVlpNamwwQ2sxSFRVZERhWE5IUVZGUlFtYzNPSGRCVVd0RlZsRjRWR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRFd3pRalZqUjBWMll6SkdkR05IZUd3S1kwaEtkbUZ0Vm1wa1F6aDFXakpzTUdGSVZtbE1NMlIyWTIxMGJXSkhPVE5qZVRsNVdsZDRiRmxZVG14TWJteDBZa1ZDZVZwWFducE1NbWhzV1ZkU2VncE1NakZvWVZjMGQwOUJXVXRMZDFsQ1FrRkhSSFo2UVVKRFoxRnhSRU5uTWsxcVJteE9SR3N6VGtkT2FFMXFWbXBhVkZWNlRWUmpNMDB5VW14YWFsVTBDazV0U21oTk1sWnJUMGRWTTAxNldtbE5NbHBxVFVJd1IwTnBjMGRCVVZGQ1p6YzRkMEZSYzBWRWQzZE9XakpzTUdGSVZtbE1WMmgyWXpOU2JGcEVRVEVLUW1kdmNrSm5SVVZCV1U4dlRVRkZUVUpEWTAxS1YyZ3daRWhDZWs5cE9IWmFNbXd3WVVoV2FVeHRUblppVXpsM1pWaENhRXd6VG1oaVdFSnpXbGhDZVFwaU1uQnNXVE5SZDA5QldVdExkMWxDUWtGSFJIWjZRVUpFVVZGeFJFTm5NazFxUm14T1JHc3pUa2RPYUUxcVZtcGFWRlY2VFZSak0wMHlVbXhhYWxVMENrNXRTbWhOTWxaclQwZFZNMDE2V21sTk1scHFUVUk0UjBOcGMwZEJVVkZDWnpjNGQwRlJORVZGVVhkUVkyMVdiV041T1c5YVYwWnJZM2s1ZEZsWGJIVUtUVUpuUjBOcGMwZEJVVkZDWnpjNGQwRlJPRVZEWjNkSlRWUlJORTlVYXpGUFZGbDNTbmRaUzB0M1dVSkNRVWRFZG5wQlFrVkJVVnBFUW1SdlpFaFNkd3BqZW05MlRESmtjR1JIYURGWmFUVnFZakl3ZG1OSWJIZFpWRUZYUW1kdmNrSm5SVVZCV1U4dlRVRkZVa0pCWjAxQ2Fsa3dUbnBCZVU1VVFtcENaMjl5Q2tKblJVVkJXVTh2VFVGRlUwSkdWVTFWTW1nd1pFaENlazlwT0haYU1td3dZVWhXYVV4dFRuWmlVemwzWlZoQ2FFd3pUbWhpV0VKeldsaENlV0l5Y0d3S1dUTlJka3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRtTnRWbk5hVjBaNldsTTFOV0pYZUVGamJWWnRZM2s1YjFwWFJtdGplVGwwV1Zkc2RRcE5SR2RIUTJselIwRlJVVUpuTnpoM1FWSk5SVXRuZDI5T2FrbDRXbFJSTlU1NlVtcFpWRWt4V1RKVk1VMTZSVE5PZWs1cldsZFpNVTlFV21sWlZFNXNDbHBFYUd4T2VrMHlXV3BPYlZsNlFWVkNaMjl5UW1kRlJVRlpUeTlOUVVWVlFrRlpUVUpJUWpGak1tZDNWMUZaUzB0M1dVSkNRVWRFZG5wQlFrWlJVa3dLUkVWc2IyUklVbmRqZW05MlRESmtjR1JIYURGWmFUVnFZakl3ZG1OSWJIZFpVemw2V1ZjeGQySkhWbmRqYlRseFdsZE9NRXd5Um1wa1IyeDJZbTVOZGdwamJsWjFZM2s0ZUUxVVkzaE5la0Y2VDBSck5FMVRPV2hrU0ZKc1lsaENNR041T0hoTlFsbEhRMmx6UjBGUlVVSm5OemgzUVZKWlJVTkJkMGRqU0ZacENtSkhiR3BOU1VkTFFtZHZja0puUlVWQlpGbzFRV2RSUTBKSWQwVmxaMEkwUVVoWlFUTlVNSGRoYzJKSVJWUktha2RTTkdOdFYyTXpRWEZLUzFoeWFtVUtVRXN6TDJnMGNIbG5Remh3TjI4MFFVRkJSMVJCTlM5WU5VRkJRVUpCVFVGU2VrSkdRV2xCTm01WlN6QkhlSEZXZWtwMWRISnFjbGxCTVdKQlNVdElWUXBxUjNKelNFMU1jazlLVkZSRlZXbEZVa0ZKYUVGS1dtOTBRVlJ1VTNkc1MzUTNRek5hZDJoNE0yWmpVM0pvUjJaUFlXdFViRTB5ZHlzNGNXMXNkR05xQ2sxQmIwZERRM0ZIVTAwME9VSkJUVVJCTW1OQlRVZFJRMDFDSzJsc2MxQm5lVFI1YmxWSE9VZDBjVVJGUW5GWE9DdGFjV3BZTmt4d2RYaFJjV3BEY2pjS2N6UjVkSGwwTW5Cd1JtUm5hbkpIY2tjeFJGazBibE5hZEZGSmQySnNjbWR4T1hRNWFYcEJUVlJyU21WeGFGRkNjekpQVldsNVNVcGFhWEJqWlVRMWRncEJRVVV2VG1ablpDODVkVXN3VFZwQlNFWnpUR2RoYkhGUFFtdzRDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsifV19fQ==",
        "inclusionPromise": {
          "signedEntryTimestamp": "MEQCIF/N/GzwLypgHSlaRpDtl6oTZ4cmviE++Z+aY5ksSWKWAiAlenzSiy6/zvFAo44EJSvvXPp8P+YiKZUxhaQPoVP5Wg=="
        },
        "inclusionProof": {
          "checkpoint": {
            "envelope": "rekor.sigstore.dev - 1193050959916656506\n25232885\nwfIuS5NLOf+4rU8wVjPaezQYEVVpf3aF1G/BfRYMXew=\n\n— rekor.sigstore.dev wNI9ajBFAiAj+8BDcU0CKq9AJ1uOND6fCQ/ugLsk1xnSz0IpXoaE+AIhALUXqsTZ40Mt2X30WNlk6baivF1KA4V4rrjbPNVo9eFC\n"
          },
          "hashes": [
            "4bt58suSLj7v+PP3+G6iSxOJV7xu75I78Fh9SZAVbho=",
            "VzJk3yFgaaO7bC/HxvHYPX2g22PiTWKDf0afdGrvceY=",
            "nLzU/ukEW1eoGR2I2UulWDBG6VLtYrA7rNJnei8kH8s=",
            "S182UV88MERSxCgUSBcfhCHJDuyUrAIs/fFmCbpjWgg=",
            "PWqRmPYAwa1fq6R1qSrYlOxCtiKnFZq9hnNt7XwCIA8=",
            "KHxYP0XNSf1yKjp+xY/5Kkckw0Yweyjx9Z6qn2+pnZM=",
            "8/b9kmTAbALhl4EaKIH4uMXhES9ILB0XQkuH44FltJY=",
            "mXfX9NDkaWje6HpniWis2CBELUGjv8LiW2jeMOclCs0=",
            "jRPOva2IEma7ZE7mPN3xHtEnXtMF/HNvrmbC5TKTy14=",
            "s8vUdxeRlxXWTCMdSLhiSzRiYM3eGsVvrm+5HWkTNBc=",
            "4lUF0YOu9XkIDXKXA0wMSzd6VeDY3TZAgmoOeWmS2+Y=",
            "gf+9m552B3PnkWnO0o4KdVvjcT3WVHLrCbf1DoVYKFw="
          ],
          "logIndex": "25232882",
          "rootHash": "wfIuS5NLOf+4rU8wVjPaezQYEVVpf3aF1G/BfRYMXew=",
          "treeSize": "25232885"
        },
        "integratedTime": "1730932628",
        "kindVersion": {
          "kind": "dsse",
          "version": "0.0.1"
        },
        "logId": {
          "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="
        },
        "logIndex": "147137144"
      }
    ]
  },
  "version": 1
}

Verification material

The verification_material conveys the materials used the verify the attestation.

The certificate is the most relevant field: it’s a base64-encoded DER X.509 certificate, which we can inspect as follows:

# put the JSON above in /tmp/attestation.json
jq -r .verification_material.certificate < /tmp/attestation.json \
  | base64 -d \
  | openssl x509 -inform DER -text -noout

producing (abbreviated for clarity):

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            15:a8:be:3c:32:9a:93:5c:40:d4:72:ea:d2:6b:24:aa:10:d5:e7
        Signature Algorithm: ecdsa-with-SHA384
        Issuer: O=sigstore.dev, CN=sigstore-intermediate
        Validity
            Not Before: Nov  6 22:37:07 2024 GMT
            Not After : Nov  6 22:47:07 2024 GMT
        Subject:
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    ...
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage:
                Code Signing
            X509v3 Subject Key Identifier:
                FF:B9:21:08:5E:64:33:B9:19:29:A5:65:EB:4C:A5:64:7D:91:49:6F
            X509v3 Authority Key Identifier:
                DF:D3:E9:CF:56:24:11:96:F9:A8:D8:E9:28:55:A2:C6:2E:18:64:3F
            X509v3 Subject Alternative Name: critical
                URI:https://github.com/pypa/sampleproject/.github/workflows/release.yml@refs/heads/main
            1.3.6.1.4.1.57264.1.1:
                https://token.actions.githubusercontent.com
    Signature Algorithm: ecdsa-with-SHA384
    Signature Value:
        30:64:02:30:1f:a2:96:c3:e0:cb:8c:a7:50:6f:46:b6:a0:c4:
        06:a5:bc:f9:9a:a3:5f:a2:e9:bb:14:2a:8c:2a:fb:b3:8c:ad:
        ca:dd:a9:a4:57:60:8e:b1:ab:1b:50:d8:e2:74:99:b5:02:30:
        6e:5a:e0:ab:db:7d:8b:30:0c:4e:42:5e:aa:14:01:b3:63:94:
        8b:22:09:66:2a:5c:78:3e:6f:00:01:3f:35:f8:1d:ff:db:8a:
        d0:c6:40:1c:5b:0b:81:a9:6a:38:19:7c

In this case, we can see that the certificate binds a public key to an identity (https://github.com/pypa/sampleproject/.github/workflows/release.yml@refs/heads/main), which is verified against the project’s registered Trusted Publishers at upload time.

Envelope

The envelope key contains two components:

  • The statement, which contains the core, signed-over in-toto Statement:

    jq -r .envelope.statement < /tmp/attestation.json | base64 -d | jq
    

    yielding:

    {
      "_type": "https://in-toto.io/Statement/v1",
      "subject": [
        {
          "name": "sampleproject-4.0.0-py3-none-any.whl",
          "digest": {
            "sha256": "c23e447ea90d796d1e645c35c4b2de125040add12a845825546f91c93f391b6b"
          }
        }
      ],
      "predicateType": "https://docs.pypi.org/attestations/publish/v1",
      "predicate": null
    }
    
  • The signature, which contains the base64-encoded signature over statement.

    The signature can be verified using the public key bound within verification_material.certificate, fully linking the attestation back to the identity that produced it.

    The signing process itself is not “bare”: instead of directly signing over statement, the payload is computed using the DSSE PAE encoding:

    SIGNATURE = Sign(PAE(UTF8(PAYLOAD_TYPE), SERIALIZED_BODY))
    

    where:

    • PAYLOAD_TYPE is fixed as application/vnd.in-toto+json

    • SERIALIZED_BODY is the JSON-encoded statement, per above

    • PAE is the “pre-authentication encoding”, defined as:

      PAE(type, body) = "DSSEv1" + SP + LEN(type) + SP + type + SP + LEN(body) + SP + body
      +               = concatenation
      SP              = ASCII space [0x20]
      "DSSEv1"        = ASCII [0x44, 0x53, 0x53, 0x45, 0x76, 0x31]
      LEN(s)          = ASCII decimal encoding of the byte length of s, with no leading zeros
      

    Thus, the actual signed-over payload roughly resembles:

    DSSEv1 28 application/vnd.in-toto+json 272 {"_type":"https://in-toto.io/Statement/v1","subject":[{"name":"pypi_attestation_models-0.0.4a2.tar.gz","digest":{"sha256":"c9709ce6fd5b67b59b4a28758cf14d3f411803c4b89b6068b1f1a8e4ee94c8ef"}}],"predicateType":"https://docs.pypi.org/attestations/publish/v1","predicate":{}}
    

“Why is the predicate empty?”

You may have noticed that the in-toto Statement above contains a predicate of type https://docs.pypi.org/attestations/publish/v1, but with an empty predicate body ({}).

This is intentional! A publish attestation does not require a custom predicate, since all of the state associated with a Trusted Publisher is fully encapsulated in the verification_material.certificate being used to verify the envelope.statement’s signature.

Verifying an attestation object

Attestation object verification is described at a high level in PEP 740.

Warning

Users are strongly discouraged from implementing the steps below in an ad-hoc manner, since they involve error-prone X.509 and transparency log operations. Instead, we strongly encourage integrators to use either pypi-attestation-models or sigstore-python’s pre-existing APIs for attestation manipulation, signing, and verification.

Using the details above, we can provide the steps with slightly more accuracy:

  1. Retrieve the distribution (sdist or wheel) being verified and its attestation. We’ll call these sampleproject-4.0.0.tar.gz and sampleproject-4.0.0.tar.gz.publish.attestation, respectively.

  2. Verify that the attestation’s verification_material.certificate is valid and chains up to the expected root of trust (i.e., the Sigstore public good instance) and has the expected subject (i.e., the subject matches a valid Trusted Publisher for project sampleproject).

    Note

    The “expected subject” is the expected signing identity, which the verifier must establish trust in. For example, depending on the security model, the verifier could either establish a priori that a given CI/CD identity is responsible for publishing a given package, or could perform a TOFU-style setup where the first identity associated with the package is considered the trusted one.

    Note

    This step is equivalent to Sigstore “bundle” verification and also requires a source of signed time, such as the verification_material.transparency_entries.

  3. Verify that the attestation’s envelope.signature is valid for envelope.statement, using the DSSE PAE encoding and the public key of verification_material.certificate.

  4. Decode the envelope.statement, verify that it’s an in-toto Statement with the expected subject (sampleproject-4.0.0.tar.gz) and subject digest (the SHA-256 of sampleproject-4.0.0.tar.gz’s contents).

  5. Confirm that the statement’s payloadType is one of the attestation types supported by PyPI, and perform any payload-specific processing. For the PyPI Publish attestation, no payload is present, and therefore no additional processing is necessary.

If any of the steps above fail, the attestation should be considered invalid and any operations on its associated distribution should halt.