facil.io

#P-256 (secp256r1)

c
#define FIO_P256
#include "fio-stl.h"

P-256 is the NIST secp256r1 curve used by TLS and certificate tooling. This module covers two jobs: ECDSA signatures over SHA-256 message hashes, and ECDH shared-secret creation.

Security note: this implementation has not been independently audited. Use it when the zero-dependency STL path matters; prefer a tested crypto library for long-lived keys, compliance, or high-value trust decisions.

#What It Provides

Use Function Notes
Sign fio_ecdsa_p256_sign Creates a DER-encoded ECDSA signature from a 32-byte SHA-256 hash.
Verify DER signature fio_ecdsa_p256_verify Accepts SEQUENCE { r INTEGER, s INTEGER } and a 65-byte uncompressed public key.
Verify raw signature fio_ecdsa_p256_verify_raw Accepts raw 32-byte r, s, x, and y values.
Key pair fio_p256_keypair Produces a 32-byte secret key and a 65-byte uncompressed public key.
Shared secret fio_p256_shared_secret Accepts compressed or uncompressed peer public keys and returns the x-coordinate.

The curve parameters are the standard P-256 parameters from NIST FIPS 186-4:

  • p = 2^256 - 2^224 + 2^192 + 2^96 - 1
  • n = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551
  • curve equation: y² = x³ - 3x + b (mod p)

#ECDSA Signatures

#fio_ecdsa_p256_sign

c
SFUNC int fio_ecdsa_p256_sign(uint8_t *sig,
                              size_t *sig_len,
                              size_t sig_capacity,
                              const uint8_t msg_hash[32],
                              const uint8_t secret_key[32]);

Signs a 32-byte SHA-256 message hash. The function writes a DER-encoded signature to sig and stores the actual length in sig_len.

  • sig_capacity must be at least 72 bytes.
  • secret_key is a 32-byte big-endian scalar and must be in the valid range 1..n-1.
  • The function generates a random ECDSA nonce internally and retries invalid nonces.
  • Returns 0 on success, -1 on invalid input or signing failure.

It signs the hash you give it; it does not hash the original message. No sneaky kitchen work here.

#fio_ecdsa_p256_verify

c
SFUNC int fio_ecdsa_p256_verify(const uint8_t *sig,
                                size_t sig_len,
                                const uint8_t *msg_hash,
                                const uint8_t *pubkey,
                                size_t pubkey_len);

Verifies a DER-encoded ECDSA signature against a 32-byte SHA-256 hash.

  • sig is a DER SEQUENCE { r INTEGER, s INTEGER }.
  • msg_hash is exactly 32 bytes.
  • pubkey must be an uncompressed P-256 public key: 0x04 || x || y.
  • pubkey_len must be 65.
  • Returns 0 for a valid signature, -1 for invalid input or a bad signature.

#fio_ecdsa_p256_verify_raw

c
SFUNC int fio_ecdsa_p256_verify_raw(const uint8_t r[32],
                                    const uint8_t s[32],
                                    const uint8_t msg_hash[32],
                                    const uint8_t pubkey_x[32],
                                    const uint8_t pubkey_y[32]);

Verifies a signature when r and s are already decoded. Public-key coordinates are 32-byte big-endian values.

The verifier checks that r and s are in range, checks the public point is on the P-256 curve, performs the ECDSA verification equation, and returns 0 only when the signature matches.

#ECDH Key Exchange

#fio_p256_keypair

c
SFUNC int fio_p256_keypair(uint8_t secret_key[32], uint8_t public_key[65]);

Generates a P-256 key pair.

  • secret_key receives a 32-byte scalar.
  • public_key receives 0x04 || x || y (65 bytes).
  • Returns 0 on success, -1 on bad arguments or random generation failure.

#fio_p256_shared_secret

c
SFUNC int fio_p256_shared_secret(uint8_t shared_secret[32],
                                 const uint8_t secret_key[32],
                                 const uint8_t *their_public_key,
                                 size_t their_public_key_len);

Computes a P-256 ECDH shared secret using your secret key and their public key.

  • secret_key is a 32-byte scalar in the valid range 1..n-1.
  • their_public_key may be uncompressed (65 bytes, 0x04 || x || y) or compressed (33 bytes, 0x02/0x03 || x).
  • The peer point is decompressed when needed and checked against the curve.
  • shared_secret receives the 32-byte x-coordinate of the result.
  • Returns 0 on success, -1 on invalid input, invalid point, point at infinity, or all-zero result.

Run the shared secret through a KDF such as HKDF before using it as an encryption key. Raw ECDH output is an ingredient, not dinner.

#Example

c
#define FIO_P256
#define FIO_SHA2
#include "fio-stl.h"

int main(void) {
  uint8_t secret_key[32];
  uint8_t public_key[65];
  if (fio_p256_keypair(secret_key, public_key))
    return 1;

  const char *message = "hello, p-256";
  fio_u256 hash = fio_sha256(message, strlen(message));

  uint8_t sig[72];
  size_t sig_len = 0;
  if (fio_ecdsa_p256_sign(sig, &sig_len, sizeof(sig), hash.u8, secret_key))
    return 1;

  if (fio_ecdsa_p256_verify(sig, sig_len, hash.u8, public_key, 65))
    return 1;

  return 0;
}