CRYPTO_SIGN(3MONOCYPHER)
π CRYPTO_SIGN β bestie edition π
okay so this is the CRYPTO_SIGN(3MONOCYPHER) page, and it lives in the LOCAL manual section. it's about public key signatures and it is genuinely one of the coolest things in the whole library, no cap. grab a snack, we're doing cryptography π
β¨ NAME β¨
crypto_sign, crypto_check, crypto_sign_public_key β public key signatures
like. that's the whole vibe. you sign stuff, you check stuff, you derive public keys. iconic behavior honestly π
π SYNOPSIS π
#include <monocypher.h>
void
crypto_sign_public_key(uint8_t public_key[32],
const uint8_t secret_key[32]);
void
crypto_sign(uint8_t signature[64], const uint8_t secret_key[32],
const uint8_t public_key[32], const uint8_t *message,
size_t message_size);
int
crypto_check(const uint8_t signature[64], const uint8_t public_key[32],
const uint8_t *message, size_t message_size);
πΈ DESCRIPTION πΈ
so crypto_sign() and crypto_check() are the duo that gives you EdDSA public key signatures plus verification. signing and checking. the whole plot. π«Ά
here's what all the arguments are, bestie, one by one:
signatureβ the signature itself. it's the receipt, the proof, the little crown emoji of authenticity πsecret_keyβ a 32-byte random number, known only to you. literally only you. do not share it, do not post it, do not put it in the group chat. checkintro(3monocypher)about random number generation (spoiler: use your operating system's random number generator, it's the one that knows what it's doing). β οΈ do not use the same private key for both signatures and key exchanges β the public keys are different, and revealing both may leak information. that's a real warning, not a vibe check.public_keyβ the public key, generated fromsecret_keywithcrypto_sign_public_key(). this one you can yell from the rooftops.messageβ message to sign. yeah. the thing you're signing.message_sizeβ length ofmessage, in bytes.
also, lil fun fact: signature and message may overlap. slay, no buffer drama π
now the functions themselves:
crypto_sign_public_key()computes the public key of the specified secret key. math goes brrr, out comes the public half.crypto_sign()signs a message withsecret_key. thepublic_keyis optional, and will be recomputed if not provided β but heads up, that recomputation doubles the execution time. so if you have it, pass it. free speed, bestie β¨crypto_check()checks that a given signature is genuine. meaning: only someone who had the private key could have signed the message. it does not run in constant time. it does not have to in most threat models, because nothing is secret: everyone knows the public key, and the signature and message are rarely secret. if the message needs to be secret, usecrypto_key_exchange(3monocypher)andcrypto_lock_aead(3monocypher)instead. different tool, different job, periodt.
there's also an incremental interface available if you want to sign in pieces β see crypto_sign_init_first_pass(3monocypher). π§
β RETURN VALUES β
crypto_sign_public_key()andcrypto_sign()return nothing. they just do the thing and dip. π¦crypto_check()returns0for legitimate messages and-1for forgeries. that's it. zero means real, minus one means someone's lying to you π
π EXAMPLES π
these examples assume arc4random_buf() exists, which fills the given buffer with cryptographically secure random bytes. if arc4random_buf() does not exist on your system, see intro(3monocypher) for advice about how to generate cryptographically secure random bytes. don't freestyle your randomness, i'm begging π
generate a public key from a random secret key:
uint8_t sk[32]; /* Random secret key */
uint8_t pk[32]; /* Matching public key */
arc4random_buf(sk, 32);
crypto_sign_public_key(pk, sk);
/* Wipe the secret key if it is no longer needed */
crypto_wipe(sk, 32);
sign a message:
uint8_t sk [32]; /* Secret key from above */
const uint8_t pk [32]; /* Matching public key */
const uint8_t message [11] = "Lorem ipsu"; /* Message to sign */
uint8_t signature[64];
crypto_sign(signature, sk, pk, message, 10);
/* Wipe the secret key if it is no longer needed */
crypto_wipe(sk, 32);
check the above:
const uint8_t pk [32]; /* Their public key */
const uint8_t message [11] = "Lorem ipsu"; /* Signed message */
const uint8_t signature[64]; /* Signature to check */
if (crypto_check(signature, pk, message, 10)) {
/* Message is corrupted, abort processing */
} else {
/* Message is genuine */
}
see how crypto_wipe() shows up twice? that's not decoration, that's self care. clean up after yourself π
π SEE ALSO π
crypto_blake2b(3monocypher), crypto_key_exchange(3monocypher), crypto_lock(3monocypher), intro(3monocypher)
go read those too, they're all in the family π«Ά
π STANDARDS π
these functions implement PureEdDSA with Curve25519 and Blake2b, as described in RFC 8032. this is the same as Ed25519, with Blake2b instead of SHA-512. so like, Ed25519 but she got a glow up. same energy, different hash. iykyk π
π¦ HISTORY π¦
- the
crypto_sign(),crypto_check(), andcrypto_sign_public_key()functions appeared in Monocypher 0.2. - starting with Monocypher 2.0.5, modified signatures abusing the inherent signature malleability property of EdDSA now cause a non-zero return value of
crypto_check(); in prior versions, such signatures would be accepted. glow up achieved β¨ - a critical security vulnerability that caused all-zero signatures to be accepted was introduced in Monocypher 0.3; it was fixed in Monocypher 1.1.1 and 2.0.4. yeah that one was bad bad. all-zero signatures getting accepted is giving "the bouncer let everyone in" π good thing it's fixed.
π₯ SECURITY CONSIDERATIONS π₯
πΈ Signature malleability πΈ
signature malleability is the ability of an attacker to produce a valid signature with knowledge of only an existing signature and the public key. that is, given a message, a signature and a public key, an attacker could generate a new signature for the same message that is valid under the same public key. yeah. rude. Monocypher prevents signature malleability by only accepting signatures in canonical form. no knockoffs allowed, only the real thing π
on the other hand, EdDSA signatures are not unique like cryptographic hashes. the signing procedure is deterministic by specification and crypto_sign() follows this specification. however, someone with the private key can generate arbitrarily many valid, canonical, different signatures of the same message. because of this, never assume that signatures are unique. like, never ever. this is a hard never. write it on your mirror πͺ
π Fault injection and power analysis π
fault injection (also known as glitching) and power analysis may be used to manipulate the resulting signature and recover the secret key in some cases. this requires hardware access. if attackers are expected to have such access and have the relevant equipment, you may try use the incremental interface provided by crypto_sign_init_first_pass(3monocypher) to mitigate the side channel attacks. note that there may still be other power-related side channels (such as if the CPU leaks information when an operation overflows a register) that must be considered. so like, mitigation, not a force field. stay aware bestie π«Ά
and that's the page!! this one shipped with Debian and the date on it is September 26, 2020 β that's the version and the day it was stamped, and the manual name is CRYPTO_SIGN(3MONOCYPHER), in case you forgot which page you were on (you didn't, you're doing great). πβ¨ go sign something responsibly!! ππ¦
π¦ where this page comes from
- fedora 43: monocypher-devel 3.1.2-12.fc43 Β· LicenseRef-Callaway-BSD OR CC0-1.0
- fedora 43: monocypher-devel 3.1.2-12.fc43 Β· LicenseRef-Callaway-BSD OR CC0-1.0
- fedora 44: monocypher-devel 3.1.2-13.fc44 Β· LicenseRef-Callaway-BSD OR CC0-1.0
- fedora 44: monocypher-devel 3.1.2-13.fc44 Β· LicenseRef-Callaway-BSD OR CC0-1.0
- π the page's license: BSD-2-Clause OR CC0-1.0
- Copyright (c) 2017-2019 Loup Vaillant
- Copyright (c) 2017-2018 Michael Savage
- Copyright (c) 2017, 2019-2020 Fabio Scotoni
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms