---
title: digest.ecdsa_verify
summary: null
url: >-
  https://www.fastly.com/documentation/reference/vcl/functions/cryptographic/digest-ecdsa-verify
---

```
BOOL digest.ecdsa_verify(ID hash_method, STRING public_key, STRING payload, STRING digest, ID digest_format, ID base64_variant?)
```

**Available in:** all subroutines

Returns `true` if the ECDSA signature of `payload` using `public_key` matches
`digest`. Uses the NIST P-256 curve (also known as secp256r1 or prime256v1).

## Parameters

| Parameter        | Type   | Description                                                                                            |
| ---------------- | ------ | ------------------------------------------------------------------------------------------------------ |
| `hash_method`    | ID     | Hash algorithm: `sha256`, `sha384`, `sha512`, or `sha1`                                                |
| `public_key`     | STRING | ECDSA public key in PEM format (P-256 curve only)                                                      |
| `payload`        | STRING | The message that was signed (raw string, not encoded)                                                  |
| `digest`         | STRING | Base64-encoded signature to verify                                                                     |
| `digest_format`  | ID     | Signature encoding: `der` or `jwt`                                                                     |
| `base64_variant` | ID     | Optional. Base64 variant for decoding `digest`: `standard`, `url`, `url_nopad` (default), or `default` |

The `payload` parameter is the raw message to verify, not a Base64 or hex encoding of it.

Since VCL strings cannot contain NUL bytes, binary payloads are not supported. If you need to verify a signature over binary data, you must use a text-safe encoding on both the signing and verification sides.

## Supported curves

Only the NIST P-256 curve (also known as secp256r1 or prime256v1) is supported. Keys using other curves such as P-384 or P-521 will be rejected.

If an unsupported curve is detected at compile time (when the key is a literal), compilation fails with an error. If the key is loaded at runtime from a variable, the function returns `false`.

## Signature formats

The `digest_format` parameter specifies how the signature is encoded before Base64 encoding.

### DER format

When `digest_format` is `der`, the signature must be an ASN.1 DER-encoded ECDSA-Sig-Value structure containing the `r` and `s` integer values. This is the standard format produced by OpenSSL and most cryptographic libraries.

### JWT format

When `digest_format` is `jwt`, the signature must be the raw concatenation of `r` and `s` as fixed-size 32-byte big-endian integers, as specified in [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.3) for JSON Web Signatures.

The `jwt` format only supports `sha256` as the hash method, corresponding to the ES256 algorithm defined in [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-3.4). Using `sha384` or `sha512` with `jwt` format causes a compile-time error.

## Base64 variants

The `base64_variant` parameter controls how the signature (`digest` parameter) is decoded:

| Variant     | Alphabet            | Padding  | RFC                |
| ----------- | ------------------- | -------- | ------------------ |
| `standard`  | `A-Za-z0-9+/`       | Required | RFC 4648 Section 4 |
| `url`       | `A-Za-z0-9-_`       | Required | RFC 4648 Section 5 |
| `url_nopad` | `A-Za-z0-9-_`       | Optional | RFC 4648 Section 5 |
| `default`   | Same as `url_nopad` | Optional | —                  |

The default is `url_nopad`, which is appropriate for JWT signatures.

## Examples

### Verifying a DER-encoded signature

```vcl
declare local var.message STRING;
declare local var.signature STRING;
declare local var.verified BOOL;

set var.message = "SECRET-MESSAGE";
set var.signature = "MEQCIFcgO8nl-TYvHYAStNZaw4UNCOxe1xdb3xlV65F_vHMiAiAF6eqlsmoCJ3dE4mnOAKlMV-FIv5jGCY3uCR02HYIbAQ";

set var.verified = digest.ecdsa_verify(sha256, {"-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEs0kRbvRgu+Ej8oPCWXC1LwQDc/st
C4gWtOSMAcX7Q9L6Qn9gNVk//78zgXJ3trTQG+9fJe2C3YAVwKqqWdknzg==
-----END PUBLIC KEY-----"},
    var.message,
    var.signature,
    der,
    url_nopad);

if (var.verified) {
    set req.http.X-Verified = "true";
} else {
    error 403 "Invalid signature";
}
```

To generate your own test vectors, use OpenSSL:

```term
$ openssl ecparam -name prime256v1 -genkey -noout -out private.pem
$ openssl ec -in private.pem -pubout -out public.pem
$ echo -n "SECRET-MESSAGE" | openssl dgst -sha256 -sign private.pem | openssl base64 -A | tr '+/' '-_' | tr -d '='
```

### Verifying a JWT ES256 signature

A JWT consists of three Base64URL-encoded parts separated by dots: `header.payload.signature`. The ES256 signature is computed over the ASCII bytes of the header and payload parts joined by a dot.

This example verifies the following JWT:

```
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.LhwRiF6ApB2aYbLEbGFSBxZUGwiJC2rxELwPjAIwEAIoPY-Glv_RRMt87RBm53QNds6na3eSXatVDs7HOQRuzg
```

Decoded, the parts are:

- Header: `{"alg":"ES256","typ":"JWT"}`.
- Payload: `{"sub":"1234567890","name":"John Doe","admin":true,"iat":1516239022}`.
- Signature: 64 bytes (two 32-byte integers `r` and `s` concatenated).

```vcl
declare local var.jwt STRING;
declare local var.header_payload STRING;
declare local var.signature STRING;
declare local var.verified BOOL;

# Test JWT signed with the ES256 algorithm
set var.jwt = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.LhwRiF6ApB2aYbLEbGFSBxZUGwiJC2rxELwPjAIwEAIoPY-Glv_RRMt87RBm53QNds6na3eSXatVDs7HOQRuzg";

# Extract header.payload and signature from the JWT
if (var.jwt ~ "^([a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$") {
    set var.header_payload = re.group.1;
    set var.signature = re.group.2;
} else {
    error 401 "Malformed JWT";
}

# Verify the signature
set var.verified = digest.ecdsa_verify(sha256, {"-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL1fdMP0q4rFa+uTGgBds+eUjH3fH
mlfVwIhQ3yKQXL7PBzlPqLJ0eYDA9Ynq1LiNQpu6q2nu/PvID/ATE3C5Bg==
-----END PUBLIC KEY-----"},
    var.header_payload,
    var.signature,
    jwt,
    url_nopad);

if (var.verified) {
    set req.http.X-JWT-Valid = "true";
} else {
    error 401 "Invalid JWT signature";
}
```

When verifying JWTs from an `Authorization: Bearer` header:

```vcl
declare local var.header_payload STRING;
declare local var.signature STRING;

if (req.http.Authorization ~ "^Bearer ([a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$") {
    set var.header_payload = re.group.1;
    set var.signature = re.group.2;
} else {
    error 401 "Invalid Authorization header";
}

if (!digest.ecdsa_verify(sha256, {"-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL1fdMP0q4rFa+uTGgBds+eUjH3fH
mlfVwIhQ3yKQXL7PBzlPqLJ0eYDA9Ynq1LiNQpu6q2nu/PvID/ATE3C5Bg==
-----END PUBLIC KEY-----"},
    var.header_payload,
    var.signature,
    jwt,
    url_nopad))
{
    error 401 "Invalid JWT signature";
}
```

This example only verifies the cryptographic signature. You should also decode and validate the claims.

### Using a public key from a variable

Public keys can be loaded from an edge dictionary at runtime. Always verify that the lookup succeeded before using the key:

```vcl
declare local var.public_key STRING;
set var.public_key = table.lookup(es256_keys, "my-service");

if (var.public_key == "") {
    error 500 "Signing key not configured";
}

if (digest.ecdsa_verify(sha256, var.public_key, req.http.Message, req.http.Sig, der)) {
    set req.http.X-Signature-Valid = "true";
} else {
    error 403 "Invalid signature";
}
```

When the key is provided at runtime, invalid keys or unsupported curves cause the function to return `false` rather than a compile-time error.

### Algorithm security

Each signing key should be used with exactly one algorithm. Store ES256 keys separately from keys intended for other algorithms (RS256, HS256, etc.), and never select the algorithm based on untrusted input.

The `alg` header in a JWT is not signed and can be modified by an attacker. Never use it to select which verification function to call. Instead, determine the expected algorithm from context (the token type, the issuer, or the key identifier).

However, you can use the `alg` header as an early rejection path: if you expect ES256 and the token claims a different algorithm, reject it immediately without performing cryptographic verification:

```vcl
declare local var.public_key STRING;
declare local var.header STRING;
declare local var.header_payload STRING;
declare local var.signature STRING;

# Look up the ES256 key for this service - only ES256 keys are stored here
set var.public_key = table.lookup(es256_keys, "auth-service");
if (var.public_key == "") {
    error 500 "ES256 signing key not found";
}

# Parse the JWT into header, header.payload, and signature
if (req.http.Authorization ~ "^Bearer (([a-zA-Z0-9_-]+)\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$") {
    set var.header_payload = re.group.1;
    set var.header = re.group.2;
    set var.signature = re.group.3;
} else {
    error 401 "Invalid Authorization header";
}

# Early rejection: if the token claims a different algorithm, reject without crypto
if (digest.base64url_nopad_decode(var.header) !~ {""alg"\s*:\s*"ES256"}) {
    error 401 "Unsupported algorithm";
}

# Verify with ES256
if (!digest.ecdsa_verify(sha256, var.public_key, var.header_payload, var.signature, jwt)) {
    error 401 "Invalid signature";
}
```

## Errors

This function does not set `fastly.error`. Invalid inputs cause the function to return `false`:

- Invalid or malformed public key.
- Unsupported elliptic curve (when key is loaded at runtime).
- Invalid Base64 encoding in the signature.
- Invalid signature format (malformed DER or incorrect length for JWT).
- Signature does not match the payload.

## Related content

- `digest.rsa_verify()` - Verify RSA signatures.
- `digest.hmac_sha256()` - Compute HMAC-SHA256 for symmetric signature verification.
