# POST /originality/read-sig/verify

Check the NXP Read_Sig originality signature of a chip, from its serial number and the signature read on the chip. Public endpoint, no API key.

Source: https://docs.sealtrust.io/en/reference/post-originality-read-sig-verify/

---

You send the serial number of a chip and the NXP Read_Sig originality
signature read on it, and the service returns a verdict.

## Authorization

None, public endpoint. This endpoint expects no API key, no session cookie, and
no `Authorization` header.

Call it from your server. A call issued by a browser page goes through two
additional checks, the origin of the request and the anti-forgery token, which
return 403 when they are not satisfied.

The check touches none of your records. No scan is recorded, no statistic is
fed, no notification is triggered.

## Call limit

No limit of its own for this endpoint. It comes under the general counter of
the API, counted per calling network address over a 60-second window.

This general counter is common to all the endpoints that have no dedicated
limit. Your calls here therefore eat into the same budget as your calls to
those other addresses. The `/v1` prefix does not create a second budget.

Every response carries three headers.

| Header | Content |
| --- | --- |
| `X-RateLimit-Limit` | the limit that the counter announces for the window |
| `X-RateLimit-Remaining` | what is left to you in the current window, floored at `0` |
| `X-RateLimit-Reset` | the timestamp of the end of the window, in seconds since January 1, 1970 |

Read `X-RateLimit-Remaining` and slow down before reaching zero. The value of
`X-RateLimit-Limit` can change without notice, so do not hard-code any number
in your code.

> [!ATTENTION] Plan for the 429 code in your client
> A call issued beyond the budget can receive a 429 code. Handle this case from
> your first integration: wait the duration in seconds given by the
> `Retry-After` header of the response, then replay the call. A client that
> does not handle the 429 stops dead the day it goes over the budget.

This endpoint consumes no quota of your plan. It requires neither an account
nor an API key, and it consults no plan.

## Path and query parameters

None. This endpoint has neither a path parameter nor a query parameter.
Everything goes through the request body.

## Request body

Send a JSON object with the `Content-Type: application/json` header.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `uid_hex` | `string` | yes | The serial number of the chip, in hexadecimal. It must be 7 or 10 bytes, that is 14 or 20 hexadecimal characters. |
| `signature_hex` | `string` | yes | The signature read on the chip, in hexadecimal, in the raw form `r` followed by `s`. Its expected length depends on the curve: 56 bytes for P-224, 64 bytes for P-256, that is 112 or 128 hexadecimal characters. |
| `public_key_hex` | `string` | no | The public key with which to check the signature, in hexadecimal, in uncompressed SEC1 format: `04` followed by the X coordinate then the Y coordinate. It must be 57 bytes for P-224 or 65 bytes for P-256, and describe a real point on the curve. Field absent or empty string: the check is made with the reference NXP originality public key chosen by the service. The `public_key_used` field of the response tells you which one was used. |

No other field is accepted. An unknown field makes the request fail with a 422,
and no field is ignored silently.

### What the service cleans up before reading

On the three values, the service removes the leading and trailing spaces,
brings uppercase down to lowercase, deletes colons and internal spaces, then
removes a `0x` prefix if it finds one.

Colons and spaces are therefore the only two separators tolerated. Any other
separator, hyphen or period, must be removed by you before sending: a serial
number written `04-1a-2b-3c-4d-5e-6f` is refused with a 422.

### It is the public key that sets the curve

The service deduces the curve from the length of the checking public key: 57
bytes give P-224, 65 bytes give P-256. You have no curve parameter to send, and
the response tells you the curve retained.

The expected signature length follows from it. A signature of 64 bytes checked
with a P-224 key is refused with a 422, before any computation.

## Example request

Full address:

```http
POST https://api.sealtrust.io/v1/originality/read-sig/verify
```

The same endpoint also replies without the `/v1` prefix, at
`https://api.sealtrust.io/originality/read-sig/verify`. The two addresses call
the same code. Use the `/v1` form for a new integration.

:::onglets
```bash title="curl"
curl -i -X POST https://api.sealtrust.io/v1/originality/read-sig/verify \
  -H "Content-Type: application/json" \
  -d '{
    "uid_hex": "04000000000000",
    "signature_hex": "0000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001",
    "public_key_hex": "04b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34"
  }'
```
```typescript
const response = await fetch(
  "https://api.sealtrust.io/v1/originality/read-sig/verify",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      uid_hex: "04000000000000",
      signature_hex:
        "0000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001",
      public_key_hex:
        "04b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34",
    }),
  },
);

console.log(response.status);
console.log(response.headers.get("X-RateLimit-Remaining"));
console.log(await response.json());
```
```python
import requests

response = requests.post(
    "https://api.sealtrust.io/v1/originality/read-sig/verify",
    json={
        "uid_hex": "04000000000000",
        "signature_hex": "0000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001",
        "public_key_hex": "04b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34",
    },
    timeout=30,
)

print(response.status_code)
print(response.headers["X-RateLimit-Remaining"])
print(response.json())
```
:::

These values are invented. The public key of the example is the generator point
of the P-224 curve, a constant published in the standard that describes this
curve. It describes a real point on the curve, so the call goes all the way to
the cryptographic check. It is the key of no manufacturer.

Copied as they are, these three values give a 200 code with
`valid` at `false`, shown below. A positive verdict requires a signature
actually read on a chip and the public key of the manufacturer that signed it.

> [!INFO] The TypeScript SDK does not cover this endpoint
> The `@sealtrust-io/sdk` package exposes no method for the originality
> signature. This check is therefore called over direct HTTP, as above.

## Example response

HTTP code 200. This is the exact response for the values of the example above.

```json
{
  "valid": false,
  "curve": "secp224r1",
  "uid_hex": "04000000000000",
  "signature_hex": "0000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001",
  "error": "signature invalide",
  "public_key_used": "04b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34"
}
```

A negative verdict therefore comes out as a 200, like a positive verdict. On a
positive verdict, the same response comes back with `valid` at `true` and
`error` at `null`. The four other fields are identical.

The six fields of the response.

| Field | Type | Description |
| --- | --- | --- |
| `valid` | `boolean` | The verdict. It is the only field on which to build your logic. |
| `curve` | `string` | The curve deduced from the public key used. Two possible values: `secp224r1` for P-224, `secp256r1` for P-256. Filled in on a positive verdict as well as on a negative one. |
| `uid_hex` | `string` | The serial number that you sent, cleaned up and in lowercase, without the `0x` prefix and without a separator. |
| `signature_hex` | `string` | The signature that you sent, cleaned up the same way. |
| `error` | `string` or `null` | `null` on a positive verdict. On a negative verdict, the value is `signature invalide`. Base your decisions on `valid` and treat this text as a display message. |
| `public_key_used` | `string` | The public key that was used for the check, cleaned up and in lowercase. It is the one that you sent, or the reference NXP originality key chosen by the service when you send none. This field tells you what the verdict was returned against. |

A 200 code means that the check could be carried through to the end. It does
not mean that the signature is good. Always read `valid`.

> [!ATTENTION] This check says nothing about your item
> This endpoint does one single thing: it verifies a signature with a public
> key. It consults none of our records. A positive verdict tells you that the
> signature does match this serial number for this public key. It tells you
> neither that the chip was placed on one of your items, nor that the item
> exists on our side, nor that it is authentic. The verification of an item is
> done elsewhere.

## Errors

Every error response has the same shape: a JSON object with a `detail` field.

| Code | Condition | What to do |
| --- | --- | --- |
| 403 | The call carries a session cookie and has neither an `Origin` header nor a `Referer` header. `detail` is `Origin or Referer header required`. | Call this endpoint from your server, without a session cookie. |
| 403 | The `Origin` or `Referer` header names a site that is not in the allowed list. `detail` is `Forbidden origin`. | Call this endpoint from your server. A direct call from the page of a third party is refused. |
| 403 | The call carries a session cookie, and the anti-forgery token of the header does not match the one of the cookie. `detail` is `bad_csrf`. | Call this endpoint from your server, without a session cookie. |
| 422 | A required field is missing, a field is not a string of characters, or you sent a field that does not exist. `detail` is then a list that names each field at fault and the reason for the refusal. | Correct the body of the request. Only `uid_hex`, `signature_hex` and `public_key_hex` are accepted. |
| 422 | `uid_hex` is not readable hexadecimal. `detail` is `Invalid uid_hex`. | Check that the value contains only digits and the letters `a` to `f`, and that it has an even number of characters. |
| 422 | `uid_hex` is readable but is neither 7 nor 10 bytes. `detail` is `uid_hex doit faire 7 ou 10 octets`. | Send 14 or 20 hexadecimal characters. A serial number truncated or padded with zeros is refused. |
| 422 | `public_key_hex` is not readable, does not have a length of 57 or 65 bytes, does not start with `04`, or does not describe a real point on the curve. `detail` starts with `public_key_hex invalide`. | Take the public key back from its source, in uncompressed SEC1 format, and send it whole. |
| 422 | `signature_hex` is not readable hexadecimal. `detail` is `Invalid signature_hex`. | Same check as for `uid_hex`: hexadecimal characters only, even number of characters. |
| 422 | `signature_hex` is readable but its length does not match the curve of the public key. `detail` is `Invalid signature format`. | Send 56 bytes with a P-224 key, 64 bytes with a P-256 key. If your signature is in DER format, convert it to `r` followed by `s` before sending it. |
| 429 | Too many calls from your network address. The response carries the `Retry-After` header in addition, in seconds. | Wait the duration in seconds given by `Retry-After`, then replay the call. Read `X-RateLimit-Remaining` to slow down before getting there. |
| 500 | Unexpected error on the server. Fixed body `{"detail": "Internal Server Error"}`. | Try again. The `X-Request-Id` header identifies the call, send it to us if it repeats. |

> [!INFO] The order of the checks
> The checks follow one another in this order: shape of the request body,
> readability of `uid_hex`, length of the serial number, loading of the public
> key, readability of `signature_hex`, length of the signature, then
> cryptographic check. A refused public key therefore stops the call before the
> signature is examined, and a 422 on the key says nothing about the validity
> of your signature.

## See also

- [Physical identification, QR and NFC](/en/identification-physique/),
  choose the physical carrier and the exact form of the GS1 Digital Link.
- [Engrave and encode NFC seals](/en/gravure-sceaux-nfc/),
  prepare a batch, engrave each chip and check the result.
- [API errors](/en/api-erreurs/),
  recognize an error code and decide whether to correct or to replay.
