Public-key signatures

Example (combined mode)

#define MESSAGE (const unsigned char *) "test"
#define MESSAGE_LEN 4

unsigned char pk[crypto_sign_PUBLICKEYBYTES];
unsigned char sk[crypto_sign_SECRETKEYBYTES];
crypto_sign_keypair(pk, sk);

unsigned char signed_message[crypto_sign_BYTES + MESSAGE_LEN];
unsigned long long signed_message_len;

crypto_sign(signed_message, &signed_message_len,
            MESSAGE, MESSAGE_LEN, sk);

unsigned char unsigned_message[MESSAGE_LEN];
unsigned long long unsigned_message_len;
if (crypto_sign_open(unsigned_message, &unsigned_message_len,
                     signed_message, signed_message_len, pk) != 0) {
    /* incorrect signature! */
}

Example (detached mode)

#define MESSAGE (const unsigned char *) "test"
#define MESSAGE_LEN 4

unsigned char pk[crypto_sign_PUBLICKEYBYTES];
unsigned char sk[crypto_sign_SECRETKEYBYTES];
crypto_sign_keypair(pk, sk);

unsigned char sig[crypto_sign_BYTES];

crypto_sign_detached(sig, NULL, MESSAGE, MESSAGE_LEN, sk);

if (crypto_sign_verify_detached(sig, MESSAGE, MESSAGE_LEN, pk) != 0) {
    /* incorrect signature! */
}

Example (multi-part message)

#define MESSAGE_PART1 \
    ((const unsigned char *) "Arbitrary data to hash")
#define MESSAGE_PART1_LEN 22

#define MESSAGE_PART2 \
    ((const unsigned char *) "is longer than expected")
#define MESSAGE_PART2_LEN 23

unsigned char pk[crypto_sign_PUBLICKEYBYTES];
unsigned char sk[crypto_sign_SECRETKEYBYTES];
crypto_sign_keypair(pk, sk);

crypto_sign_state state;
unsigned char sig[crypto_sign_BYTES];

/* signature creation */

crypto_sign_init(&state)
crypto_sign_update(&state, MESSAGE_PART1, MESSAGE_PART1_LEN);
crypto_sign_update(&state, MESSAGE_PART2, MESSAGE_PART2_LEN);
crypto_sign_final_create(&state, sig, NULL, sk);

/* signature verification */

crypto_sign_init(&state)
crypto_sign_update(&state, MESSAGE_PART1, MESSAGE_PART1_LEN);
crypto_sign_update(&state, MESSAGE_PART2, MESSAGE_PART2_LEN);
if (crypto_sign_final_verify(&state, sig, pk) != 0) {
    /* message forged! */
}

Purpose

In this system, a signer generates a key pair consisting of:

  • A secret key, which you can use to append a signature to any number of messages.

  • A public key, which anybody can use to verify that the signature appended to a message was 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.

Warning: this is different from authenticated encryption. Appending a signature does not change the representation of the message itself.

Key pair generation

int crypto_sign_keypair(unsigned char *pk, unsigned char *sk);

The crypto_sign_keypair() function randomly generates a secret key and a corresponding public key. The public key is put into pk (crypto_sign_PUBLICKEYBYTES bytes) and the secret key into sk (crypto_sign_SECRETKEYBYTES bytes).

int crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk,
                             const unsigned char *seed);

Using crypto_sign_seed_keypair(), the key pair can also be deterministically derived from a single key seed (crypto_sign_SEEDBYTES bytes).

Combined mode

int crypto_sign(unsigned char *sm, unsigned long long *smlen_p,
                const unsigned char *m, unsigned long long mlen,
                const unsigned char *sk);

The crypto_sign() function prepends a signature to a message m, whose length is mlen bytes, using the secret key sk.

The signed message, which includes the signature plus an unaltered copy of the message, is put into sm and is crypto_sign_BYTES + mlen bytes long.

If smlen is not a NULL pointer, then the actual length of the signed message is stored in smlen.

int crypto_sign_open(unsigned char *m, unsigned long long *mlen_p,
                     const unsigned char *sm, unsigned long long smlen,
                     const unsigned char *pk);

The crypto_sign_open() function checks that the signed message sm, whose length is smlen bytes, has a valid signature for the public key pk.

If the signature isn’t valid, then the function returns -1.

On success, it puts the message without the signature into m, stores its length in mlen if mlen is not a NULL pointer, and returns 0.

Detached mode

In detached mode, the signature is stored without attaching a copy of the original message to it.

int crypto_sign_detached(unsigned char *sig, unsigned long long *siglen_p,
                         const unsigned char *m, unsigned long long mlen,
                         const unsigned char *sk);

