Skip to content

Public key signatures

Frank Denis edited this page May 22, 2019 · 6 revisions

Public-key signatures

Single-part signature example

#define CONTEXT "Example"
#define MESSAGE "Test"
#define MESSAGE_LEN 4

hydro_sign_keypair key_pair;
hydro_sign_keygen(&key_pair);

uint8_t signature[hydro_sign_BYTES];

/* Sign the message using the secret key */
hydro_sign_create(signature, MESSAGE, MESSAGE_LEN, CONTEXT, key_pair.sk);

/* Verify the signature using the public key */
if (hydro_sign_verify(signature, MESSAGE, MESSAGE_LEN, CONTEXT, key_pair.pk) != 0) {
    /* forged */
}

Multi-part signature example

#define CONTEXT "Example"
#define MESSAGE_PART1 "first chunk"
#define MESSAGE_PART2 "second chunk"
#define MESSAGE_PART1_LEN 11
#define MESSAGE_PART2_LEN 12

hydro_sign_keypair key_pair;
hydro_sign_keygen(&key_pair);

uint8_t signature[hydro_sign_BYTES];
hydro_sign_state st;

/* Sign the message using the secret key */
hydro_sign_init(&st, CONTEXT);
hydro_sign_update(&st, MESSAGE_PART1, MESSAGE_PART1_LEN);
hydro_sign_update(&st, MESSAGE_PART2, MESSAGE_PART2_LEN);
hydro_sign_final_create(&st, signature, key_pair.sk);

/* Verify the signature using the public key */
hydro_sign_init(&st, CONTEXT);
hydro_sign_update(&st, MESSAGE_PART1, MESSAGE_PART1_LEN);
hydro_sign_update(&st, MESSAGE_PART2, MESSAGE_PART2_LEN);
if (hydro_sign_final_verify(&st, signature, key_pair.pk) != 0) {
    /* forged */
}

Purpose

In this system, a signer generates a key pair:

  • a secret key, that will be used to sign any number of messages
  • a public key, that anybody can use to verify that the signature for a message was actually issued by the creator of the public key.

Verifiers need to already know and ultimately trust a public key before messages signed using it can be verified.

Key pair generation

void hydro_sign_keygen(hydro_sign_keypair *kp);

The hydro_sign_keygen() function generates a secret key and a corresponding public key. The public key is put into kp->pk (hydro_sign_PUBLICKEYBYTES bytes) and the secret key into kp->sk (hydro_sign_SECRETKEYBYTES bytes).

void hydro_sign_keygen_deterministic(
    hydro_sign_keypair *kp, const uint8_t seed[hydro_sign_SEEDBYTES]);

Using hydro_sign_keygen_deterministic(), the key pair can be deterministically derived from a single key seed (hydro_sign_SEEDBYTES bytes).

Single-part signature

int hydro_sign_create(uint8_t csig[hydro_sign_BYTES], const void *m_,
    size_t mlen, const char ctx[hydro_sign_CONTEXTBYTES],
    const uint8_t sk[hydro_sign_SECRETKEYBYTES]);

The hydro_sign_create() function computes a signature for a message m_ whose length is mlen bytes, using the secret key sk and a context ctx.

The signature is put into csig, and is hydro_sign_BYTES bytes long.

int hydro_sign_verify(const uint8_t csig[hydro_sign_BYTES], const void *m_,
    size_t mlen, const char ctx[hydro_sign_CONTEXTBYTES],
    const uint8_t pk[hydro_sign_PUBLICKEYBYTES]);

The hydro_sign_verify() function checks that the signed message m_ whose length is smlen bytes has a valid signature for the public key pk and the context ctx.

If the signature is doesn't appear to be valid, the function returns -1.

On success, it returns 0.

Multi-part signature

If the whole message doesn't fit in memory, it can be provided as a sequence of arbitrarily-sized chunks.

The multi-part API is almost the same for signing and for verification.

int hydro_sign_init(
    hydro_sign_state *state, const char ctx[hydro_sign_CONTEXTBYTES]);

int hydro_sign_update(hydro_sign_state *state, const void *m_, size_t mlen);

int hydro_sign_final_create(hydro_sign_state *state,
    uint8_t                                   csig[hydro_sign_BYTES],
    const uint8_t                             sk[hydro_sign_SECRETKEYBYTES]);

int hydro_sign_final_verify(hydro_sign_state *state,
    const uint8_t                             csig[hydro_sign_BYTES],
    const uint8_t                             pk[hydro_sign_PUBLICKEYBYTES]);

The hydro_sign_init() function initializes a state state using the context ctx.

Individual chunks composing the entire message can then be hashed using hydro_sign_update(). In this function m_ is the address of a chunk, and mlen its own length.

Finally:

  • hydro_sign_final_create() can be used to compute a signature using the secret key sk.
  • or hydro_sign_final_verify() can be used to verify a signature using the public key pk.

hydro_sign_final_verify() returns -1 if the signature doesn't appear to be valid for the given message, context and public key, or 0 if it could be successfully verified.

Constants

#define hydro_sign_BYTES 64
#define hydro_sign_CONTEXTBYTES 8
#define hydro_sign_PUBLICKEYBYTES 32
#define hydro_sign_SECRETKEYBYTES 64
#define hydro_sign_SEEDBYTES 32

Data types

typedef struct hydro_sign_state {
    hydro_hash_state hash_st;
} hydro_sign_state;

typedef struct hydro_sign_keypair {
    uint8_t pk[hydro_sign_PUBLICKEYBYTES];
    uint8_t sk[hydro_sign_SECRETKEYBYTES];
} hydro_sign_keypair;

Notes

The nonces are non-deterministic. They are computed using the output of the CSPRNG as well as a hash of the message to be signed. As a result, signing the same message multiple times can produce different, all valid signatures. However, only the owner of the secret key can compute a valid signature.