The crypto_sign_detached() function signs the message m, whose length is mlen bytes, using the secret key sk and puts the signature into sig, which can be up to crypto_sign_BYTES bytes long.

The actual length of the signature is put into siglen if siglen is not NULL.

It is safe to ignore siglen and always consider a signature as crypto_sign_BYTES bytes long; shorter signatures will be transparently padded with zeros if necessary.

int crypto_sign_verify_detached(const unsigned char *sig,
                                const unsigned char *m,
                                unsigned long long mlen,
                                const unsigned char *pk);

The crypto_sign_verify_detached() function verifies that sig is a valid signature for the message m, whose length is mlen bytes, using the signer’s public key pk.

It returns -1 if signature verification fails and 0 on success.

Multi-part messages

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

This uses the Ed25519ph signature system, which pre-hashes the message. In other words, what gets signed is not the message itself but its image through a hash function.

If the message can fit in memory and be supplied as a single chunk, then the single-part API should be preferred.

Note: Ed25519ph(m) is intentionally not equivalent to Ed25519(SHA512(m)).

If, for some reason, you need to pre-hash the message yourself, then use the multi-part crypto_generichash_*() APIs and sign the 512-bit output, preferably prefixed by your protocol name (or anything that will make the hash unique for a given use case).

int crypto_sign_init(crypto_sign_state *state);

The crypto_sign_init() function initializes the state state. This function must be called before the first crypto_sign_update() call.

int crypto_sign_update(crypto_sign_state *state,
                       const unsigned char *m, unsigned long long mlen);

Add a new chunk m of length mlen bytes to the message that will eventually be signed.

After all parts have been supplied, one of the following functions can be called:

int crypto_sign_final_create(crypto_sign_state *state, unsigned char *sig,
                             unsigned long long *siglen_p,
                             const unsigned char *sk);

The crypto_sign_final_create() function computes a signature for the previously supplied message using the secret key sk and puts it into sig.

If siglen_p is not NULL, then the length of the signature is stored at this address.

It is safe to ignore siglen and always consider a signature as crypto_sign_BYTES bytes long; shorter signatures will be transparently padded with zeros if necessary.

int crypto_sign_final_verify(crypto_sign_state *state, const unsigned char *sig,
                             const unsigned char *pk);

The crypto_sign_final_verify() function verifies that sig is a valid signature using the public key pk for the message whose content has been previously supplied using crypto_update().

Extracting the seed and the public key from the secret key

The secret key includes the seed (either a random seed or the one given to crypto_sign_seed_keypair()) and public key.

While the public key can always be derived from the seed, the precomputation saves a significant amount of CPU cycles when signing.

If required, Sodium provides two functions to extract the seed and public key from the secret key:

int crypto_sign_ed25519_sk_to_seed(unsigned char *seed,
                                   const unsigned char *sk);

int crypto_sign_ed25519_sk_to_pk(unsigned char *pk, const unsigned char *sk);

The crypto_sign_ed25519_sk_to_seed() function extracts the seed from the secret key sk and copies it into seed (crypto_sign_SEEDBYTES bytes).

The crypto_sign_ed25519_sk_to_pk() function extracts the public key from the secret key sk and copies it into pk (crypto_sign_PUBLICKEYBYTES bytes).

Data structures

  • crypto_sign_state, whose size can be retrieved using crypto_sign_statebytes()

Constants

  • crypto_sign_PUBLICKEYBYTES

  • crypto_sign_SECRETKEYBYTES

  • crypto_sign_BYTES

  • crypto_sign_SEEDBYTES

Algorithm details

  • Single-part signature: Ed25519

  • Multi-part signature: Ed25519ph

References

Notes

crypto_sign_verify() and crypto_sign_verify_detached() are only designed to verify signatures computed using crypto_sign() and crypto_sign_detached().

The original NaCl crypto_sign_open() implementation overwrote 64 bytes after the message, whereas the libsodium implementation doesn’t write past the end of the message.

Ed25519ph (used by the multi-part API) was implemented in libsodium 1.0.12.

The Ed25519 system was designed to compute deterministic signatures.

Non-deterministic (but also non-standard) signatures can be produced by compiling libsodium with the ED25519_NONDETERMINISTIC macro defined.

Computing an Ed25519 signature requires an Edwards25519 secret and a hashing prefix. But applications do not directly supply them: they are both internally derived from the seed.

One of these secrets is the scalar the Edwards25519 base point is multiplied with. That scalar is computed by calling crypto_hash_sha512(seed) and truncating the output to the first 32 bytes. There's nothing specific to libsodium here, this is how Ed25519 signatures work and have been standardized.

Last updated